Date: Aug 31, 2026
Subject: Securing M-Pesa Integrations: Credentials, Callbacks, and Validation
$ curl -X POST https://yourapp.co.ke/mpesa/callback \
-H "Content-Type: application/json" \
-d '{"TransactionID": "??", "Amount": "??", "PhoneNumber": "??"}'
> 200 OK — but who actually sent this request?
> And can you prove the amount received matches the amount invoiced?
# If you can't answer both questions in under five seconds, keep reading.
If your business in Nairobi, Mombasa, Kisumu or anywhere in between takes payments through M-Pesa via an app, a website, a SACCO management system, or a school fees portal, you're not just "using M-Pesa" — you're running a small piece of financial infrastructure. That distinction matters. A paybill number on a poster is simple and hard to attack. A live integration with Safaricom's Daraja API, handling STK push requests, callback URLs, and automated reconciliation, is software, and software has attack surface. Get it wrong and you risk double-crediting accounts, losing track of payments customers insist they made, or exposing customer phone numbers and transaction data in ways that could interest the Office of the Data Protection Commissioner. This post walks through the three areas that most commonly go wrong: how you store and use API credentials, how you secure the callback endpoints that receive payment notifications, and how you validate what actually happened before you trust it.
Before anything else: this article deliberately avoids quoting specific Daraja API field names, endpoint paths, timeout values, or fee structures, because those details change and are documented directly by Safaricom's developer portal. If you're building or reviewing an integration, treat the official Daraja documentation as your source of truth for exact specifications, and treat this article as the surrounding judgment you need to apply to it.
Every M-Pesa integration involves a set of secrets: a consumer key and secret used to authenticate your application to Safaricom's systems, and typically a separate passkey or credential used specifically to initiate payment requests like STK push. Treat all of these with the same seriousness you'd apply to a bank password, because functionally that's what they are — anyone who has them can, within the permissions of your integration, initiate transactions or receive payment data on your business's behalf.
The most common failure here is embarrassingly simple: credentials get hardcoded into source code, and that source code ends up in a version control system. A developer commits a config file "temporarily," pushes it to a shared repository, and forgets. If that repository is ever made public, forked, or accessed by someone who shouldn't have it — including a former employee or a contractor whose access was never revoked — your credentials are compromised. This happens more often than businesses like to admit, and it's rarely malicious; it's just rushed work under deadline pressure, which describes most software projects run by small teams in Kenya with limited budgets and tight timelines.
The fix is not exotic. Keep credentials out of code entirely. Use environment variables or a proper secrets manager, even a modest one, rather than files that get committed. Separate your sandbox/test credentials from production credentials clearly, so a developer testing against Safaricom's sandbox environment can never accidentally push a live transaction. Limit who on your team actually has access to production credentials — a five-person agency building a client's payment portal does not need all five developers holding the live passkey. Rotate credentials when someone leaves the team or when a contractor's engagement ends, and don't assume that's automatic; if nobody owns that task, it won't happen. If you work with an outsourced developer or agency, agree explicitly in writing who retains and controls the production credentials once the project ships, because disputes over "who has access to our M-Pesa integration" after a falling-out with a developer are more common than you'd expect, and they're painful to resolve after the fact.
The part of an M-Pesa integration most likely to be quietly insecure is the callback — the URL on your server that Safaricom's systems call to tell you a payment happened. This is a webhook: an incoming HTTP request from the outside internet, hitting an endpoint on your server, carrying a claim about a financial transaction. The core security problem is that an HTTP endpoint, by default, will accept a request from anyone who knows the URL, not just from Safaricom. If your callback URL is guessable or has ever leaked — in logs, in a public repository, in a screenshot shared for debugging — someone could send your server a fake "payment confirmed" request. If your application trusts that request blindly and credits an account or unlocks a service based on it, you have a serious problem, and it's one that won't show up until someone exploits it.
There are several layers of defence worth building, and you should apply more than one rather than relying on any single measure:
First, your callback endpoint should only be reachable over HTTPS, never plain HTTP. This is non-negotiable and, fortunately, cheap to arrange today through most hosting providers and reverse proxies.
Second, treat the callback URL itself as sensitive. Don't log it in places accessible to people outside your core team, don't paste it into support tickets with third parties, and don't reuse the same predictable pattern across every client if you're an agency running many integrations — a leaked pattern for one client can expose the shape of URLs for others.
Third, wherever the integration pattern allows it, verify the source of the request rather than assuming it. Depending on how your specific integration is structured, this might involve checking the request against known characteristics of Safaricom's calling systems, or — more robustly — never trusting the callback payload as the final word at all. That leads to the third pillar: validation.
The single most important design principle in M-Pesa integrations is this: a callback notification should trigger a check, not be treated as ground truth on its own. When your server receives a callback claiming a transaction succeeded with a certain amount, for a certain account or invoice reference, your system should independently reconcile that claim against what you expected — the checkout or transaction reference you generated when you initiated the request, the exact amount you asked the customer to pay, and the account or order it relates to. If any of those don't line up, don't silently accept the transaction; flag it for a human to look at. This matters for a very practical reason beyond fraud: connectivity in Kenya, even in Nairobi's business districts, is not always perfect, and power interruptions do happen. Callbacks can arrive late, arrive twice, or in rare cases not arrive at all even though the customer's money moved. A well-built integration handles all three situations without either double-crediting a customer or leaving them out of pocket and angry. That means designing for idempotency — if the same transaction reference arrives twice, your system should recognise it's already processed and not credit the account a second time — and building a manual reconciliation path, however simple, for cases where the automated callback never turns up. Many SMEs and SACCOs get this wrong not through negligence but because the happy path (callback arrives once, on time, matches perfectly) works fine in testing and nobody budgets time to handle the unhappy path until a customer complaint forces the issue.
It's also worth building a habit of independently querying transaction status rather than depending solely on inbound callbacks, where your integration pattern supports it. If a callback hasn't arrived within a reasonable window after you initiated a payment request, a follow-up check against Safaricom's own record of that transaction — rather than guessing — closes the gap and gives you an audit trail you can defend if a customer disputes a payment.
M-Pesa transaction data includes personal information — phone numbers, transaction identifiers, sometimes names — and how you store, log, and transmit that data falls within the scope of data protection obligations that the Office of the Data Protection Commissioner oversees. Beyond legal exposure, this is a trust issue: customers who pay a school, a clinic, or a SACCO via M-Pesa are extending a basic assumption that their payment details won't end up in a plaintext log file readable by anyone with server access, or in a spreadsheet emailed around for "debugging." Keep transaction logs access-controlled, avoid storing more personal data than you actually need for reconciliation and for any tax record-keeping relevant to KRA and eTIMS obligations, and if you're not confident about current data protection requirements for your sector, that's a conversation worth having with a lawyer or compliance advisor rather than a guess.
None of this requires a large budget or a large team. It requires deciding, deliberately, that a payment integration is not a "set it up once and forget it" feature but a piece of infrastructure that needs an owner, a credential-handling policy, and a habit of reconciling what your systems claim happened against what actually happened. For a business where M-Pesa is effectively the front door for revenue, that discipline is not overhead — it's the cost of taking your own money seriously.
We build AI agents and automation for Kenyan businesses — and the infrastructure underneath them. Run the automation scan and find out what's worth building first.