Payments · M-Pesa Daraja API

PesaFlow — M-Pesa checkout that reconciles itself.

A production-grade Daraja integration: STK Push at checkout, callbacks that survive retries, and a reconciliation engine that matches every payment to its order — without anyone opening a spreadsheet.

99.4%Callback processing success
0Manual reconciliation hours / week
<3sCheckout-to-confirmation
9:41

M-PESA

KES 4,850
REF · PF-2025-04812
Pay toPESAFLOW LTD
Cancel Send
Enter PIN to complete payment
The problem

Payments came in. Nobody knew which order they belonged to.

The business sold online and collected via M-Pesa, but the reconciliation was manual: someone exported the M-Pesa statement, opened a spreadsheet, and tried to match amounts and timestamps by hand. Every week, hours disappeared — and mismatches quietly piled up.

Before

  • Weekly manual matching of payments to orders
  • Duplicate callbacks created double-credited orders
  • Failed payments went unnoticed until a customer complained

After

  • Payments auto-matched to orders within seconds
  • Idempotent callbacks — one payment, one credit, always
  • Stuck payments trigger an alert before the customer calls
What's inside

Built for the messy reality of mobile money.

STK Push at checkout

A single API call sends the payment prompt to the customer's phone with the exact amount and reference. No paybill numbers to remember.

Idempotent callbacks

Daraja retries callbacks. Every request is keyed by transaction ID, so a retry is processed once — never double-crediting an order.

Auto-reconciliation

Incoming payments are matched to open orders by reference, amount and phone — with a confidence score and manual review queue for edge cases.

Failure alerts

When a payment is stuck, cancelled or times out, the right person gets notified instantly — with the order and customer context attached.

Reconciliation dashboard

Daily settlement summaries, matched vs. unmatched counts, and exportable reports that finance can hand to an auditor without editing anything.

Security & audit trail

Every request, callback and match decision is logged with a timestamp. Secrets are never in code, and webhooks are validated on receipt.

Payment lifecycle

From tap to matched order.

Each stage is independently observable, so when something goes wrong you know exactly where it stopped.

01 · INITIATE

Customer checks out

The app creates an order, generates a reference, and calls the Daraja STK Push endpoint.

02 · PROMPT

M-Pesa asks for PIN

The customer receives the payment prompt with the exact amount and business name.

03 · CALLBACK

Result is delivered

Daraja posts the result to our webhook. The handler is idempotent and always returns 200 fast.

04 · RECONCILE

Payment matches order

The engine matches by reference, amount and phone, then marks the order as paid or flags it for review.

webhook-handler.js
// Idempotent M-Pesa callback handler — safe under Daraja retries export async function handleMpesaCallback(req, res) { const body = req.body?.Body?.stkCallback; const txnId = body?.CheckoutRequestID; // 1. Always ACK quickly — Daraja retries on timeouts res.status(200).json({ ResultCode: 0 }); // 2. Deduplicate: has this checkout request already been processed? if (await seen(txnId)) return; // 3. Persist first, then reconcile — never lose a payment event await saveEvent({ txnId, raw: body }); await reconcile({ reference: body?.CallbackMetadata?.find(m => m.Name === 'AccountReference')?.Value, amount: body?.CallbackMetadata?.find(m => m.Name === 'Amount')?.Value, receipt: body?.CallbackMetadata?.find(m => m.Name === 'MpesaReceiptNumber')?.Value, }); }
Reconciliation view

The dashboard finance actually opens.

Today's reconciliation · 16 Sep

Live
Order Reference M-Pesa Receipt Amount Status
#4812 PF-2025-04812 SJH7XK9L2M KES 4,850 Matched
#4813 PF-2025-04813 SJH7XK9L3P KES 1,200 Matched
#4814 PF-2025-04814 — KES 7,400 Awaiting PIN
#4815 PF-2025-04815 SJH7XK9L5R KES 2,300 Matched
#4816 PF-2025-04816 — KES 9,900 Cancelled
Results

What changed for the business.

99.4%Callbacks processed successfully, including retries
0 hrsWeekly manual reconciliation time — down from ~6 hours
2.7sMedian time from PIN entry to order marked paid
100%Payments traceable from order to M-Pesa receipt

“We stopped arguing about whether a payment had come in. The dashboard settles it in one line.”

— Finance lead, e-commerce deployment
Stack

Built with.

M-Pesa Daraja API Node.js Express PostgreSQL Redis BullMQ React Docker AWS

Want M-Pesa payments you don't have to babysit?

I build Daraja integrations with proper idempotency, reconciliation and alerting — so your team stops chasing payments and starts trusting the numbers.