M-Pesa Integration: What to Know Before You Start
JonxTechnologies Team · August 11, 2026

Almost every business we talk to wants to take money through M-Pesa, and almost every one of them underestimates the same part of the job. The payment request itself is a couple of days of work. Everything around it — deciding which product you need, handling the times the network doesn't answer, and proving at month end that your records match Safaricom's — is where the real effort goes.
Here's what we wish more businesses knew before the first meeting.
First, decide which M-Pesa product you actually need
People say "M-Pesa integration" as if it's one thing. It isn't, and picking the wrong shape early is expensive to undo.
- Buy Goods (till number) suits over-the-counter sales where you don't need to know which customer paid. It's simple and fast to settle, but the payment carries very little information you can match to an order.
- Paybill gives you an account number field. That field is the difference between a payment you can automatically match to an invoice and one a human has to chase. If customers are paying for something specific — an order, a booking, a school fee, an invoice — you almost certainly want Paybill.
- STK Push (Lipa Na M-Pesa Online) isn't a separate account type. It's a way of initiating a payment from your app so the customer gets a PIN prompt on their phone instead of typing a number from memory. It sits on top of a paybill or till.
The practical rule: if you will ever need to answer "which of my customers paid this?" automatically, start with Paybill and a well-designed account number.
Design the account number before you write any code
The account number is the only field carrying your business context into Safaricom's system and back out. Make it something your system can parse without ambiguity — an order reference, a member number, an invoice ID. Avoid asking customers to type anything long, case-sensitive, or easy to mistype. Every character you add is a support call waiting to happen.
How STK Push actually behaves
The happy path is genuinely lovely: your system calls Safaricom, the customer's phone buzzes, they enter their PIN, and money moves. Build only for that path and you will be firefighting within a week.
What actually happens in production:
- The prompt expires if the customer doesn't respond in about a minute. Phone in a bag, on silent, or out of signal — all common.
- The customer cancels, or fat-fingers the PIN, or has insufficient balance.
- The customer pays successfully, but the confirmation never reaches your server.
That last one is the one that hurts, and it's worth being blunt about it.
You cannot treat the absence of a success callback as evidence that the payment failed. The customer's money may already be gone.
The callback problem
Safaricom notifies your system by calling a URL you registered — a publicly reachable HTTPS endpoint. Callbacks get lost. Your server has a bad minute, a deploy restarts a process at the wrong moment, a firewall rule changes, a certificate expires. The payment succeeded; you just never heard about it.
The fix is not to make callbacks more reliable — you don't control that. The fix is to stop depending on them as your only source of truth:
- Record the transaction as pending the moment you initiate it, with your own reference.
- Update it when the callback arrives.
- Independently poll transaction status for anything still pending after a short delay.
- Make your callback handler idempotent, because you will occasionally receive the same notification twice and must not credit the customer twice.
Idempotency is the one people skip. Test it deliberately: send the same callback payload three times and confirm your balances don't move three times.
Reconciliation is the actual project
Once money is flowing, the question stops being "did the API work?" and becomes "does what my system thinks happened match what Safaricom thinks happened?" For any business doing real volume, this is where the ongoing pain lives.
Build for it from day one:
- Store the M-Pesa receipt number against every transaction. It's your join key when something is disputed.
- Keep the raw payload you received, not just the fields you parsed. When you're debugging a mismatch six weeks later, the field you didn't think mattered is the one you need.
- Give finance a screen that lists unmatched payments. There will always be some — a customer typed the wrong account number, or paid twice. Someone needs to resolve those without calling a developer.
Going live takes longer than building
Development happens against a sandbox with test credentials. Production requires a separate application, real shortcode credentials, and review by Safaricom. That review is not instant, and it's outside your control.
Two things follow from that. First, start the paperwork in parallel with development, not after it — we've seen finished integrations sit idle for weeks waiting on credentials. Second, don't promise a launch date that assumes approval is a formality. Build the buffer in.
The Safaricom developer portal is where that process starts.
What to prepare before you talk to a developer
You'll get a faster, cheaper, more accurate quote if you arrive with these answers:
- Do you already have a paybill or till, and in whose name is it registered?
- What exactly is the customer paying for, and what reference identifies it?
- What should happen automatically the moment payment confirms — a receipt, a booking confirmation, stock movement, a status change?
- Who in your business resolves a payment that doesn't match anything, and what do they need to see to do it?
- Do you need to send money out as well as take it in? That's a different set of permissions and a different conversation.
The businesses that get this live smoothly are not the ones with the biggest budgets. They're the ones who knew what a successful payment was supposed to do in their business before anyone opened an editor.
If you're weighing up a payment integration and want a straight answer about scope, get in touch — we'd rather tell you it's simpler than you feared than sell you something you don't need.