Webhooks: the reliable way to know what happened

2 min readLast updated 16 September 2026 Read as plain text

The response to your own request is not the answer. The callback is, and treating it that way is the difference between an integration that works and one that mostly works.

When you create a mobile money payment, the response you get back says pending. It has to: the customer has not entered their PIN yet, and they might be asleep. The actual outcome arrives later, as an HTTP request from us to you, and that request is the only thing that should ever make your system treat a payment as paid.

Almost every integration bug in this space comes from acting on the first response instead of the second.

Verify before you trust

Every callback is signed with HMAC-SHA256 over the raw request body. Compute the same hash with your secret and compare in constant time. Verify against the raw bytes, not a parsed and re-serialised object, or the signature will never match and you will spend an afternoon deciding whether we are broken.

An endpoint that skips verification is an endpoint anyone can post to. That matters more here than it does for most webhooks, because the message being forged says money arrived.

Answer immediately, work afterwards

Return 200 as soon as you have stored the event. Anything slow, fulfilment, emails, a call to somebody else’s API, belongs in a queue. A handler that takes eight seconds will be retried while it is still running, and you will process the same payment twice.

Expect it more than once

Delivery is at-least-once. Retries happen on timeouts, on network faults, and on any non-2xx response. Make your handler idempotent on the payment id: if you have seen it, acknowledge and stop.

This is not a rare edge case to guard against defensively. It is the normal behaviour of every webhook system, including this one.

  • Verify the signature before parsing anything.
  • Return 200 for events you do not care about, or you will keep receiving them.
  • Log the event id, so a support question has an answer.
  • Never assume ordering. A success and a status update can arrive in either order.

When one does not arrive

Every delivery attempt is visible with the response we received, which is usually enough to see that the endpoint returned 500 or timed out. Where a callback is genuinely lost, the payment can always be read back from the API: the webhook is the fast path, not the only path.