Callback Reliability and Reconciliation
Why this matters
Section titled “Why this matters”Daraja integrations are asynchronous. If you only look at the first API response, you will eventually misclassify payments or payouts.
Production-safe integrations assume:
- callbacks can arrive late
- callbacks can be retried
- timeout notifications can arrive before a final result
- your own receiver can be down temporarily
- a
503or unavailable listener can cause Daraja to drop a callback result
Endpoint map by API
Section titled “Endpoint map by API”| Flow | Primary callback | Secondary callback or recovery path |
|---|---|---|
| STK Push | callbackUrl | stkQuery() if callback is delayed |
| C2B | confirmationUrl | validationUrl before completion, then pull.query() for reconciliation |
| B2C | resultUrl | queueTimeOutUrl plus reconciliation |
| Account balance | resultUrl | queueTimeOutUrl |
| Transaction status | resultUrl | queueTimeOutUrl |
| Reversal | resultUrl | queueTimeOutUrl |
Idempotency rules
Section titled “Idempotency rules”Your callback handlers should be safe to run more than once.
- For STK, key idempotency on
CheckoutRequestID. - For C2B confirmation, key idempotency on
TransID. - For B2C, account balance, transaction status, and reversal, key idempotency on
ConversationIDandOriginatorConversationID. - If the same callback arrives twice, do not create a second payment or payout record.
What to persist
Section titled “What to persist”At minimum, keep:
- your own business record ID, such as order ID, payout batch ID, or reconciliation task ID
- Daraja correlation IDs
- current internal status such as
submitted,paid,failed,timed_out, orneeds_review - raw callback payloads
- timestamps for first seen, last seen, and any retries
- M-Pesa receipt number or transaction ID when available
Network controls
Section titled “Network controls”If you protect callback routes with IP filtering, allow the Safaricom gateway callback IPs documented in the callbacks guide. Otherwise your firewall can become the reason Daraja cannot deliver the result at all.
Timeout callbacks are not the same as final failure
Section titled “Timeout callbacks are not the same as final failure”queueTimeOutUrl means the workflow did not finish in time from Daraja’s perspective.
- Record the timeout immediately.
- Do not automatically resend money or retry the payment.
- Schedule reconciliation first.
- If a later final callback arrives, let that final callback win.
A practical reconciliation loop
Section titled “A practical reconciliation loop”Use a periodic worker to review records stuck in intermediate states.
- Find records in
submittedortimed_outstate beyond your expected callback window. - For STK, run
stkQuery()using the storedCheckoutRequestID. - For C2B, compare your received confirmations with the M-Pesa Org portal and run Pull Transactions for the affected window.
- For B2C, reversal, balance, or status flows, inspect stored conversation IDs and your operational logs.
- Move ambiguous records to
needs_reviewinstead of guessing. - Keep every recovery attempt in your audit history.
Suggested outcome rules
Section titled “Suggested outcome rules”- Callback success beats synchronous acceptance.
- Callback failure beats synchronous acceptance.
- Timeout creates uncertainty, not final business truth.
- Reconciliation results should update the record, but they should not erase the original callback history.
When to escalate manually
Section titled “When to escalate manually”Escalate to finance or support when:
- the customer reports payment success but you never received the callback
- you have conflicting status between a callback and your internal ledger
- a payout timed out and the beneficiary claims they still received funds
- you need to decide whether to trigger Transaction Status or Reversal