How Kenyan Businesses Can Accept M-Pesa Payments Online

Paybill, Till, STK Push, Daraja, aggregators. Four of those words get used interchangeably and they should not be. Here is what each one is, which route fits your business, and what has to exist before a single shilling can reach your account.

The short version

  • You need a registered business and either a Paybill or a Till before any online integration is possible.
  • STK Push is the prompt that appears on the customer’s phone. It is a feature of the API, not a separate product.
  • Direct Daraja integration costs less per transaction; an aggregator costs less in developer time and gives you cards too.
  • The hard part is never the payment. It is the callback, the reconciliation, and the transaction nobody can find.

M-Pesa is the default way money moves in Kenya, so “can people pay on the site?” is usually the second question a client asks. The answer is yes, in three quite different ways, and the one you choose determines your fees, your build time, and how much of your Saturday you spend matching payments to orders.

A note on specifics. Safaricom changes its portal, its onboarding steps, and its tariffs periodically, and aggregators change their pricing more often than that. Treat every process detail and every rate here as a description of the shape of things, and confirm the current version with Safaricom or the provider before you commit.

The three routes, briefly

  1. Manual. You display your Paybill number and an account reference, the customer pays from their phone, and someone on your team matches the confirmation SMS to the order. No integration, no development cost, and it works from day one. It also does not scale past roughly a few payments a day without someone losing their temper.
  2. Direct integration with Daraja, Safaricom’s developer API. Your site triggers the payment prompt itself and is told the outcome automatically. Lowest per-transaction cost, most control, most engineering.
  3. A payment aggregator such as Paystack, Flutterwave, IntaSend, Pesapal, DPO or Kopo Kopo. They hold the Safaricom relationship and give you one clean API that also handles cards and, usually, other mobile-money networks. Fastest to launch, highest per-transaction cost.

Paybill vs Till: pick the right one

This choice is made with Safaricom before any code exists, and switching later is administrative pain, so get it right.

Differences between an M-Pesa Paybill and a Till number
Paybill Till (Buy Goods)
Customer enters Business number plus an account reference Till number only
Best for Invoices, rent, school fees, orders you must identify Over-the-counter retail, quick fixed-price sales
Reconciliation Straightforward: the reference identifies the order Harder: you get an amount and a phone number
Online checkout Preferred, because you control the reference Workable, but you lean on the API to correlate

For anything sold online, a Paybill is usually the better instrument, because you can set the account reference to your own order number and reconciliation becomes a lookup rather than a guess.

What STK Push actually is

STK Push (you will also see it called Lipa Na M-Pesa Online or M-Pesa Express) is the moment a payment prompt appears on the customer’s phone without them dialling anything. They see the amount and the business name, they enter their M-Pesa PIN, and it is done.

It matters because it removes the step where customers abandon: switching apps, remembering a Paybill number, typing an account reference correctly. The sequence is:

  1. The customer enters their phone number on your checkout and confirms the amount.
  2. Your server asks Safaricom to push a prompt to that number.
  3. Safaricom immediately returns an acknowledgement. This only means “the request was accepted”, not “you have been paid”.
  4. The customer enters their PIN, or does not.
  5. Safaricom sends the real outcome to a callback URL on your server, separately, moments later.
Step 3 is where most broken integrations live. Treating the acknowledgement as a successful payment is the single most common bug we find in inherited code.

What you need in place first

No amount of code substitutes for this list:

  • A registered business with the documentation Safaricom asks for: typically a registration certificate, KRA PIN, and identification for the signatories.
  • An M-Pesa business account with a Paybill or Till, obtained through Safaricom.
  • A Daraja developer account at developer.safaricom.co.ke, with an app that gives you a Consumer Key and Consumer Secret.
  • The Lipa Na M-Pesa passkey for your shortcode, issued by Safaricom, needed to sign STK Push requests.
  • Publicly reachable HTTPS callback URLs. Safaricom’s servers must be able to reach them from the internet. They cannot reach your laptop, which is why local development uses a tunnelling tool.
  • Somewhere to store transactions before, during, and after payment. This is why taking payments makes your site a web application rather than a website.

Expect the paperwork, not the programming, to set your timeline. Sandbox access is quick; production credentials go through Safaricom’s own review, and that step is outside anyone’s control.

Integrating Daraja directly

The shape of a direct integration, in the order you build it:

1. Authenticate

Exchange your Consumer Key and Secret for a short-lived access token. Tokens expire in about an hour, so cache the token and refresh it on expiry rather than requesting a new one per transaction.

2. Create the order before you charge

Write a pending record in your own database first, with your own reference. If anything downstream fails, this row is the only proof of what the customer was trying to do.

3. Trigger the STK Push

Send the amount, the customer’s number in the 2547XXXXXXXX format, your shortcode, a timestamped password derived from the passkey, your account reference, and the callback URL. Store the CheckoutRequestID that comes back against your pending order.

4. Receive the callback

Safaricom posts the result to your callback URL. A success carries the M-Pesa receipt number, the amount, the phone number, and the timestamp. Match it to your pending order by CheckoutRequestID, mark it paid, and only then fulfil.

5. Handle everything that is not a clean success

The customer cancels. The prompt times out. The PIN is wrong. There is not enough money. Each has a distinct result code, and each needs a sensible message on screen rather than a spinner that never stops.

6. Query when the callback never arrives

