Skip to content

Webhooks

How Wajub tells your server what happened, and how to trust it.

A webhook tells your server the outcome of a payment, a transfer or any other change, without you asking for it. Three rules make an integration correct; the rest of this section details them.

RuleWhy
Verify the signature before reading the bodyAnyone can POST to your URL
Answer inside 10 seconds, work afterwardsA slow handler is retried, so it duplicates
Key on the event idThe same event can legitimately arrive twice

The envelope

Every delivery has the same shape. The payload never varies by event type, only data does.

A payment.succeeded delivery
{
"id": "evt_test_aio5DpN577tNU2vOxdmuZGhT",
"event": "payment.succeeded",
"livemode": false,
"created": "2026-05-24T10:21:09+00:00",
"api_version": "2026-10-11",
"pending_webhooks": 1,
"request": {
"id": null,
"idempotency_key": "idem_kZ3qP8mWvL2xR7tB5nY4hC6dF9jS1aG0"
},
"data": {
"id": "trx_CSUGajfv9xh0XQ5wu2lx",
"reference": "order-4172",
"amount": 25000,
"amount_paid": 25000,
"currency": "XAF",
"status": "succeeded",
"channel": "cm.mtn",
"customer": {
"id": "cus_test_4tRb3nP8sZcXvK2mQ9wL",
"email": "amina@example.com"
}
}
}

data is the full resource, byte for byte what GET /payments/{id} returns under transaction. You never need a second call to learn what happened, though making one is a reasonable way to re-check a payment you consider high value. Match an event to your own order through data.reference, the reference you supplied, or data.metadata.

FieldWhat it carries
idThe event id, your deduplication key
eventThe type, for example payment.succeeded
livemodefalse in sandbox, true in live
createdWhen the event was recorded, ISO 8601
api_versionThe version the payload was serialised at
pending_webhooksHow many endpoints this event is going to
dataThe resource itself

The headers

What arrives with the body
X-Wajub-Signature: v1=8f3c1b0e4a7d...
X-Wajub-Timestamp: 1748081269
X-Wajub-Event: payment.succeeded
X-Wajub-Delivery-Id: whd_7Yh2MpL4tRb3nP8sZcXv
Content-Type: application/json

The first two are what you verify against. X-Wajub-Event lets you route before parsing, and X-Wajub-Delivery-Id identifies the delivery of this event to this endpoint: it stays the same on every retry, and differs from one endpoint to the next.

What happens end to end

  1. 1

    You register an endpoint

    A URL, in Konsole or through POST /webhooks. A live endpoint must use https://; a sandbox endpoint may use http://. Each endpoint gets its own signing secret.

  2. 2

    Something happens

    A payment succeeds, a transfer fails, a dispute opens. Wajub records an event.

  3. 3

    Wajub posts it

    Signed, to every endpoint subscribed to that type, with a 10 second timeout.

  4. 4

    You answer 2xx

    Anything else, or no answer in time, and the delivery is tried again for up to 72 hours.

Where to go next

You want toRead
Receive one on your laptopQuickstart
Prove it came from WajubSignature verification
Know which events existEvent catalogue
Understand a duplicateRetries and failures
Decide against webhooksWebhooks or polling

You do not need a public URL to start

wajub listen --forward-to http://localhost:3000/webhooks streams live events straight to your machine, signed the same way as production. The quickstart starts there.

What did you think of this content?