Smartphone with mobile apps beside a laptop, building for the phone

M-PESA was built under constraints most card APIs have never faced. It had to work on a feature phone, over USSD, on a patchy network, for someone who may have no smartphone and no bank account. Those constraints pushed it toward a design that is less polished than a modern card API and far more reliable in the field. After integrating Safaricom's Daraja API on a range of projects, here is what stuck with us.

It is asynchronous, and it admits it

A card charge feels instant. You call an endpoint and get an authorisation back. M-PESA does not pretend. With STK Push you start a request, the customer approves it on their handset, and the result comes back later on a callback you registered. Your system has to accept that the answer arrives out of band, sometimes after a few seconds, sometimes after a timeout.

That forces a healthier design. You cannot assume a payment worked because a function returned. You record the intent, wait for confirmation, and reconcile.

Trust the callback, but verify it

The confirmation callback is the source of truth, and it can arrive more than once. We build every handler to be idempotent, so the same transaction processed twice never credits an account twice. The Safaricom transaction ID is stored and made unique, and the database rejects duplicates instead of relying on application code to remember.

We also never trust an amount or a phone number sent from the browser. The callback from Safaricom is authoritative. The client is not.

Rules we keep on every integration

  • Write a pending record before you call, so a crash between request and callback leaves a trail to reconcile rather than a silent gap.
  • Reconcile on a schedule. Callbacks do get lost, and a daily check against the transaction status API closes the loop on anything that never came back.
  • Fail loudly on timeout. If a customer never approves the prompt, say so clearly instead of spinning.
  • Keep test and live credentials in configuration, never in code, and assume the sandbox behaves differently from production, because it does.

The lesson

Stripe optimises for developer comfort. M-PESA optimises for working in the real conditions of an emerging market, so it assumes failure, accepts delay, and reconciles constantly. Those happen to be the properties you want in any payment system. Building on it makes you a more careful engineer, because the platform never lets you pretend a payment is simple.

More in Engineering

Keep reading.