Safaricom's Daraja API is the entry point to M-Pesa for every developer building in East Africa. It is also, to put it charitably, a documentation challenge.
The official docs describe the happy path. This post covers what happens when you leave it.
STK Push: the timeout problem
STK Push initiates a payment prompt on the customer's phone. The callback fires when the transaction completes — or fails. What the docs don't say clearly: callbacks can arrive seconds or minutes later, and they can arrive out of order.
The correct implementation uses idempotent webhook handlers keyed on MpesaReceiptNumber, not on the checkout request ID. We have seen duplicate callbacks arrive for the same transaction in production.
The sandbox vs production gap
Sandbox and production behave differently in ways that are not documented. Phone number validation is stricter in production. Timeout windows differ. Some callback fields present in sandbox responses are absent in production ones.
The full guide — including our complete Django implementation with error handling, idempotency, and retry logic — is available to Insights members.