The event envelope
Every delivery has the same outer shape and the same headers. Only data varies by event type.
Every delivery, of every type, has the same outer shape. Only data varies.
{
"id": "3a6f19c8-2b74-4de0-91a5-6c8f13d0e4b7",
"type": "transfer.completed",
"apiVersion": "1.0",
"createdAt": "2026-08-21T10:14:22.183Z",
"environment": "test",
"tenant": "acme",
"subject": { "type": "financial_transaction", "reference": "3f1c6a2e-8b47-4d19-9a0e-15d2c7b8e441" },
"owner": { "type": "ORGANIZATION", "reference": "9b2e17a4-5c33-4f80-8de1-6a4f92c0b7d5" },
"data": {
"reference": "3f1c6a2e-8b47-4d19-9a0e-15d2c7b8e441",
"type": "PEER_TRANSFER",
"status": "COMPLETED",
"amount": { "amount": "5000.00", "currency": "NGN" },
"fee": { "amount": "26.88", "currency": "NGN" },
"narration": "October rent",
"postedAt": "2026-08-21T10:14:22.101"
}
}
Fields
| Field | Type | Notes |
|---|---|---|
id |
string | The event’s reference. Stable across every endpoint and every retry. Your idempotency key. |
type |
string | The catalogue name. Route on this. |
apiVersion |
string | The payload contract version for this event type, frozen once published. |
createdAt |
string | When the event happened, ISO-8601 UTC. Not when this attempt was sent. |
environment |
string | test or live: the environment the event happened in. |
tenant |
string | Your tenant. |
subject |
object | { type, reference }: what the event is about, and what you’d fetch to see more. |
owner |
object | { type, reference }: whose event it is (USER, ORGANIZATION or TENANT). |
data |
object | The event-type-specific payload. |
Three notes that save an afternoon each
datareuses the shape the REST API already returns wherever one exists, so most fields will look familiar.- Fields are added, never removed or renamed, within an
apiVersion. Parse permissively. - Event type names never change. A superseded type is marked
DEPRECATEDwith a retirement date and keeps firing until then.
Headers on every delivery
| Header | Example | Notes |
|---|---|---|
User-Agent |
Bridge-Webhooks/1.0 |
|
Content-Type |
application/json |
|
Bridge-Event-Id |
3a6f19c8-… |
Same as id. Dedupe without parsing. |
Bridge-Event-Type |
transfer.completed |
Route without parsing. |
Bridge-Api-Version |
1.0 |
|
Bridge-Environment |
test |
The environment the event happened in. |
Bridge-Timestamp |
1787307262 |
A copy of the t= in the signature, for convenience. Verify against the t= inside Bridge-Signature. |
Bridge-Signature |
t=1787307262,v1=6f3c… |
See Verify a signature. |
| your own headers | X-Merchant-Route: ledger-sync |
Whatever you registered on the endpoint. |
The body is byte-identical across retries, so the signature doesn’t change and deduping on a hash of the body still works.
No Authorization header is ever sent, and an endpoint can’t add one. A bearer token in a header has none of the signature’s properties: it isn’t bound to the body, doesn’t expire and can’t be rotated with overlap. Verify the signature.