What Mpesa Integration Actually Involves: STK Push, Till vs Paybill, and Failed Callbacks
Every POS vendor in Kenya says they "support Mpesa." That phrase covers everything from a proper API integration to a staff member squinting at the Safaricom confirmation SMS and typing the amount into the till by hand. Those are not the same thing, and the difference shows up on the days you are busiest.
We integrate Mpesa into POS and ERP systems through Safaricom's Daraja API. Here is what that actually involves, so you know what to ask for and what can go wrong.
First decide: Till, Paybill, or Buy Goods
Before any code runs, you need the right Mpesa product registered under your business. This trips people up because the names overlap.
- Buy Goods (Till number) suits a shop or restaurant taking payments at a counter. Money settles to your business account and the customer sees your trading name. Lower transaction friction for walk-in sales.
- Paybill suits businesses where customers pay against an account or invoice number, like a school, a landlord, or a service with monthly billing. The account reference field is the point of it.
- Personal Till is not for this. If your "integration" is running through a personal number, you have no clean API access and no proper reconciliation. Fix that first.
Pick based on how your customers already think about paying you. Changing later means new shortcodes and reprinted signage, so it is worth getting right at the start.
STK Push is the experience you want
The old way: the cashier reads out the Till number, the customer types it into their phone, enters the amount, gets the amount wrong, and the queue backs up. STK Push (Safaricom calls it Lipa na Mpesa Online) removes all of that.
Your POS sends the phone number and amount to Daraja. Safaricom pushes a payment prompt straight to the customer's handset. They enter their PIN, and the sale completes. No typing a Till number, no wrong amounts, no reading confirmation messages aloud. For a busy counter this is the whole reason to integrate properly.
Under the hood, the POS calls the STK Push endpoint with your shortcode, a timestamp, and a password built from your passkey. Safaricom returns a CheckoutRequestIDalmost immediately. That response only means "the prompt was sent." It does not mean the customer paid. That distinction is where most broken integrations fall apart.
The callback problem nobody warns you about
After the customer enters their PIN, Safaricom sends the real result to a callback URL you registered: paid, cancelled, insufficient funds, or timed out. This is an asynchronous call from Safaricom's servers to yours. And here is the catch: your POS usually sits on a shop's local network with no public address. Safaricom has nowhere to send the result.
When that happens, the sale sits on "pending" forever even though the customer's money left their account. The cashier either waits, re-charges the customer (now you owe a refund), or waves them through unpaid. All three are bad.
The fix is architectural, not a setting you toggle:
- Point the callback at a public HTTPS endpoint you control, a small cloud service, not the till on the shop LAN.
- That endpoint records the result and relays it to the POS, so the terminal never needs a public address.
- Add a fallback: if no callback arrives within a few seconds, the POS calls the STK Push Query endpoint to ask Safaricom directly what happened. Never leave the outcome to a callback alone.
We have picked up more than one "Mpesa is broken" job that turned out to be exactly this: a callback URL pointing at a private IP, so results never landed. The integration worked in the demo and failed in the shop.
Reconciliation is the part that saves you money
Getting the payment through is half the job. Matching it to the sale is the other half. A good integration writes the Mpesa receipt number against the specific sale, so at close of day the POS total and the Mpesa statement agree to the shilling.
Without that link, someone spends an hour every evening comparing the POS report to the Mpesa statement by eye, and shrinkage hides in the gaps. If a vendor tells you reconciliation is "manual," understand that they are handing you a daily unpaid job.
Going live takes longer than you expect
Everyone builds against Daraja's sandbox first, which is fine. Moving to production is where timelines slip. You need the live shortcode, the production passkey, and Safaricom's go-live approval, and that approval is not instant. Plan for days, not hours, and do it before you promise a client a launch date.
Keep your consumer key, consumer secret, and passkey out of the POS app itself. They belong on the server side. Credentials baked into an Android app on a shop tablet are credentials waiting to leak.
What to ask a vendor before you sign
- Does the customer get an STK prompt, or does my cashier type the Till number?
- Where does the Mpesa callback go, and what happens to a sale if the callback never arrives?
- Does each Mpesa payment attach to its sale automatically for reconciliation?
- Are my API credentials stored on a server or inside the app on the till?
If the answers are vague, the integration is probably the SMS-and-retype kind dressed up as an API. You will feel the difference the first time you have a queue.
Want Mpesa done properly on your POS, callbacks and all?
Talk to us