M-Pesa (Kenya)
M-Pesa is the world’s most successful mobile money service. Operated by Safaricom in Kenya, it processes roughly 50% of Kenya’s GDP in transaction value annually. For many Kenyans, M-Pesa is money.
Overview
Section titled “Overview”| Property | Value |
|---|---|
| Operator | Safaricom PLC |
| Type | Mobile Money (STK Push) |
| Country | Kenya |
| Currency | KES |
| Flow | USSD Push (STK Push) |
| Programmatic Refunds | Yes (Reversal API) |
| Sandbox Available | Yes (solid) |
| Settlement | T+1 business days |
How M-Pesa Payments Work
Section titled “How M-Pesa Payments Work”Zirzir uses STK Push (Lipa Na M-Pesa Online) under the hood:
- Your app calls
zirzir.charge()with the customer’s phone number - Safaricom sends an STK Push prompt to the customer’s phone
- Customer enters their M-Pesa PIN to approve
- Safaricom sends a callback to your
callbackUrl - You verify and fulfill the order
checkoutUrl is null for M-Pesa — the customer interacts entirely via their phone.
Credentials
Section titled “Credentials”Register at developer.safaricom.co.ke to get sandbox credentials immediately. Production credentials require Go Live approval (1-2 weeks).
| Credential | Description |
|---|---|
consumerKey | Your app’s consumer key |
consumerSecret | Your app’s consumer secret |
businessShortCode | Your PayBill or Till number |
passKey | Lipa Na M-Pesa passkey (provided after approval) |
Configuration
Section titled “Configuration”const zirzir = new Zirzir({ provider: 'mpesa', credentials: { consumerKey: process.env.MPESA_CONSUMER_KEY!, consumerSecret: process.env.MPESA_CONSUMER_SECRET!, businessShortCode: process.env.MPESA_SHORT_CODE!, passKey: process.env.MPESA_PASS_KEY!, environment: 'production' // or 'sandbox' }})Charging a Customer
Section titled “Charging a Customer”phone is required and must be in 2547XXXXXXXX format (country code, no +).
const tx = await zirzir.charge({ amount: 1000, currency: 'KES', phone: '254712345678', txRef: 'order_001', callbackUrl: 'https://yourapp.com/webhooks/zirzir',})
// tx.checkoutUrl is null — customer prompted via STK Pushconsole.log(tx.status) // 'pending'Known Issues & Quirks
Section titled “Known Issues & Quirks”OAuth Token Expiry
Section titled “OAuth Token Expiry”M-Pesa OAuth tokens expire every 3600 seconds. The Zirzir SDK handles refresh automatically.
Callbacks Arrive Out-of-Order
Section titled “Callbacks Arrive Out-of-Order”STK Push callbacks can arrive before the initial API response completes. Always verify via API, not just callbacks.
Phone Number Format
Section titled “Phone Number Format”Must be 2547XXXXXXXX. No + prefix, no leading 0. Zirzir normalizes common formats but we recommend passing the canonical format.
Minimum Transaction
Section titled “Minimum Transaction”Minimum STK Push transaction is 1 KES.
5-Second Callback Timeout
Section titled “5-Second Callback Timeout”Your callback endpoint must respond within 5 seconds or Safaricom marks it as failed.
Production vs Sandbox URLs
Section titled “Production vs Sandbox URLs”Production and sandbox use different base URLs and different credentials — easy to mix up.
Fee Structure
Section titled “Fee Structure”Merchant Fees (Lipa Na M-Pesa — Buy Goods)
Section titled “Merchant Fees (Lipa Na M-Pesa — Buy Goods)”| Transaction Amount (KES) | Fee |
|---|---|
| 1 - 100 | 0 KES |
| 101 - 500 | 7 KES |
| 501 - 1,000 | 13 KES |
| 1,001 - 1,500 | 23 KES |
| 1,501 - 2,500 | 33 KES |
| 2,501 - 3,500 | 53 KES |
| 3,501 - 5,000 | 57 KES |
| 5,001 - 7,500 | 78 KES |
| 7,501 - 10,000 | 90 KES |
| 10,001 - 15,000 | 100 KES |
| 15,001 - 20,000 | 105 KES |
PayBill fees vary by industry and are negotiated with Safaricom.
Refunds
Section titled “Refunds”M-Pesa supports reversals via the Reversal API:
const refund = await zirzir.refund('order_001')Reversals are processed quickly but are subject to Safaricom approval.
Settlement
Section titled “Settlement”- Cycle: T+1 business days (reliable)
- Currency: KES
- Method: Bank transfer to registered business account
- Settlement days: Monday - Friday
- Reporting: Via M-Pesa portal or API
M-Pesa’s settlement is more reliable and consistent than most African payment gateways.
Common Issues
Section titled “Common Issues””STK Push callback not received”
Section titled “”STK Push callback not received””callbackUrlmust be publicly accessible (no localhost)- Firewall may block Safaricom’s callback IP ranges
- HTTPS required in production
- URL must respond within 5 seconds
”Invalid Access Token”
Section titled “”Invalid Access Token””Tokens expire every 3600 seconds. Ensure you’re refreshing proactively.
”Request cancelled by user”
Section titled “”Request cancelled by user””Customer dismissed the USSD prompt. The transaction is permanently failed — you cannot re-push the same request.
Useful Links
Section titled “Useful Links”- Daraja developer portal: developer.safaricom.co.ke
- API documentation: developer.safaricom.co.ke/docs
- Developer forum: developer.safaricom.co.ke/forum