Central API · v1

One credential boundary for every product I ship.

Kirada, Djib Payroll and SMKit never hold a Meta token or a Stripe key. They send one signed request to this service, and it handles the providers, the retries, the idempotency and the audit trail on their behalf.

Laravel 13 PHP 8.5 HMAC-SHA256 Queued & idempotent Encrypted at rest
0 Isolated queue lanes: webhooks, outbound sends and application events never block each other.
0 Delivery attempts per job, backing off 1s → 5s → 30s → 120s.
0 Signature freshness window before a request is rejected as stale.
0 Customer-service window enforced before free-form text requires a template.
0 Provider credentials stored in any product codebase. That is the entire point.
What it handles

Infrastructure built for real products, not a demo.

Provider complexity, credentials, retries and audit history stay here. Each product keeps a single contract and clear ownership of the events it cares about.

01 / Auth

Signed application access

Each product gets its own request and event secrets. Requests are timestamped, signed over the raw body, and replay-protected by a persisted single-use nonce.

  • HMAC-SHA256 over a canonical string
  • hash_equals comparison
  • 120 requests/min per application
02 / Messaging

WhatsApp orchestration

A provider-neutral contract routes traffic through the Meta Cloud API, with a second provider kept behind a hard-disabled flag so a stale column can never silently reroute production traffic.

  • Templates and free-form text
  • Customer-service window enforced
  • Inbound product routing
03 / Billing

Centralised Stripe billing

Checkout sessions, portal sessions, customer mapping and the full Stripe webhook lifecycle are coordinated once, then fanned out to whichever product owns the customer.

  • Redirect host allow-listing
  • Signed webhook verification
  • Per-product billing callbacks
04 / Events

Reliable event delivery

Status changes become durable event rows with stable IDs before anything is sent. Delivery is queued, signed, retried on transient failures, and recorded either way.

  • Stable ULID event IDs
  • Retry only on 5xx and 429
  • Attempt count and last error stored
05 / Privacy

Protected data boundaries

Phone numbers, display names and message bodies are encrypted at rest. Lookups run against deterministic SHA-256 hashes, so no plaintext identifier is ever indexed.

  • Authenticated encryption casts
  • Hash-based contact lookup
  • Logs carry IDs, never content
06 / Operations

Observable by design

Queues, exceptions, slow requests and failed deliveries surface in one secured console. It is deliberately read-only — every mutation is an audited console command instead.

  • MFA-required operations panel
  • Laravel Pulse metrics
  • Readiness probe for dependencies
How it works

Three trust boundaries, one signing scheme.

Traffic crosses a boundary in three directions. All three verify the exact raw body with the same canonical string, so there is one rule to reason about instead of three.

canonical string
METHOD
PATH
TIMESTAMP
REQUEST_ID
SHA256(raw body)
01
Provider → API

Meta and Stripe webhooks are verified against their own signature schemes, persisted as durable event rows, then queued. The HTTP 200 goes back before any domain work runs, so a slow processor never causes a provider retry storm.

X-Hub-Signature-256 Stripe-Signature
02
Product → API

Every /api/v1 route sits behind one middleware that checks the signature, that the application is enabled, that the timestamp is fresh, that the request ID has never been used, and that the caller is inside its rate limit.

X-BWA-App X-BWA-Timestamp X-BWA-Request-ID X-BWA-Signature
03
API → product

Outbound events are signed with a separate event secret and posted to the product's webhook. A 200 or 202 marks it delivered; a 5xx or 429 is thrown back onto the queue; anything else is recorded as a permanent failure with its status.

Separate event secret Stable event_id
Checking…

Live dependency readiness

Read live from GET /health/ready, which verifies the database, cache, queue connection and the configured WhatsApp provider on every call.

Built as the foundation for a growing product portfolio.

Adding a product is a configuration exercise, not another provider integration project. Read the integration contract, or see how the whole thing is put together.