M-Pesa API Integration in Kenya: Callbacks and Reconciliation
Daraja product/version check: The Transaction Status and reversal details below are product- and account-specific examples, not a universal Daraja contract. Before relying on exact identifiers, eligible transaction types, result callbacks, permissions, or availability, verify them in the current documentation for the product and account you have enabled. Follow that contract if it differs from this guide.
An M-Pesa payment flow needs an explicit record of each attempt, provider event, business decision, and unresolved difference. A callback is one input to that record; it is not a substitute for the payment ledger or reconciliation. The examples below focus on a request-initiated collection flow such as an STK journey. Daraja products, accounts, callback fields, query access, and production requirements differ, so confirm the current contract for the enabled product in the Safaricom Daraja API catalogue.
Last updated: September 19, 2026.
Define the internal payment states
Keep the order or invoice separate from each attempt to collect payment. A useful internal model distinguishes an attempt that is being created, awaiting a result, confirmed, declined, and later reversed. A local timeout means the result is unknown; it is not proof that the customer did not pay. Preserve the original event and state history when the current state changes.
Write down allowed transitions for the selected product and business policy. For example, a verified late success may resolve an uncertain attempt, while a later reversal is a new financial event that may require a compensating ledger entry. Do not let an older or conflicting event silently move a completed record backwards.
Map provider events to decisions
Normalise the selected Daraja payload into your own internal event shape only after validating it against that product's current documentation. Keep the provider's actual names and codes at the integration boundary; avoid assuming one product's payload applies to another.
| Situation | Safe business handling |
|---|---|
| Request is accepted for processing | Keep the attempt pending. A request acknowledgement is not payment confirmation |
| Valid successful result matches the expected attempt | Check the provider identity, reference, amount and relevant account details; record confirmation once, then allow the approved business action |
| Customer cancels or the attempt fails | Record the outcome for that attempt; keep the order unpaid and offer the agreed retry or support path |
| The same provider event is delivered again | Recognise the same event identity and make the repeated delivery a no-op for the ledger, fulfilment, receipt and notification |
| A different successful receipt is linked to an already-paid order | Treat it as a possible second collection; hold it for the agreed allocation, refund, or finance review |
| Reference or amount does not match | Keep the event in an exception or unallocated queue; do not attach it to a nearby order by guesswork |
| Result arrives after a local timeout | Reconcile the attempt and apply the verified result once; do not treat the earlier timeout as a failure |
| A reversal is reported after confirmation | Store a separate reversal event and apply the authorised compensating business and ledger actions while retaining the original confirmation |
A unique deduplication key must represent the event identity defined by the provider contract. Where the contract does not provide a suitable stable event identifier, agree and test a product-specific fallback with the provider documentation; do not deduplicate only on a broad label such as “success,” because a later reversal or second receipt is a different event.
Process a callback without duplicating effects
Use a short, durable processing path:
- Accept the request over HTTPS and validate it using the authentication, validation, and response rules for the configured Daraja product. Do not invent a generic signature check or trust a success message sent by the browser.
- Normalise only the fields needed by the application and persist the validated event or a suitably protected audit record with its deduplication identity. Avoid retaining unnecessary personal data or secrets.
- Find the internal payment attempt using the approved reference. Compare the expected amount and other required details. If there is no unambiguous match, retain the event for review instead of changing an order.
- In a database transaction, enforce the unique event and payment constraints, check that the transition is allowed, and record the payment or exception. Make ledger changes idempotent as well as payment-state updates.
- Trigger fulfilment, email, SMS, or WhatsApp through a durable after-commit or outbox pattern. Make each downstream action idempotent; a database lock around the payment row cannot prevent a second message or job by itself.
- Return the response required by the product contract promptly. Run slow work outside the request where the contract permits it, and monitor failed processing for retry or review.
Treat this as an architecture checklist, not drop-in controller code. Choose the exact identifiers, validation, acknowledgement, retry, and query behaviour from the current product documentation and test them in that product's sandbox.
Reconcile provider records with the ledger
Reconciliation compares provider results or statements with internal payment attempts and shows a person what needs attention. Daraja 3.0 describes Transaction Status as a secondary reconciliation mechanism when callbacks are missing. It is asynchronous and its query requires an M-Pesa receipt number or Originator Conversation ID. A successful query response acknowledges the request; wait for the configured result callback to learn the transaction status. The Reversals API is also asynchronous and the current documentation describes reversing C2B transactions: its request acknowledgement is not the final reversal result, which arrives at ResultURL. The Daraja FAQ says B2C reversals are handled manually through the M-PESA portal. Confirm product availability, permissions, roles, and account eligibility for the flow in use.
| Provider side | Internal side | Resolution |
|---|---|---|
| Successful receipt, no matching local attempt | No confirmed payment | Place it in an unallocated queue; investigate the reference and account, then assign or refund under finance policy |
| No confirmed provider result yet | Attempt remains pending or timed out | Query or compare an approved provider record when available; keep the outcome uncertain until evidence resolves it |
| Confirmed provider receipt | Local attempt still pending | Verify the match and expected amount, then record confirmation once and preserve the source event |
| More than one receipt for one order | One payment already applied | Identify whether there was a second collection; keep each receipt distinct and follow the approved allocation or refund procedure |
| Amount or reference differs | Local attempt is unpaid or disputed | Hold for finance review with an audit trail; do not silently correct the order |
| Reversal after a confirmed payment | Original receipt remains in history | Record the reversal separately, reconcile the balance, and apply only the authorised adjustment to any related service or order |
Run reconciliation on a defined schedule and assign an owner and resolution status to each exception. Keep totals that can be compared for the same account and period, plus enough evidence to explain each adjustment. A green dashboard should mean discrepancies are resolved or explicitly assigned, not merely that a file was imported.
Test uncertain and exceptional cases
In the sandbox, exercise a successful result, cancellation or decline, duplicate delivery, a delayed result after timeout, an unknown reference, a mismatched amount, two distinct successful receipts for one order, a reversal, and temporary database or queue failure. Assert the final payment state, ledger count, order action, customer message, and audit record for each case. Include a concurrent duplicate test; sequential retries alone do not expose a race.
Keep secrets outside source control and server responses. Restrict payment-record access, redact tokens and unnecessary customer details from logs, and document who investigates missing results and unmatched receipts. The companion pre-production sign-off checklist turns these cases into launch evidence and owner assignments. If payment starts in WhatsApp, the bot planning guide covers the surrounding customer journey and handoff.
A practical next step
Need help applying this to a real system?
Statum can help turn the idea into a scoped piece of work. See our software services, browse completed projects, read the developer documentation, or start a project conversation.
Bob Mwenda
Bob Mwenda is a software engineer at Statum with over ten years of experience building business applications and integrating enterprise systems. His work spans Java, Spring Boot, PHP, Laravel and Vue.js, including API integrations, payment and messaging systems, and application deployment. On the Statum blog, Bob writes about software development, business automation and the practical decisions behind building and maintaining software. His articles help business owners and developers understand implementation options, technical trade-offs and common pitfalls.