Networks fail. Implement the transaction status query so that a pending order older than a few minutes can be checked directly rather than left stranded. This is the difference between a payment system and a demo.

Treat the callback as hostile input. It arrives at a public URL, so validate it: check the payload shape, confirm the amount matches the order, restrict the endpoint to Safaricom’s published source addresses where you can, and make the handler idempotent. Callbacks can and do arrive twice. Fulfilling an order twice because of a retry is a real, avoidable loss.

When an aggregator is the better call

An aggregator sits between you and Safaricom. You integrate once and get M-Pesa, cards, and often Airtel Money behind a single API, along with a dashboard, receipts, refund tooling, and someone to email when a transaction goes missing.

They are the right choice when:

  • You need card payments too, particularly for customers outside Kenya.
  • You want to launch in weeks rather than after Safaricom’s review cycle.
  • Your volumes are modest enough that percentage fees cost less than engineering time.
  • You want refunds and payouts handled by a dashboard instead of by you.

Direct Daraja wins when volumes are high enough that the per-transaction saving is material, when you need unusual flows, or when money must land in your own M-Pesa account immediately rather than being settled to your bank on the provider’s schedule.

On fees: aggregator pricing in Kenya generally sits in the low single-digit percentages per transaction, sometimes with a floor or a cap, and settlement to your bank typically takes a day or several. Those numbers move, so compare current published rates rather than anything you read in an article.

What actually goes wrong

  • Trusting the acknowledgement. Covered above, and worth repeating: it means the request was accepted, nothing more.
  • Phone number formatting. Users type 0712..., +254712..., and 254 712.... Normalise before sending, or the push silently goes nowhere.
  • Amount rounding. M-Pesa deals in whole shillings. Decide deliberately how a cart total with cents is rounded, and show the customer the figure they will actually be charged.
  • Duplicate callbacks. Make fulfilment idempotent, keyed on the receipt number.
  • No audit trail. Log every request and callback with its identifiers. When a customer says they paid, the answer must be a lookup, not an investigation.
  • Credentials in the front-end. Consumer Secret and passkey belong on the server only. Anything in browser JavaScript is public.
  • Forgetting failure is normal. A meaningful share of prompts are never completed. Design the checkout so an abandoned payment is recoverable rather than a dead end.

Choosing your route

If you are selling a handful of items a week, start manual with a clear Paybill and reference on the page. It costs nothing and tells you whether the demand is real before you spend on plumbing.

If you are building a store or a booking system and want to launch this quarter, take an aggregator. The percentage buys you cards, a dashboard, and someone else’s support desk.

If payments are core to the business, volumes are meaningful, or you need control over the flow, integrate Daraja directly and budget properly for the error handling and reconciliation, which is where most of the real work sits.

M-Pesa STK Push and card gateways are standard options on our commerce and application builds, and you can see how that fits into the tiers on the services page. If you are still weighing what kind of build this is, start with website versus web app.

Common questions

What do I need before I can accept M-Pesa payments on my website?

A registered business with the documentation Safaricom asks for, typically a registration certificate, KRA PIN and identification for the signatories. Then an M-Pesa business account with a Paybill or Till, a Daraja developer account with a Consumer Key and Secret, the Lipa Na M-Pesa passkey for your shortcode, publicly reachable HTTPS callback URLs, and somewhere to store transactions before, during and after payment. Expect the paperwork rather than the programming to set your timeline.

Should I use a Paybill or a Till number for online payments?

For anything sold online a Paybill is usually the better instrument, because the customer enters an account reference you control. Set that reference to your own order number and reconciliation becomes a lookup rather than a guess. A Till gives you an amount and a phone number, which is harder to match to an order. The choice is made with Safaricom before any code exists, and switching later is administrative pain.

What is M-Pesa STK Push?

STK Push, which you will also see called Lipa Na M-Pesa Online or M-Pesa Express, is the payment prompt that appears on the customer’s phone without them dialling anything. They see the amount and the business name, they enter their M-Pesa PIN, and it is done. It matters because it removes the steps where customers abandon: switching apps, remembering a Paybill number, and typing an account reference correctly.

Should I integrate Daraja directly or use a payment aggregator?

Take an aggregator such as Paystack, Flutterwave, IntaSend, Pesapal, DPO or Kopo Kopo when you also need card payments, when you want to launch in weeks rather than after Safaricom’s review cycle, or when your volumes are modest enough that percentage fees cost less than engineering time. Integrate Daraja directly when volumes make the per-transaction saving material, when you need unusual flows, or when money must land in your own M-Pesa account immediately rather than being settled on a provider’s schedule.

Why does my M-Pesa integration mark orders paid when no money arrived?

Almost always because the code treats Safaricom’s immediate acknowledgement as a successful payment. That response only means the request was accepted. The real outcome arrives separately at your callback URL moments later. Mark the order paid on the callback, match it to your pending order by CheckoutRequestID, make the handler idempotent because callbacks can arrive twice, and implement the transaction status query so a payment is never left stranded when the callback never arrives.

Taking payments on your next build? Tell us what you sell and we will recommend a route, including the case where the honest answer is a Paybill number printed on the page.

Joseph Ng'era

Joseph Ng'era

Co-Founder · Backend Developer

Joseph co-founded ByteFrame in 2025 and builds the databases, APIs, and payment integrations behind every project. Meet the team.