Introduction
M-Pesa isn't just a payment method in Kenya; it's the financial backbone of the entire economy. With over 30 million active users and processing more than KSh 35 trillion annually, M-Pesa dominates the Kenyan payments landscape. If your e-commerce website doesn't accept M-Pesa, you're turning away the majority of your potential customers.
This guide walks you through everything you need to know about integrating M-Pesa into your website, from getting your Safaricom Daraja API credentials to handling callbacks and building a seamless checkout experience that converts.
Understanding the Safaricom Daraja API
Safaricom's Daraja API is the gateway to M-Pesa integration. It provides several payment APIs, but the two most relevant for website integration are STK Push (Lipa na M-Pesa Online) and C2B (Customer to Business).
STK Push is the preferred method for e-commerce. It sends a payment prompt directly to the customer's phone, they enter their M-Pesa PIN, and the payment is processed automatically. The customer never leaves your website, which dramatically reduces cart abandonment.
C2B is a fallback option where customers manually initiate the payment from their M-Pesa menu using your Paybill or Till number. This is useful for customers on older phones that don't support STK Push or when the STK Push times out.
API Access Requirements
Implementing STK Push
The STK Push flow works in four steps. First, your server authenticates with Daraja to get an access token. Second, it sends the STK Push request with the customer's phone number and amount. Third, the customer receives a prompt on their phone and enters their PIN. Fourth, Safaricom sends a callback to your server with the payment result.
Let's walk through each step with code examples using Laravel, the most popular PHP framework in Kenya.
// Step 1: Get OAuth access token
$response = Http::withBasicAuth(
config('mpesa.consumer_key'),
config('mpesa.consumer_secret')
)->get('https://api.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials');
$accessToken = $response->json('access_token');
// Step 2: Initiate STK Push
$timestamp = now()->format('YmdHis');
$password = base64_encode(
config('mpesa.shortcode') . config('mpesa.passkey') . $timestamp
);
$stkResponse = Http::withToken($accessToken)
->post('https://api.safaricom.co.ke/mpesa/stkpush/v1/processrequest', [
'BusinessShortCode' => config('mpesa.shortcode'),
'Password' => $password,
'Timestamp' => $timestamp,
'TransactionType' => 'CustomerPayBillOnline',
'Amount' => $order->total,
'PartyA' => $phoneNumber,
'PartyB' => config('mpesa.shortcode'),
'PhoneNumber' => $phoneNumber,
'CallBackURL' => route('mpesa.callback'),
'AccountReference' => 'Order-' . $order->id,
'TransactionDesc' => 'Payment for Order #' . $order->id,
]);
Security Warning
Handling the M-Pesa Callback
After the customer completes (or cancels) the payment, Safaricom sends a callback to the URL you specified. This callback contains the payment status, transaction ID, and other details. Your callback handler must be idempotent (safe to receive multiple times) and respond quickly.
// routes/api.php
Route::post('/mpesa/callback', [MpesaController::class, 'callback']);
// MpesaController.php
public function callback(Request $request): JsonResponse
{
$result = $request->input('Body.stkCallback');
$resultCode = $result['ResultCode'];
$checkoutId = $result['CheckoutRequestID'];
$payment = Payment::where('checkout_request_id', $checkoutId)->first();
if (!$payment) {
return response()->json(['status' => 'not found']);
}
if ($resultCode === 0) {
// Payment successful
$payment->update([
'status' => 'completed',
'mpesa_receipt' => $result['CallbackMetadata']['Item'][1]['Value'],
]);
$payment->order->markAsPaid();
} else {
// Payment failed or cancelled
$payment->update(['status' => 'failed']);
}
return response()->json(['ResultCode' => 0, 'ResultDesc' => 'Accepted']);
}
Building a Great M-Pesa Checkout UX
The technical integration is only half the battle. The checkout experience determines whether customers complete their purchase or abandon their cart. A great M-Pesa checkout UX should feel instant, transparent, and reassuring.
Key UX principles for M-Pesa checkout include a clean phone number input that accepts multiple formats (0722..., 254722..., +254722...) and normalizes them automatically. Show a real-time status indicator after initiating the STK Push: "Sending payment request..." then "Please check your phone and enter your M-Pesa PIN" then "Processing payment..." and finally "Payment confirmed!" with the M-Pesa receipt number.
Always provide a fallback. If the STK Push fails or times out after 60 seconds, offer manual payment instructions with your Paybill number and the account reference pre-filled. Some customers prefer this method, especially those on feature phones.
Building an E-Commerce Site?
Get our free M-Pesa integration checklist and weekly development tips.
Testing Your M-Pesa Integration
Safaricom provides a sandbox environment for testing your integration before going live. The sandbox simulates the entire payment flow, including STK Push prompts and callbacks, without processing real money.
Create a test plan that covers successful payments, cancelled payments (user declines STK Push), insufficient balance scenarios, timeout scenarios (user doesn't respond within 60 seconds), and duplicate payment attempts. Test on multiple devices and phone types to ensure the experience is consistent.
Going Live Checklist
Conclusion
M-Pesa integration is essential for any e-commerce website targeting Kenyan customers. While the technical implementation requires careful attention to security and error handling, the payoff is enormous: access to Kenya's most popular payment method and significantly higher conversion rates.
Start with the STK Push integration for the smoothest customer experience, always include a manual payment fallback, and invest in thorough testing before going live. Your M-Pesa checkout experience will be one of the biggest differentiators for your e-commerce business.