M-Pesa, Kenya's leading mobile money service from Safaricom, enables seamless payments on websites. While HTML provides the frontend structure, full integration requires a backend (like PHP or Node.js) for API calls to Safaricom's Daraja platform, as pure HTML can't handle secure server-side authentication or callbacks.​​

Prerequisites

Successful M-Pesa integration starts with preparation. Developers need a Safaricom Daraja account, consumer key/secret, and HTTPS hosting, since M-Pesa mandates secure endpoints. Basic knowledge of HTML forms, JavaScript for frontend prompts, and a server-side language is essential. Sandbox testing via developer.safaricom.co.ke simulates live transactions without real money.

Step-by-Step Integration Process

Follow these numbered steps to integrate M-Pesa's Lipa Na M-Pesa (STK Push) into an HTML-based site. This focuses on Customer-to-Business (C2B) payments, common for e-commerce.

  1. Register on Daraja Portal: Visit developer.safaricom.co.ke, sign up, and create a sandbox app. Select "Lipa Na M-Pesa Online" as the product. Note your Consumer Key and Secret—these authenticate API requests. Go live later via M-Pesa for Business approval.

  2. Set Up Development Environment: Ensure your server supports HTTPS (use Let's Encrypt for free SSL). Install a backend like PHP with cURL for API calls. Create folders for your project: one for HTML frontend, another for backend scripts.​​

  3. Generate Access Token: Use your Consumer Key/Secret to fetch a token from Safaricom's OAuth endpoint. In PHP, base64-encode "consumer_key:consumer_secret" and POST to https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials (switch to production URL later). Store the token, valid for 1 hour.​​

  4. Create HTML Payment Form: Build a simple form in HTML with fields for phone number, amount, and a "Pay Now" button. Use JavaScript to validate inputs (e.g., Kenyan phone format 2547xxxxxxxx) before submitting to backend via AJAX or form POST.​

  5. Implement STK Push Backend: On form submission, send a POST request to M-Pesa's /mpesa/stkpush/v1/processrequest endpoint. Payload includes BusinessShortCode (your till number), Password (ShortCode+passkey+timestamp, base64-encoded), Timestamp, TransactionType="CustomerPayBillOnline", Amount, PartyA (phone), PartyB (ShortCode), PhoneNumber, CallBackURL, AccountReference, and TransactionDesc.​​

  6. Handle Callbacks: Set a Validation/Confirmation/Callback URL in Daraja portal. Your backend receives asynchronous JSON responses with transaction status (e.g., "Completed"). Verify ResultCode=0 for success, update database, and notify user.​​

  7. Test in Sandbox: Use Daraja simulator or test phone numbers. Initiate payment from your HTML form; M-Pesa sends a push on phone. Enter PIN to complete. Check logs for CheckoutRequestID to track.

  8. Go Live: Apply for production credentials via M-Pesa for Business. Replace sandbox URLs/keys, update ShortCode to live PayBill, and submit endpoints for approval. Test thoroughly before launch.​​

Sample Code Snippets

Here's practical code to illustrate. Always secure keys in environment variables, not hardcoded.

HTML Frontend (index.html)


 

xml

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>M-Pesa Payment</title> </head> <body> <form id="paymentForm" action="stkpush.php" method="POST"> <label>Phone Number:</label> <input type="tel" id="phone" name="phone" placeholder="2547xxxxxxxx" required><br> <label>Amount:</label> <input type="number" id="amount" name="amount" min="10" required><br> <button type="submit">Pay with M-Pesa</button> </form> <script> document.getElementById('paymentForm').addEventListener('submit', function(e) { const phone = document.getElementById('phone').value; if (!phone.startsWith('2547') || phone.length !== 12) { alert('Enter valid Kenyan phone number'); e.preventDefault(); } }); </script> </body> </html>

This form collects data and posts to backend.​​

PHP Backend (stkpush.php)


 

php

<?php // Fetch token (simplified) $consumerKey = 'your_key'; $consumerSecret = 'your_secret'; $credentials = base64_encode($consumerKey . ':' . $consumerSecret); $tokenUrl = 'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials'; $curl = curl_init($tokenUrl); curl_setopt($curl, CURLOPT_HTTPHEADER, ["Authorization: Basic $credentials"]); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); $response = curl_exec($curl); $token = json_decode($response)->access_token; curl_close($curl); // STK Push $phone = $_POST['phone']; $amount = $_POST['amount']; $shortcode = 174379; // Your ShortCode $passkey = 'your_passkey'; $timestamp = date('YmdHis'); $password = base64_encode($shortcode . $passkey . $timestamp); $callback = 'https://yourdomain.com/callback.php'; $stkUrl = 'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest'; $data = [ 'BusinessShortCode' => $shortcode, 'Password' => $password, 'Timestamp' => $timestamp, 'TransactionType' => 'CustomerPayBillOnline', 'Amount' => $amount, 'PartyA' => $phone, 'PartyB' => $shortcode, 'PhoneNumber' => $phone, 'CallBackURL' => $callback, 'AccountReference' => 'Test', 'TransactionDesc' => 'Payment' ]; $curl = curl_init(); curl_setopt($curl, CURLOPT_URL, $stkUrl); curl_setopt($curl, CURLOPT_HTTPHEADER, [ 'Content-Type: application/json', 'Authorization: Bearer ' . $token ]); curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); $response = curl_exec($curl); $info = curl_getinfo($curl); curl_close($curl); if ($info['http_code'] == 200) { echo "Payment initiated. Check phone."; } else { echo "Error: " . $response; } ?>

Adapt ShortCode/passkey from Daraja.​

Callback Handler (callback.php)


 

php

<?php $json = file_get_contents('php://input'); $data = json_decode($json, true); if ($data['Body']['stkCallback']['ResultCode'] == 0) { // Payment successful, update DB $mpesaReceipt = $data['Body']['stkCallback']['CallbackMetadata'][1]['Value']; echo "Success: " . $mpesaReceipt; } else { echo "Failed"; } file_put_contents('callback.log', $json); // Log for debugging ?>

Common Challenges and Tips

  • Token Expiry: Refresh tokens automatically before requests.​

  • HTTPS Required: Sandbox rejects HTTP.​

  • Phone Format: Always use 254 format internationally.​

  • Debugging: Log all responses; use Daraja simulator for edge cases.

  • Security: Never expose keys in frontend; validate callbacks with timestamps/signatures.

Security Best Practices

Use HTTPS everywhere, store sensitive data encrypted, and implement rate limiting. Validate all inputs to prevent injection. For production, register a real PayBill/Till number.

Going Beyond Basics

Extend to B2C (disbursements) or dynamic QR codes. Libraries like mpesa-php simplify code. For Kenyan businesses like yours in ICT, this boosts e-commerce conversions.