STK Push (Lipa na M-PESA Online) triggers a payment prompt on a customer's phone. They enter their M-PESA PIN and the money moves; Safaricom POSTs the result to your callback URL. It is the default checkout flow for Kenyan e-commerce and the most reliable channel Daraja offers: no manual paybill entry, no wrong-account errors, no forgotten reference numbers.
This guide covers the request shape, the callback shape, the error codes you will hit in production, and the retry patterns that keep checkout success rates above 92 percent. For the full endpoint catalogue see the M-PESA Daraja API complete guide.
The STK Push flow at a glance
- Your server exchanges Consumer Key + Secret for a Bearer token (cache for 1 hour).
- Your server POSTs the STK Push request to Daraja.
- Daraja returns a synchronous acknowledgement with a CheckoutRequestID.
- Safaricom sends an M-PESA prompt to the customer's phone (approximately 90-second timeout).
- Customer enters PIN (or cancels or times out).
- Safaricom POSTs the final result to your CallBackURL.
- Your server reads ResultCode, updates the order, returns HTTP 200.
The STK Push request body
Every STK Push request is a POST to https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest (production: api.safaricom.co.ke) with a JSON body of these fields:
{
"BusinessShortCode": 174379,
"Password": "MTc0Mzc5YmZiMjc5ZjlhYTliZGJjZjE1OGU5N2RkNzFhNDY3Y2QyZTBjODkzMDU5YjEwZjc4ZTZiNzJhZGExZWQyYzkxOTIwMjYwOTAxMTIzMDAw",
"Timestamp": "20260901123000",
"TransactionType": "CustomerPayBillOnline",
"Amount": 100,
"PartyA": 254712345678,
"PartyB": 174379,
"PhoneNumber": 254712345678,
"CallBackURL": "https://api.yourdomain.co.ke/mpesa/callback",
"AccountReference": "INV-00042",
"TransactionDesc": "Payment for invoice 42"
}| Field | Purpose |
|---|---|
| BusinessShortCode | Your paybill (5-6 digits) or till (5-7 digits) |
| Password | base64(BusinessShortCode + Passkey + Timestamp) |
| Timestamp | yyyyMMddHHmmss, same value used in the Password concatenation |
| TransactionType | CustomerPayBillOnline (paybill) or CustomerBuyGoodsOnline (till) |
| Amount | Integer KES, no decimals |
| PartyA | Customer phone in 254 format |
| PartyB | Your paybill or till (usually equals BusinessShortCode) |
| PhoneNumber | Customer phone in 254 format (same as PartyA) |
| CallBackURL | Your HTTPS endpoint; Safaricom POSTs the result here |
| AccountReference | Free text, max 12 chars, shown on the customer's STK prompt and receipt |
| TransactionDesc | Free text, max 13 chars, logged only |
The STK Push callback body
Safaricom POSTs the final result to your CallBackURL. Successful callback:
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": 0,
"ResultDesc": "The service request is processed successfully.",
"CallbackMetadata": {
"Item": [
{ "Name": "Amount", "Value": 100 },
{ "Name": "MpesaReceiptNumber", "Value": "TA57ZBCAM3" },
{ "Name": "TransactionDate", "Value": 20260901123045 },
{ "Name": "PhoneNumber", "Value": 254712345678 }
]
}
}
}
}Failure callback (customer cancelled):
{
"Body": {
"stkCallback": {
"MerchantRequestID": "29115-34620561-1",
"CheckoutRequestID": "ws_CO_191220191020363925",
"ResultCode": 1032,
"ResultDesc": "Request cancelled by user"
}
}
}Note that CallbackMetadata is only present on ResultCode 0. On failures, only MerchantRequestID, CheckoutRequestID, ResultCode and ResultDesc are guaranteed.
Common STK Push errors and fixes
| Code | Meaning | Fix |
|---|---|---|
| 0 | Success | Book the payment |
| 1 | Insufficient funds in customer wallet | Prompt customer to top up and retry |
| 1001 | Wrong PIN entered | Retry allowed; STK prompt reopens |
| 1019 | Transaction expired | Customer took too long; prompt to retry |
| 1032 | Request cancelled by user | Customer pressed cancel; surface a retry button |
| 1037 | DS timeout, user cannot be reached | Phone off or out of coverage; ask customer to check phone and retry |
| 2001 | Wrong initiator information | B2C error accidentally on STK; check request routing |
| 500.001.1001 | Unable to lock subscriber, transaction in process | Wait 30 seconds; customer has an open prompt |
| 500.001.1032 | Request cancelled by user | Same as 1032, cancellation at request layer |
| 2006 | Duplicate detected | You sent the same request twice within 60 seconds; drop and use the first CheckoutRequestID |
Retry patterns that work in production
- Idempotency-key every request. Send the same Idempotency-Key on retries so Safaricom does not queue two STK prompts.
- Poll STK Query at 5-second intervals. If the callback has not landed within 15 seconds, start polling
/mpesa/stkpushquery/v1/queryto fetch the current status. Stop when you get a definitive result or after 120 seconds. - Surface a "retry" button after 60 seconds. Do not auto-retry because you cannot tell whether the customer is still deciding.
- On 500.001.1001, wait 30 seconds before the second attempt. Safaricom will not open a second prompt until the first clears.
Related reading
- M-PESA for Business hub
- Daraja API complete guide
- C2B integration
- B2C integration
- M-PESA payment gateways compared
- Daraja API (original integration guide)
- M-PESA charges 2026
FAQ
What is STK Push?
STK Push, marketed by Safaricom as Lipa na M-PESA Online (LNMO), is the Daraja endpoint that triggers a payment prompt on a customer's phone. The customer enters their M-PESA PIN and the money moves. It is the standard checkout flow for Kenyan e-commerce.
What is the STK Push password and how do I generate it?
The password is a base64 encoding of the concatenation of your BusinessShortCode + Passkey + Timestamp. Timestamp format is yyyyMMddHHmmss. The passkey is issued by Safaricom (sandbox uses a public test passkey; production uses one issued to your app). Regenerate the password on every request because the timestamp changes.
What phone number format does STK Push expect?
2547XXXXXXXX or 2541XXXXXXXX (Airtel is on Daraja for STK too since 2024). No plus sign, no leading zero, no spaces. 0712345678 must be sent as 254712345678. Normalise on your server before every request.
How long does the customer have to enter the PIN?
Safaricom's STK prompt times out after approximately 90 seconds. If the customer does not respond, the callback returns ResultCode 1037 (DS timeout) or 1032 (cancelled). Design your checkout page to poll STK Query at 5-second intervals and surface a "did not receive prompt?" retry after 60 seconds.
Why do I get 500.001.1001 "Unable to lock subscriber"?
The customer already has an open STK Push prompt from a previous request (yours or another business). Safaricom will not open a second prompt. Wait 30 seconds and retry, or show the customer a "please complete or cancel the previous M-PESA prompt on your phone" message.
What is the difference between the synchronous response and the callback?
The synchronous response confirms Safaricom received and queued your request; it returns a CheckoutRequestID. The callback (POSTed to your CallBackURL) is the final result: whether the customer actually paid, cancelled or timed out. Never mark an order paid on the synchronous response. Wait for the callback.
Can I use STK Push with a paybill?
Yes. Set TransactionType to CustomerPayBillOnline, BusinessShortCode to your paybill, PartyB to your paybill, and AccountReference to the customer account (invoice number, meter number, membership ID). For a till, set TransactionType to CustomerBuyGoodsOnline and PartyB to your till number.
How do I handle STK Push callback duplicates?
Safaricom occasionally replays callbacks on network glitches. Make your handler idempotent: index by CheckoutRequestID or MpesaReceiptNumber. If you have already processed the ID, return 200 OK and do not book the payment twice.
Verified 1 September 2026. STK Push endpoint paths and body schema verified against the current Safaricom Daraja portal at developer.safaricom.co.ke. Volume context from Safaricom PLC Annual Report FY2026.
Related reading
Curated external sources we cite. Open in a new tab.