All articles
Fintech9 min read

M-Pesa Daraja API Integration: A Practical Guide for Kenyan Developers

STK Push, C2B, B2C, B2B — how the Daraja API actually works in production, the common traps, and what every developer should verify before money moves.

TGJOF ENTERPRISE

TGJOF ENTERPRISE

21 September 2026

Every successful Kenyan fintech product runs on M-Pesa, and M-Pesa runs on Daraja — Safaricom's API for payment integration. The documentation is readable, but production reality is where most teams get burned. This guide distils what we have learned shipping real money flows end to end.

Know the four API families before you write code. STK Push (Lipa na M-Pesa Online) initiates a payment prompt on the customer's phone — the customer authorises, and your product does nothing until their PIN is entered. C2B collects money from a customer to your PayBill or Till. B2C sends money out to a customer's phone, like a withdrawal or refund. B2B moves money between businesses, which is how rent and other payments reach a landlord's own PayBill directly.

The flows sound similar but the failure modes are completely different. STK Push is asynchronous: you send the prompt request, get back an immediate response with a CheckoutRequestID, and the actual confirmation arrives later through a callback. C2B callbacks arrive on your registered URL whenever someone pays your PayBill. B2C responses come over an Overlay callback. Treat every one of these as async — never assume a prompt that was initiated was also paid.

Here is the trap that costs companies real money: with STK Push, a 2000 response confirming the prompt was sent is NOT confirmation that payment happened. The customer may dismiss the prompt, enter the wrong PIN three times, or have insufficient funds. The only reliable signal is the result of a transaction-query call you make yourself with the CheckoutRequestID. Query, then credit. Never credit from the prompt response alone.

Callbacks deserve the same suspicion. Safaricom's classic callbacks carry no cryptographic signature header, so many teams invent HMAC checks that every genuine callback then fails. The safer pattern is to treat callbacks as hints, verify the real status by querying Safaricom yourself, and keep your confirmation logic idempotent — the same payment should credit a balance exactly once even if the callback arrives twice.

Something every integration must plan for is idempotency and expiry. Customers abandon payment prompts all the time. In production you need a job that finds initiated-but-unconfirmed transactions and, after a sane timeout, marks them expired and frees any money they were holding in escrow. If you skip this, failed payments leak balance forever and your reconciliation report never balances.

Security is non-negotiable. Your consumer secret and passkey live only in server-side functions, never in a mobile app — an app that ships the secret is a machine that can sign requests for anyone who reads the binary. Webhook URLs should accept only expected traffic, rate-limit everything that touches money, and log every gateway message to an audit store you can replay.

Finally, build for reconciliation from day one. Every payment should carry a unique reference that survives the round trip, so a month later you can prove which transactions settled, which were reversed and which are still pending. Teams that add reconciliation after launch spend months explaining why the ledger disagrees with the bank. Teams that design it in from the start just download a report.

This is exactly the discipline we apply inside KodiiPay, where thousands of rent and bill payments are verified, idempotently confirmed and reconciled every day. If you are planning an M-Pesa integration, do not rush to the sandbox write-up — start with the query, callback and reconciliation design, and the rest becomes easy.

Keep reading