Automating M-Pesa Reconciliation With n8n and Daraja
A complete n8n build for Kenyan businesses: capture M-Pesa callbacks, pull the Safaricom Daraja record on a schedule, compare the two, and alert on every missing or mismatched payment the same day.

If your business collects M-Pesa payments through a paybill or till number, some of that money never reaches your books. Callbacks fail, customers abandon STK prompts, and the M-Pesa statement ends up being the only place the payment exists. This guide shows how to close that gap with n8n and Safaricom's Daraja API: record every callback, pull Safaricom's own transaction record on a schedule, compare the two, and get an alert on every missing or mismatched payment the same day.
The whole build is three n8n workflows plus a small database table. No custom application code, no Laravel, no middleware vendor.
Why M-Pesa reconciliation is a daily job
A Daraja confirmation callback is a notification, not a record. It arrives once, it is not signed, and if your server is down or slow when it arrives, Safaricom does not retry it in any way you can rely on. As the engineering guide at laraveldaraja.com puts it, after the callback fires there are two records of the same money: Safaricom's and yours. Reconciliation is the process of proving they match.
The scale makes this worth automating. The Central Bank of Kenya's mobile payments statistics show KSh 761 billion moving through mobile money agents in August 2026 alone, across 563,605 active agents and 95.31 million registered accounts. Monthly values through 2025 ran between KSh 636 and 722 billion. A tiny failure rate on that volume is still a lot of untracked money.
Daraja gives you one tool designed for exactly this problem: the Pull Transactions API. Two limits define the whole design, and both come from the laraveldaraja reconciliation guide:
- 48-hour retention. Pull only covers roughly the last two days. If you reconcile monthly, most of the window is already gone.
- C2B only. The pull covers customer-to-business receipts. B2C payouts, B2B transfers, and reversals are not in it.
So the correct cadence is hourly or daily, not monthly. A reconciliation job nobody notices has stopped is worse than none at all, which is why alerting is part of the build, not an afterthought.
What you need before you start
- A Daraja app. Create one free on the Daraja portal to get a consumer key and consumer secret. Sandbox works for building; production requires your paybill or till credentials from Safaricom.
- A self-hosted n8n instance. The reason is in the next bullet.
- The community node
n8n-nodes-mpesa-daraja(GitHub repo), an MIT-licensed wrapper for Daraja API 3.0, currently at version 0.1.8. It covers STK Push, C2B registration, B2C, B2B, balance, transaction status, reversal, and both Pull operations: registering the pull callback URL and querying a date range. - Somewhere to store payments. Postgres is the natural choice if you already run n8n on it. A single table keyed on the M-Pesa receipt number is enough to start.
- An alert channel. Email, Slack, or WhatsApp. Reconciliation output that nobody reads is a cost, not a control. If you plan to deliver alerts over WhatsApp, price the channel first: what Kenyan teams now pay for the WhatsApp Business API is rarely worth it for internal reports, where email or Slack is effectively free.
A word of caution on the community node. n8n's own community node risk documentation is blunt: an unverified community node is unreviewed npm code with full access to your server and to every workflow's data. This node is unverified, which is also why it cannot be installed on n8n Cloud, only on self-hosted instances. Two mitigations: pin the exact version you tested instead of tracking latest, and consider doing the Daraja calls with n8n's built-in HTTP Request node against the REST endpoints instead. The Daraja API is plain REST with OAuth, so the HTTP Request route costs you some convenience, not capability.
Workflow 1: record every callback the moment it happens
The first workflow is your real-time ledger. Its job is to capture each payment the instant Safaricom reports it.
Structure:
- M-Pesa Trigger node from the community package, set to the C2B confirmation event. The node gives you a webhook URL; register that URL with the C2B Register URL operation so Safaricom sends confirmations to it.
- A Code or Set node that normalizes the payload: receipt number, amount, transaction date, bill reference, and the masked phone number.
- A database insert written as update-or-create on the receipt number.
- A fast response. Return HTTP 200 immediately and let any slow downstream work run after the response.
The receipt number, something like NEF61H8J60, is the only identifier both your system and Safaricom's system agree on, so it becomes your unique key. Two details matter here:
- Make the handler idempotent. Duplicate C2B confirmations are a real, documented behaviour. An update-or-create on the receipt number makes a duplicate a harmless no-op. If you instead insert blindly, your own books double-count the money.
- Respond fast. Slow webhook endpoints get throttled at exactly the wrong moment, like the first of the month when rent payments spike. Write the row, answer 200, do everything else after.
If you also initiate STK Push payments, record every prompt you send in the same table with a pending status. That is how you catch the third state everyone forgets: the customer was prompted, never completed, and no callback of any kind will ever arrive. The prompt record is the only evidence that transaction was supposed to exist.
Workflow 2: pull the Daraja record on a schedule
The second workflow asks Safaricom for its version of events. This is the half most builds skip, and it is the half that makes callbacks trustworthy.
Structure:
- A Schedule Trigger running hourly. Hourly is a good default: far inside the 48-hour retention window, frequent enough that a broken run is still recoverable the same day.
- Register the Pull URL once. Daraja's pull is itself asynchronous. You register a callback URL for pull results, then each query posts its result set back to that URL.
- A pull query for the last hour (or last day, if you run daily) using the Pull API query operation in the community node.
- A second webhook workflow that receives the pull result and upserts each row into a second table, the provider-side ledger.
Three quirks from the reconciliation guide to bake in:
- Result code 1001 means empty, not broken. A pull over a period with no transactions returns code 1001. Treat it as a valid empty result. If you treat it as an error, your alert channel fills with false alarms every quiet night.
- The offset is a row offset, not a page number. A busy shortcode will not fit in one response. Page until the response stops returning rows, or you will silently reconcile against a partial list.
- Pick one timezone and reconcile in it. Safaricom timestamps and your server clock are not the same thing. Convert everything to one explicit timezone before comparing, or day-boundary payments will appear to vanish.
Workflow 3: compare and alert
The third workflow is the actual reconciliation: a scheduled comparison between your ledger (workflow 1) and Safaricom's ledger (workflow 2).
Run it on a schedule, after each pull completes. The comparison produces three disagreement types, and each has a different meaning:
- Missing: Safaricom has the payment, you do not. This is the dangerous one, usually a failed callback. Action: insert it from the pull record and flag it for review.
- Mismatched: both sides have the receipt, amounts differ. Rare, but it happens, and it always deserves a human eye.
- Unlisted: you have a record, the pull does not list it. Mostly STK prompts that never completed, or edge effects at the window boundary. Filter these against your pending prompts before alarming anyone, or this category becomes noise that trains staff to ignore the report.
The output should be a short report, not a data dump: counts of each category, the specific receipt numbers, and a total shilling value at stake. Send it through n8n's alert nodes, and wire the whole workflow chain to an error workflow. n8n lets you set an error workflow per workflow, starting with the Error Trigger node, so a crashed pull at 3am pages someone instead of failing silently. You can also use the Stop And Error node to deliberately fail the run when the comparison finds missing payments above a threshold, which turns your error workflow into the escalation path.
A worked example with real numbers
Say you manage a housing cooperative collecting rent through a paybill. On a typical due-date day you receive 1,200 payments averaging KSh 8,500, about KSh 10.2 million. Assume callbacks fail for 0.6 percent of payments, seven receipts, roughly KSh 59,500, that never lands in your system.
Without reconciliation, those seven payments surface weeks later, if at all, when a tenant disputes a balance and someone pulls a portal statement. With the hourly pull, the comparison flags all seven within the hour, auto-inserts them from Safaricom's record, and the report shows KSh 59,500 recovered and zero items pending. The pull costs you one scheduled workflow run per hour. That is the entire trade.
Daraja gotchas that will bite you
These are the failure modes that do not appear in vendor marketing pages. All of them are documented in the sources above; none of them are obvious until they cost you money.
- Phone numbers are masked in C2B callbacks. You cannot reliably match on MSISDN. The receipt number is the join key; design around it.
- Callbacks are unsigned. There is no signature to verify. You can restrict traffic to Safaricom IPs via an allow-list from your account manager, and for high-value goods you should confirm with the Transaction Status API before releasing anything. Treat the callback as a hint, the API as the truth.
- Amounts arrive as strings. "168.00" and "168.0" are the same money and different strings. Compare amounts as decimals, never as floats and never as raw string equality.
- The pull covers C2B only. B2C payouts, B2B transfers, and reversals need a different control: record the result callbacks from your own payout requests, and use Transaction Status queries for anything ambiguous.
- Anything older than two days is a manual job. Older records exist only as exports from the M-Pesa organisation portal. Your automated loop handles the present; the portal export handles history.
- Customer phone numbers are personal data. Kenya's Data Protection Act 2019 applies to what you store, and the Office of the Data Protection Commissioner is the regulator. Store the minimum you need, restrict access to the table, and set a retention policy.
What this costs to run
Honest numbers, since this is where most automation advice goes vague:
- Daraja API access: creating apps and credentials on the portal is free. The M-Pesa transaction charges your shortcode already pays do not change.
- Self-hosted n8n Community Edition: the software is free with no execution limit. A verified cost breakdown puts a solid single-server build on Hetzner at about $5.90 per month all-in. A more conservative analysis prices the cash floor at $16.60 a month with proper backups, $24.60 with monitoring, and rightly adds the hidden line item: roughly 4.5 hours a month of maintenance, which at a $25 to $75 hourly internal rate makes the true monthly cost $137 to $362.
- n8n Cloud instead: Starter is EUR 20 per month for 2,500 executions and Pro is EUR 50 for 10,000, both billed annually (n8n pricing, cross-checked October 2026). Do the maths for this use case: 1,200 callbacks a day plus hourly pulls is over 36,000 executions a month, which blows past Pro into Business territory. For a callback-heavy integration like this one, self-hosting is not the frugal option, it is the only sensible one. It is also required, since the M-Pesa community node cannot be installed on Cloud.
- Your time to build it: the three workflows are a day or two of focused work for someone comfortable in n8n, plus testing against the Daraja sandbox.
For comparison, the same three-ledger pattern appears in fund administration, where the stakes and the audit requirements are higher; we covered the cost and limit trade-offs there in AI reconciliation for fund administration.
When n8n is the wrong tool
n8n is a good fit up to a clear ceiling. Past it, be honest and build something else:
- Very high volume. Tens of thousands of callbacks a day means queue mode, Redis, worker scaling, and real on-call discipline. That is infrastructure engineering wearing an n8n costume.
- B2C payout reconciliation at scale. The pull API does not cover payouts, so your control is only as good as your own callback handling. High-volume disbursement businesses need a purpose-built ledger service.
- Hard audit or regulatory requirements. Banks and insurers usually need signed events, immutable audit trails, and segregation of duties that a workflow tool does not provide out of the box. That is a custom build, and for regulated finance work it sits closer to our Claude for financial services practice than to workflow automation.
- Nobody will own the server. An unpatched n8n instance holding payment data is a liability. If no one will maintain it, paying for a managed run is cheaper than the incident.
For teams weighing agents versus workflows more broadly, our guide to autonomous AI agents for business covers where each fits. Reconciliation is firmly on the workflow side: deterministic, repetitive, and auditable.
Frequently asked questions
Is the Daraja API free to use? Creating a developer account and apps on the Daraja portal is free, and Safaricom does not charge separately for API access. The standard M-Pesa transaction charges on your paybill or till still apply exactly as before.
Can I run this on n8n Cloud instead of self-hosting? Not with the community node. n8n Cloud only allows verified community nodes, and the M-Pesa Daraja node is unverified. You could rebuild the Daraja calls with the HTTP Request node on Cloud, but the execution volume of a callback-heavy integration makes Cloud's per-execution pricing poor value here. Self-hosting is the practical answer.
How far back can automated reconciliation go? Two days. The Pull Transactions API retains roughly 48 hours and covers C2B receipts only. Anything older requires an export from the M-Pesa organisation portal and a manual match, which is why the schedule runs hourly or daily, never monthly.
Does this work for till numbers as well as paybills? Yes. Both shortcode types receive C2B callbacks and both are covered by the pull. Production credentials differ slightly, and you get them from Safaricom during go-live either way.
What happens if my server is down when a callback arrives? You lose that callback, and that is exactly why the pull workflow exists. The hourly pull re-fetches Safaricom's record independently, so a missed callback becomes a missing item in the next comparison instead of a permanent hole.
Can this reconcile my B2C payouts to suppliers or salaries? Not through the pull API, since it covers C2B only. The workable pattern is to record every B2C result callback against the payout you initiated and query Transaction Status for anything without a result. It is a weaker control than pull-based reconciliation, and at high payout volumes it argues for a custom ledger service.
Where to go from here
The three workflows above are enough to stop M-Pesa payments falling between your callbacks and your books, and the whole thing runs for the price of a small VPS plus a few maintenance hours a month. If you would rather have it built, tested against the Daraja sandbox, and run for you, that is what our business automation service does with n8n every day.
Related reading
About AI Agents Plus Editorial
AI automation expert and thought leader in business transformation through artificial intelligence.



