Fintech • 45 min • Feb 26, 2026
Six gotchas in the Daraja v3 API that have caught every team we've worked with, and the patterns we use to avoid them in production.
Daraja's callback model is best-effort. Build your reconciliation logic on the assumption that callbacks may be late, duplicated, or missing entirely.
Timeouts are misleading. A 504 from Daraja often means the transaction succeeded on the M-Pesa side. Never auto-retry without checking transaction status first.
Sandbox does not behave like production for STK push. Test on real production credentials with small amounts before launching.
Auto-generated and lightly edited. Let us know about errors.
Wanjiru Kamau: Welcome everyone. Today I'm going to walk through six gotchas in the M-Pesa Daraja v3 API that we've seen in every customer integration we've done. These are the things that catch every team — including ours, the first time we shipped a Daraja integration in 2019. Gotcha one: callbacks are best-effort. Daraja sends you a callback when a transaction completes. The documentation implies that callback is reliable. It isn't. We have observed callbacks that arrive forty minutes late. We have observed duplicate callbacks for the same transaction. We have observed callbacks that simply never arrive. Your reconciliation logic must assume all three. The pattern we use is — on every transaction, schedule a status query for sixty seconds in the future. If the callback has arrived by then, cancel the query. If it hasn't, query Daraja directly for the transaction status. Gotcha two: 504 timeouts do not mean failure. If you get a 504 from Daraja, the transaction may still have completed on the M-Pesa side. If you treat 504 as failure and retry, you risk debiting the customer twice. The pattern is — on any non-2xx response, query the transaction status before deciding what to do. Gotcha three: STK push behaves differently in sandbox versus production. In sandbox, STK push is synchronous and very fast. In production it depends on the customer's network, their phone state, and Safaricom's load. We've seen STK pushes take ninety seconds to display on the customer's phone. Your user experience needs to handle that — show a clear waiting state, give the customer a way to cancel, and don't time out from your side faster than two minutes. Gotcha four: phone number formatting. Daraja is picky about phone numbers. It expects 254... with no leading zero, no plus, no spaces. If you send a number with the leading zero, the request fails with a generic error that doesn't say what's wrong. Normalize at the boundary. Gotcha five: pre-production credentials expire. Sandbox credentials are time-limited. We've seen teams build an integration against sandbox, ship it to staging, leave it for two weeks, come back and find everything broken because the sandbox credentials expired. Production credentials don't have this problem, but sandbox does. Plan around it. Gotcha six: amount limits. Daraja enforces a maximum transaction amount of 150,000 KES per single transaction. If you process larger amounts, you have to split. The platform won't tell you that in a useful way — it just rejects with a generic error. Validate at your edge. There's a bonus seventh gotcha — your sponsor bank's settlement cutoff. Even though Daraja accepts transactions 24/7, your sponsor bank settles on a schedule. If you accept a payment at 22:00 on Friday, the funds might not appear in your account until Monday morning. Your customer-facing communication needs to account for that. All the code samples and the reconciliation pattern are in the GitHub repo linked below.
We run custom 60-minute briefings for enterprise customers. Topics tailored to your engagement.
Request a private briefing