M-Pesa Daraja API Integration Guide for Developers (2026)
To integrate M-Pesa payments, you use Safaricom's Daraja API. The most common integration is STK Push (Lipa Na M-Pesa Online), which prompts a user to enter their M-Pesa PIN on their phone to complete a payment. The process involves: registering on the Daraja developer portal, getting API credentials, generating an OAuth token, sending a STK Push request, and handling the callback with the payment result.
What Is M-Pesa and Why Developers Need to Know It
M-Pesa is a mobile money transfer service launched by Safaricom in Kenya in 2007. It lets users deposit, withdraw, transfer money, and pay for goods and services using their mobile phones, no bank account required. As of 2026, M-Pesa has over 65 million active users across Kenya, Tanzania, Mozambique, DRC, Lesotho, Ghana, Egypt, and other markets. It processes over $314 billion in transactions annually.
For developers building products in East Africa, M-Pesa integration is not optional. It is as fundamental as Stripe integration is for US developers. Roughly 96% of Kenyan adults use M-Pesa, and it is the default payment method for everything from e-commerce purchases to utility bills to salary disbursements.
The Daraja API (Daraja means "bridge" in Swahili) is Safaricom's developer platform for integrating M-Pesa into applications. It provides REST APIs for all M-Pesa transaction types and includes a sandbox environment for testing.
Below is everything you need to build a working M-Pesa integration, from account setup to production deployment. At McTaba Labs, M-Pesa integration is one of the core modules in our 6-month full-stack developer marathon. This guide gives you the technical foundations that our students build on during the programme.
Setting Up Your Daraja Developer Account
Before writing any code, you need to register on the Daraja developer portal and get your sandbox credentials.
Step 1: Register on Daraja
- Go to developer.safaricom.co.ke
- Click "Sign Up" and create an account with your email
- Verify your email and log in
Step 2: Create a sandbox app
- Navigate to "My Apps" and click "Create App" or "Add a New App"
- Give your app a name (e.g., "My Test App")
- Select the APIs you want to use. For most integrations, select "Lipa Na M-Pesa Sandbox" at minimum
- After creation, you will see your Consumer Key and Consumer Secret. Save these securely
Step 3: Note the sandbox test credentials
Daraja provides sandbox test credentials that simulate real M-Pesa behaviour without moving actual money:
- Shortcode (Business short code): 174379
- Passkey: Available on the Daraja portal under "APIs > Lipa Na M-Pesa > Simulate"
- Test phone number: You can use your real Safaricom number in sandbox (no money is charged) or use 254708374149
Important: Sandbox credentials are for testing only. For production, you will need to apply for live credentials through Safaricom, which requires a registered business and KYC verification. The approval process typically takes 2-5 business days.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
Authentication: Getting an OAuth Token
Every Daraja API request requires an OAuth 2.0 access token. The token is valid for 3600 seconds (1 hour) and must be refreshed before expiration.
To get a token, send a GET request to the OAuth endpoint with your Consumer Key and Consumer Secret encoded in Base64 as a Basic Auth header:
// Node.js — Generate OAuth Token
const axios = require('axios');
async function getAccessToken() {
const consumerKey = process.env.MPESA_CONSUMER_KEY;
const consumerSecret = process.env.MPESA_CONSUMER_SECRET;
// Base64 encode the credentials
const auth = Buffer.from(
`${consumerKey}:${consumerSecret}`
).toString('base64');
const { data } = await axios.get(
'https://sandbox.safaricom.co.ke/oauth/v1/generate?grant_type=client_credentials',
{
headers: {
Authorization: `Basic ${auth}`,
},
}
);
return data.access_token;
}
// Usage
const token = await getAccessToken();
console.log('Access token:', token);
// Returns something like: "SGWcJPtNtYNPGm6uSYR9yPYrAI3Bm"
Production URL: Replace sandbox.safaricom.co.ke with api.safaricom.co.ke for live transactions.
Cache the token and refresh it proactively before the 1-hour expiry. Do not request a new token for every API call. This is wasteful and may trigger rate limiting.
// Token caching pattern
let cachedToken = null;
let tokenExpiry = 0;
async function getCachedToken() {
const now = Date.now();
// Refresh 5 minutes before expiry
if (!cachedToken || now >= tokenExpiry - 300000) {
cachedToken = await getAccessToken();
tokenExpiry = now + 3600000; // 1 hour
}
return cachedToken;
}
STK Push (Lipa Na M-Pesa Online)
STK Push is the most common M-Pesa integration pattern. When triggered, it sends a push notification to the customer's phone prompting them to enter their M-Pesa PIN to authorise a payment. This is the same flow used when you pay at a till in a Kenyan supermarket.
How the flow works:
- Your server sends an STK Push request to Daraja with the customer's phone number and amount
- Safaricom sends a push prompt to the customer's phone
- The customer enters their M-Pesa PIN (or cancels)
- Safaricom sends the result to your callback URL
- Your server processes the callback and updates the order/transaction status
// Node.js — Initiate STK Push
const axios = require('axios');
async function initiateSTKPush(phoneNumber, amount, accountRef) {
const token = await getCachedToken();
const shortcode = process.env.MPESA_SHORTCODE; // 174379 for sandbox
const passkey = process.env.MPESA_PASSKEY;
// Generate timestamp in the format YYYYMMDDHHmmss
const timestamp = new Date()
.toISOString()
.replace(/[-T:.Z]/g, '')
.slice(0, 14);
// Generate password: Base64(Shortcode + Passkey + Timestamp)
const password = Buffer.from(
`${shortcode}${passkey}${timestamp}`
).toString('base64');
const { data } = await axios.post(
'https://sandbox.safaricom.co.ke/mpesa/stkpush/v1/processrequest',
{
BusinessShortCode: shortcode,
Password: password,
Timestamp: timestamp,
TransactionType: 'CustomerPayBillOnline',
Amount: amount,
PartyA: phoneNumber, // Customer phone (254XXXXXXXXX)
PartyB: shortcode,
PhoneNumber: phoneNumber,
CallBackURL: process.env.MPESA_CALLBACK_URL,
AccountReference: accountRef, // e.g., order ID
TransactionDesc: `Payment for ${accountRef}`,
},
{
headers: {
Authorization: `Bearer ${token}`,
},
}
);
return data;
// Success response includes:
// { MerchantRequestID, CheckoutRequestID, ResponseCode: "0",
// ResponseDescription: "Success. Request accepted for processing",
// CustomerMessage: "Success. Request accepted for processing" }
}
// Example usage
const result = await initiateSTKPush('254712345678', 100, 'ORDER-001');
Important notes:
- Phone numbers must be in the format
254XXXXXXXXX(no leading zero, no +) - Amount must be a whole number (no decimals). M-Pesa does not support cents
- The
AccountReferencefield is shown on the customer's M-Pesa statement, so use something meaningful like an order ID - The initial response only confirms the request was accepted. The actual payment result comes via the callback
Handling the M-Pesa Callback
After the customer enters (or fails to enter) their M-Pesa PIN, Safaricom sends the result to your callback URL. This is the most critical part of the integration. You must handle callbacks correctly to avoid lost payments or incorrect order statuses.
// Express.js — M-Pesa Callback Handler
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/mpesa/callback', async (req, res) => {
// Always respond immediately with 200
// M-Pesa will retry if it doesn't get a response
res.status(200).json({ ResultCode: 0, ResultDesc: 'Accepted' });
const { Body } = req.body;
const { stkCallback } = Body;
const {
MerchantRequestID,
CheckoutRequestID,
ResultCode,
ResultDesc,
} = stkCallback;
if (ResultCode === 0) {
// Payment successful
const callbackMetadata = stkCallback.CallbackMetadata.Item;
const amount = callbackMetadata.find(
(i) => i.Name === 'Amount'
)?.Value;
const mpesaReceiptNumber = callbackMetadata.find(
(i) => i.Name === 'MpesaReceiptNumber'
)?.Value;
const transactionDate = callbackMetadata.find(
(i) => i.Name === 'TransactionDate'
)?.Value;
const phoneNumber = callbackMetadata.find(
(i) => i.Name === 'PhoneNumber'
)?.Value;
console.log('Payment successful:', {
amount,
mpesaReceiptNumber,
transactionDate,
phoneNumber,
checkoutRequestId: CheckoutRequestID,
});
// TODO: Update your database
// - Mark the order as paid
// - Store the MpesaReceiptNumber for reconciliation
// - Send confirmation to the customer
} else {
// Payment failed or was cancelled
console.log('Payment failed:', {
resultCode: ResultCode,
resultDesc: ResultDesc,
checkoutRequestId: CheckoutRequestID,
});
// Common ResultCodes:
// 1032 — Request cancelled by user
// 1037 — Timeout (user did not respond)
// 2001 — Wrong PIN entered
// 1 — Insufficient balance
// TODO: Update order status to failed/cancelled
}
});
Critical callback best practices:
- Respond immediately with HTTP 200. Process the callback data asynchronously. If Safaricom does not receive a timely response, it will retry, potentially causing duplicate processing.
- Implement idempotency. Use the
CheckoutRequestIDas a unique key. Before processing, check if you have already handled this callback. M-Pesa may send duplicates. - Store the raw callback payload. Before any processing, save the entire raw JSON payload to your database. This is invaluable for debugging and reconciliation.
- Callback URL must be publicly accessible via HTTPS. M-Pesa will not send callbacks to HTTP URLs or localhost. During development, use a tool like ngrok to expose your local server.
C2B and B2C Transactions
Beyond STK Push, Daraja supports two other important transaction types:
C2B (Customer to Business): This handles payments where the customer initiates the transaction from their M-Pesa menu (not via a push prompt). It is commonly used for paybill and till number payments.
To use C2B, you need to register your confirmation and validation URLs:
// Register C2B URLs
async function registerC2BUrls() {
const token = await getCachedToken();
const { data } = await axios.post(
'https://sandbox.safaricom.co.ke/mpesa/c2b/v1/registerurl',
{
ShortCode: process.env.MPESA_SHORTCODE,
ResponseType: 'Completed', // or 'Cancelled'
ConfirmationURL: `${process.env.BASE_URL}/api/mpesa/c2b/confirm`,
ValidationURL: `${process.env.BASE_URL}/api/mpesa/c2b/validate`,
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
return data;
}
The validation URL receives the transaction details before it is completed, allowing you to accept or reject the payment (e.g., reject if the account number is invalid). The confirmation URL receives the final transaction details after completion.
B2C (Business to Customer): This sends money from your business M-Pesa account to a customer's phone. Common use cases include salary disbursement, refunds, cashback, and prize payouts.
// Initiate B2C Payment
async function sendB2CPayment(phoneNumber, amount, remarks) {
const token = await getCachedToken();
const { data } = await axios.post(
'https://sandbox.safaricom.co.ke/mpesa/b2c/v1/paymentrequest',
{
InitiatorName: process.env.MPESA_INITIATOR,
SecurityCredential: process.env.MPESA_SECURITY_CREDENTIAL,
CommandID: 'BusinessPayment', // or SalaryPayment, PromotionPayment
Amount: amount,
PartyA: process.env.MPESA_SHORTCODE,
PartyB: phoneNumber,
Remarks: remarks,
QueueTimeOutURL: `${process.env.BASE_URL}/api/mpesa/b2c/timeout`,
ResultURL: `${process.env.BASE_URL}/api/mpesa/b2c/result`,
Occasion: '',
},
{
headers: { Authorization: `Bearer ${token}` },
}
);
return data;
}
B2C requires a SecurityCredential, which is generated by encrypting your M-Pesa initiator password with Safaricom's public certificate. This is more complex than STK Push. Refer to the Daraja documentation for the certificate encryption process. Our students at McTaba Labs work through this in detail during the payments module.
Common Errors and Debugging
M-Pesa integration has several common pitfalls. The errors you will likely encounter, and how to fix them:
Error: "Bad Request - Invalid AccessToken"
Your OAuth token has expired or is malformed. Generate a fresh token. Ensure you are using the correct Consumer Key and Consumer Secret for the environment (sandbox vs production).
Error: "The initiator information is invalid" (ResultCode 2001)
Usually a wrong password or timestamp issue in STK Push. Double-check that your password is generated as Base64(ShortCode + Passkey + Timestamp) and that the timestamp matches the format YYYYMMDDHHmmss exactly.
Error: Callback never received
The most common debugging challenge. Check these in order:
- Is your callback URL publicly accessible? Test it with curl or a tool like reqbin.com.
- Is your URL using HTTPS? M-Pesa requires HTTPS.
- Is your server actually running and listening on the correct port?
- Check your server logs. The callback might be arriving but your code has an error processing it.
- In sandbox, callbacks can be delayed by several minutes. Be patient.
Error: "Wrong credentials" on production
Production uses different URLs (api.safaricom.co.ke instead of sandbox.safaricom.co.ke) and different credentials. Ensure all environment variables are updated for production.
Error: Amount must be an integer
M-Pesa does not support decimal amounts. Always round to whole numbers. KES 100.50 should be sent as 101 (round up to avoid underpayment).
Error: Phone number format
The phone number must be in the format 254XXXXXXXXX. Common mistakes include using 0712345678 (missing country code), +254712345678 (leading +), or 254-712-345-678 (hyphens).
// Helper: Normalise phone number to Daraja format
function normalisePhone(phone) {
// Remove spaces, hyphens, and leading +
let cleaned = phone.replace(/[s-+]/g, '');
// Convert 07XX to 2547XX
if (cleaned.startsWith('0')) {
cleaned = '254' + cleaned.slice(1);
}
// Validate format
if (!/^254[17]d{8}$/.test(cleaned)) {
throw new Error(`Invalid phone number: ${phone}`);
}
return cleaned;
}
Security Best Practices
Handling money demands the highest standard of security. A bug in a blog app is embarrassing. A bug in a payments integration can lose real money and destroy trust. Follow these practices rigorously:
1. Never expose credentials in client-side code.
Your Consumer Key, Consumer Secret, and Passkey must never appear in front-end JavaScript, mobile app code, or any client-accessible location. All M-Pesa API calls must originate from your server.
2. Use environment variables for all secrets.
# .env file — NEVER commit this to git
MPESA_CONSUMER_KEY=your_consumer_key_here
MPESA_CONSUMER_SECRET=your_consumer_secret_here
MPESA_PASSKEY=your_passkey_here
MPESA_SHORTCODE=174379
MPESA_CALLBACK_URL=https://yourdomain.com/api/mpesa/callback
3. Validate callback authenticity.
While Daraja does not provide a webhook signature mechanism (unlike Stripe), you should implement your own validation:
- Only accept callbacks from Safaricom IP ranges
- Verify that the
CheckoutRequestIDin the callback matches one you actually initiated - Validate that the amount in the callback matches the expected payment amount
- Rate-limit your callback endpoint to prevent abuse
4. Implement reconciliation. Do not rely solely on callbacks to confirm payments. Use the Transaction Status API to periodically verify pending transactions. Callbacks can be lost or delayed, and reconciliation catches these edge cases.
5. Log everything. Log every API request and callback with timestamps, request IDs, and full payloads. Store logs for at least 12 months. When a customer says "I paid but my order was not processed," you need logs to investigate.
6. Use a separate shortcode for testing. Never test against your production shortcode. Use the sandbox environment for all development and testing. When you need to test in production, use small amounts (KES 1-10) and have a clear rollback process.
Going to Production: Checklist
Before launching your M-Pesa integration to real users, work through this checklist:
- Apply for production credentials: Submit your application on the Daraja portal with your business registration documents. Allow 2-5 business days for approval.
- Update all URLs: Replace
sandbox.safaricom.co.kewithapi.safaricom.co.kein every API endpoint. - Switch credentials: Update Consumer Key, Consumer Secret, Shortcode, and Passkey to production values.
- HTTPS everywhere: Ensure your callback URL uses HTTPS with a valid SSL certificate.
- Test with small amounts: Make 3-5 real transactions with KES 1-10 to verify the end-to-end flow works in production.
- Implement error handling and retries. Handle network timeouts, API errors, and edge cases gracefully.
- Set up monitoring. Alert on failed callbacks, high error rates, or unusual transaction patterns.
- Implement reconciliation. Build a daily process that compares your transaction records against M-Pesa statements.
- Document your flow. Write internal documentation for how your M-Pesa integration works, including error-handling procedures.
- Comply with regulations. Ensure you meet CBK (Central Bank of Kenya) requirements for handling mobile money transactions, including KYC and AML obligations.
M-Pesa integration is one of the most career-relevant skills for developers in East Africa. At McTaba Labs, our students build production-grade M-Pesa integrations as part of the core curriculum. That goes well beyond the STK Push basics covered here, including the complete payment stack with reconciliation, refunds, and multi-tenant payment systems. If you want to go deeper, our 6-month full-stack developer marathon is the most thorough training available.
Key Takeaways
- ✓M-Pesa processes over $314 billion annually and is the dominant payment method in East Africa. Every developer building for this market needs to know how to integrate it.
- ✓The Daraja API provides four main capabilities: STK Push (customer-initiated payments), C2B (customer to business), B2C (business to customer), and B2B (business to business).
- ✓Always develop and test in the Daraja sandbox before going to production. Sandbox credentials are free and available instantly.
- ✓Security is critical: never expose your consumer secret or passkey in client-side code, always validate callbacks, and use HTTPS for all endpoints.
- ✓We teach M-Pesa integration as a core part of the McTaba Labs 6-month curriculum, with hands-on projects that go to production.
Frequently Asked Questions
- Is the M-Pesa Daraja API free to use?
- The Daraja sandbox is free for development and testing. For production, there is no API usage fee from Safaricom, but M-Pesa charges transaction fees on each payment, typically 0-1% depending on the transaction type and amount. You will also need a registered business to get production credentials.
- Can I test M-Pesa without a Safaricom SIM card?
- Yes, in sandbox mode. The Daraja sandbox simulates the entire M-Pesa flow without requiring a real Safaricom SIM. You can use test phone numbers like 254708374149. For production testing, you will need a real Safaricom number, but you can test with amounts as low as KES 1.
- How long does it take to get M-Pesa production credentials?
- Typically 2-5 business days after submitting your application with the required business documents (business registration certificate, KRA PIN, and ID of the authorised signatory). The process is faster if your documents are in order. Some developers report delays of up to 2 weeks during peak periods.
- Can I use M-Pesa Daraja API outside Kenya?
- Daraja is Safaricom Kenya's platform. M-Pesa in other countries (Tanzania via Vodacom, Mozambique, DRC, etc.) has separate APIs with different endpoints and documentation. The concepts are similar, but the code is not directly portable. Safaricom has been working on a unified API, but as of 2026, you need to integrate separately for each country.
- What is the best tech stack for M-Pesa integration?
- M-Pesa Daraja is a REST API, so it works with any backend language. Node.js (Express or Fastify), Python (Flask or Django), and PHP (Laravel) are the most common choices in the Kenyan developer community. At McTaba Labs, we teach M-Pesa integration with Node.js and TypeScript because of the full-stack JavaScript synergy with React front-ends.
Start learning for free
Create a free McTaba Academy account. Access starter lessons, join the community, and explore at your own pace.
Create Free Account