openmpesa-sdk
v1.0.8
Published
Official Node.js SDK for OpenMpesa API - Complete M-Pesa integration with 20+ endpoints
Maintainers
Readme
OpenMpesa Node.js SDK
Official Node.js SDK for the OpenMpesa API - Complete M-Pesa integration with 20+ endpoints
🚀 Features
- ✅ 20+ M-Pesa Endpoints: Complete Daraja API coverage
- ✅ Promise-based: Modern async/await syntax
- ✅ Lightweight: Minimal dependencies (only axios)
- ✅ Well-documented: Comprehensive examples and guides
- ✅ Production-ready: Battle-tested in production environments
- ✅ Error Handling: Detailed error messages and status codes
- ✅ TypeScript Ready: Full IntelliSense support
- ✅ Platform Callback URLs: No need to provide URLs in each request (v2.0+)
⚠️ Version 2.0.0 Breaking Changes
Important: Version 2.0.0 introduces breaking changes. If you're upgrading from v1.x, please read the Migration Guide.
What Changed:
- ✅ Removed
resultURLandqueueTimeOutURLfrom all transaction methods - ✅ Removed
validationUrlandconfirmationUrlfrom C2B registration - ✅ Removed
callbackUrlfrom Bill Manager optin - ✅ Added
shortCoderequirement to C2B and Pull Transactions registration
Why? The OpenMpesa platform now handles all callbacks centrally, providing:
- Automatic retry mechanism with exponential backoff
- Callback signature verification for security
- Centralized callback management
- Simpler API calls
Quick Example:
// ❌ Old way (v1.x) - providing URLs in each request
await mpesa.b2c.initiate({
amount: 1000,
phone: '254712345678',
resultURL: 'https://yourdomain.com/callback', // No longer needed
queueTimeOutURL: 'https://yourdomain.com/timeout' // No longer needed
});
// ✅ New way (v2.0) - set callback URL once when creating merchant
await mpesa.b2c.initiate({
amount: 1000,
phone: '254712345678',
remarks: 'Payment'
// No URL parameters needed!
});See the full migration guide below.
📋 Table of Contents
- Installation
- Getting Started
- Authentication
- API Reference
- Error Handling
- Best Practices
- Examples
- Troubleshooting
📦 Installation
npm install openmpesa-sdkOr with yarn:
yarn add openmpesa-sdk🏁 Getting Started
Step 1: Get Your API Key
- Sign up at OpenMpesa
- Login and navigate to your dashboard
- Create a merchant account with your M-Pesa credentials
- Copy your API key from the dashboard
Step 2: Initialize the SDK
const OpenMpesa = require('openmpesa-sdk');
// Initialize with your API key
const mpesa = new OpenMpesa('YOUR_API_KEY');Step 3: Make Your First Request
async function makePayment() {
try {
const response = await mpesa.stk.push({
amount: 100,
phone: '254712345678',
reference: 'ORDER-123',
description: 'Payment for order'
});
console.log('Payment initiated:', response);
} catch (error) {
console.error('Error:', error.message);
}
}
makePayment();🔐 Authentication
The SDK uses API key authentication. Include your API key when initializing:
const mpesa = new OpenMpesa(process.env.OPENMPESA_API_KEY);Security Best Practices:
- Never commit API keys to version control
- Use environment variables to store sensitive credentials
- Rotate API keys periodically
- Use different API keys for development and production
Environment Variables Setup
Create a .env file in your project root:
OPENMPESA_API_KEY=your_api_key_hereThen load it in your application:
require('dotenv').config();
const mpesa = new OpenMpesa(process.env.OPENMPESA_API_KEY);📡 API Reference
STK Push (Lipa Na M-Pesa Online)
Initiate a payment request to a customer's phone.
// Initiate STK Push
const response = await mpesa.stk.push({
amount: 100, // Required: Amount to charge
phone: '254712345678', // Required: Customer phone number (254...)
reference: 'ORDER-123', // Optional: Your reference/order ID
description: 'Payment' // Optional: Payment description
});
// Response
{
success: true,
MerchantRequestID: '29115-34620561-1',
CheckoutRequestID: 'ws_CO_DMZ_123456789_12345678',
ResponseCode: '0',
ResponseDescription: 'Success. Request accepted for processing',
CustomerMessage: 'Success. Request accepted for processing'
}Query STK Push Status:
const status = await mpesa.stk.query({
checkoutRequestId: 'ws_CO_DMZ_123456789_12345678'
});
// Response
{
MerchantRequestID: '29115-34620561-1',
CheckoutRequestID: 'ws_CO_DMZ_123456789_12345678',
ResultCode: '0', // 0 = Success, 1032 = Cancelled, etc.
ResultDesc: 'The service request is processed successfully.'
}Result Codes:
0- Success1032- Request cancelled by user1037- Timeout (user didn't enter PIN)2001- Invalid initiator information
C2B (Customer to Business)
Simulate a customer payment to your business.
// Simulate C2B payment
const response = await mpesa.c2b.initiate({
amount: 100, // Required: Amount
phone: '254712345678', // Required: Customer phone
billRefNumber: 'ACCOUNT-123' // Optional: Account reference
});Register C2B URLs:
await mpesa.c2bRegister.register({
validationUrl: 'https://yourdomain.com/api/validation',
confirmationUrl: 'https://yourdomain.com/api/confirmation',
responseType: 'Completed' // or 'Cancelled'
});Note: Your validation and confirmation URLs must be publicly accessible HTTPS endpoints.
B2C (Business to Customer)
Send money from your business to a customer.
const response = await mpesa.b2c.initiate({
amount: 1000, // Required: Amount to send
phone: '254712345678', // Required: Recipient phone
remarks: 'Salary payment', // Optional: Transaction remarks
occasion: 'Monthly salary' // Optional: Occasion
});Use Cases:
- Salary payments
- Refunds
- Cashback
- Rewards and incentives
B2B (Business to Business)
Transfer funds between businesses.
const response = await mpesa.b2b.initiate({
amount: 5000, // Required: Amount
receiverShortCode: '600000', // Required: Receiver paybill/till
accountReference: 'INV-2024-001', // Optional: Invoice/reference
remarks: 'Payment for services' // Optional: Remarks
});B2B Express
Fast business-to-business transfer.
const response = await mpesa.b2bExpress.initiate({
primaryShortCode: '600000', // Required: Sender shortcode
receiverShortCode: '600001', // Required: Receiver shortcode
amount: 10000, // Required: Amount
accountReference: 'TRANSFER-001' // Optional: Reference
});B2P (Business to Pochi)
Transfer to M-Pesa Pochi (wallet).
const response = await mpesa.b2p.initiate({
amount: 500, // Required: Amount
phone: '254712345678', // Required: Recipient phone
remarks: 'Pochi transfer' // Optional: Remarks
});B2C Top Up
Business to customer top-up.
const response = await mpesa.b2cTopup.initiate({
amount: 1000, // Required: Amount
partyA: '600000', // Required: Sender shortcode
partyB: '600001', // Required: Receiver shortcode
accountReference: 'TOPUP-001', // Optional: Reference
requester: '254712345678', // Optional: Requester phone
remarks: 'Account top up' // Optional: Remarks
});Paybill
Pay to a paybill number.
const response = await mpesa.paybill.initiate({
amount: 500, // Required: Amount
paybillNumber: '888880', // Required: Paybill number
billNumber: 'ACCOUNT-123', // Required: Account number
remarks: 'Utility payment' // Optional: Remarks
});Common Paybills:
888880- KRA (Tax)400200- Equity Bank247247- M-Pesa Paybill
Buy Goods
Pay to a till number.
const response = await mpesa.buygoods.initiate({
amount: 1500, // Required: Amount
tillNumber: '123456', // Required: Till number
remarks: 'Purchase from store' // Optional: Remarks
});Account Balance
Check your M-Pesa account balance.
const response = await mpesa.balance.get({
remarks: 'Balance check' // Optional: Remarks
});Note: Balance check is asynchronous. Results are sent to your callback URL.
Transaction Reversal
Reverse a completed transaction.
const response = await mpesa.reversal.initiate({
transactionId: 'OEI2AK4Q16', // Required: Original transaction ID
amount: 100, // Required: Amount to reverse
receiverParty: '254712345678', // Required: Receiver party
remarks: 'Refund for cancelled order' // Optional: Remarks
});Important:
- Can only reverse transactions within 24 hours
- Requires the exact transaction ID from the original transaction
- Amount must match the original transaction amount
Transaction Status
Query the status of any transaction.
const response = await mpesa.transactionStatus.query({
transactionId: 'OEI2AK4Q16', // Required: Transaction ID
occasion: 'Status check' // Optional: Occasion
});Tax Remittance
Pay taxes to KRA (Kenya Revenue Authority).
const response = await mpesa.tax.remit({
amount: 5000, // Required: Amount
taxHead: 'VAT', // Required: Tax type (VAT, PAYE, etc.)
remarks: 'Monthly VAT payment' // Optional: Remarks
});Tax Heads:
VAT- Value Added TaxPAYE- Pay As You EarnCORPORATION- Corporation Tax
Dynamic QR Code
Generate a QR code for payment.
const response = await mpesa.qr.generate({
amount: 500, // Required: Amount
reference: 'QR-ORDER-123', // Required: Reference
trxCode: 'BG' // Required: Transaction code (BG, PB, etc.)
});
// Response includes base64 encoded QR code image
{
success: true,
ResponseCode: '0',
QRCode: 'iVBORw0KGgoAAAANSUhEUgAA...' // Base64 image
}Transaction Codes:
BG- Buy GoodsPB- PaybillWA- Withdraw CashSM- Send Money
Standing Orders (Ratiba)
Set up recurring payments.
const response = await mpesa.ratiba.manage({
amount: 1000, // Required: Amount
phone: '254712345678', // Required: Phone number
frequency: 'Monthly', // Required: Frequency
startDate: '2024-01-01', // Required: Start date
endDate: '2024-12-31', // Required: End date
remarks: 'Monthly subscription' // Optional: Remarks
});Frequencies:
DailyWeeklyMonthlyQuarterlyAnnually
Bill Manager
M-Pesa Bill Manager enables businesses to send e-invoices and manage payments.
Important: You must opt-in before sending invoices (one-time setup).
// 1. Opt-in to Bill Manager (one-time setup)
await mpesa.billmanager.optin({
email: '[email protected]',
officialContact: '0710123456',
sendReminders: true, // Enable SMS payment reminders
callbackUrl: 'https://yourdomain.com/mpesa/callback'
});
// 2. Send a single invoice
const invoice = await mpesa.billmanager.sendInvoice({
externalReference: 'INV-2024-001',
billedFullName: 'John Doe',
billedPhoneNumber: '0722000000',
billedPeriod: 'January 2024',
invoiceName: 'Monthly Subscription',
dueDate: '2024-02-15',
accountReference: 'ACC-12345',
amount: 5000,
invoiceItems: [ // Optional
{ itemName: 'Service Fee', amount: '4000' },
{ itemName: 'Tax', amount: '1000' }
]
});
// 3. Send bulk invoices (up to 1000)
await mpesa.billmanager.sendBulkInvoices([
{
externalReference: 'INV-001',
billedFullName: 'Customer 1',
// ... other fields
},
{
externalReference: 'INV-002',
billedFullName: 'Customer 2',
// ... other fields
}
]);
// 4. Reconcile payment (acknowledge and send receipt)
await mpesa.billmanager.reconcile({
paymentDate: '2024-01-15',
paidAmount: 5000,
accountReference: 'ACC-12345',
transactionId: 'PJB53MYR1N',
phoneNumber: '0722000000',
fullName: 'John Doe',
invoiceName: 'Monthly Subscription',
externalReference: 'INV-2024-001'
});
// 5. Cancel a single invoice
await mpesa.billmanager.cancelInvoice('INV-2024-001');
// 6. Cancel multiple invoices
await mpesa.billmanager.cancelBulkInvoices(['INV-001', 'INV-002']);Note: Bill Manager is a commercial API (KES 20 per invoice). Requires signed agreement with Safaricom.
Pull Transactions
Query M-Pesa transactions for a date range.
// Query transactions
const response = await mpesa.pullTransactions.query({
startDate: '2024-01-01 00:00:00', // Required: Start date
endDate: '2024-01-31 23:59:59', // Required: End date
offSetValue: 0 // Optional: Pagination offset
});
// Register for pull transactions (one-time setup)
await mpesa.pullTransactions.register({
nominatedNumber: '254712345678' // Required: Your phone number
});Note: You must register before you can pull transactions.
IoT SIM Management
Manage IoT SIM cards (for IoT devices).
// Get all SIMs
const sims = await mpesa.iotSim.getAllSIMs('254712345678');
// Search messages
const messages = await mpesa.iotSim.searchMessages('254712345678');
// Activate SIM
await mpesa.iotSim.activateSIM('254712345678');
// Get lifecycle status
const status = await mpesa.iotSim.getLifeCycleStatus('254712345678');
// Get customer info
const info = await mpesa.iotSim.getCustomerInfo('254712345678');
// Get activation trends
const trends = await mpesa.iotSim.getActivationTrends();
// Rename asset
await mpesa.iotSim.renameAsset('254712345678', 'Device-001');
// Get location
const location = await mpesa.iotSim.getLocation('254712345678');
// Suspend or unsuspend
await mpesa.iotSim.suspendOrUnsuspend('254712345678', true); // true = suspend
// Generic manage method
await mpesa.iotSim.manage({
action: 'getAllSIMs',
msisdn: '254712345678'
});Available Actions:
getAllSIMs- Get all SIM cardssearchMessages- Search SMS messagessimActivation- Activate a SIMqueryLifeCycleStatus- Check SIM lifecycle statusqueryCustomerInfo- Get customer informationgetActivationTrends- Get activation statisticsrenameAsset- Rename a SIM assetgetLocationInfo- Get SIM locationsuspendUnsuspend- Suspend/unsuspend SIM
IoT Location Info
Get IoT device location.
const location = await mpesa.iotLocation.get({
msisdn: '254712345678' // Required: Device phone number
});
// Response
{
success: true,
location: {
latitude: -1.286389,
longitude: 36.817223,
accuracy: 100
}
}❌ Error Handling
Basic Error Handling
try {
const response = await mpesa.stk.push({
amount: 100,
phone: '254712345678',
reference: 'ORDER-123'
});
console.log('Success:', response);
} catch (error) {
console.error('Error:', error.message);
}Detailed Error Handling
try {
const response = await mpesa.stk.push({
amount: 100,
phone: '254712345678',
reference: 'ORDER-123'
});
} catch (error) {
if (error.response) {
// API returned an error response
console.error('API Error:', {
status: error.response.status,
message: error.response.data.message,
details: error.response.data
});
// Handle specific error codes
if (error.response.status === 401) {
console.error('Invalid API key');
} else if (error.response.status === 429) {
console.error('Rate limit exceeded');
}
} else if (error.request) {
// Request was made but no response received
console.error('Network Error:', error.message);
} else {
// Error in request setup
console.error('Error:', error.message);
}
}Common Error Codes
| Code | Meaning | Solution | |------|---------|----------| | 400 | Bad Request | Check request parameters | | 401 | Unauthorized | Verify API key is correct | | 403 | Forbidden | Check subscription status | | 404 | Not Found | Verify endpoint URL | | 429 | Too Many Requests | Reduce request rate or upgrade plan | | 500 | Server Error | Contact support |
💡 Best Practices
1. Use Environment Variables
// ✅ Good
const mpesa = new OpenMpesa(process.env.OPENMPESA_API_KEY);
// ❌ Bad
const mpesa = new OpenMpesa('hardcoded-api-key');2. Implement Retry Logic
async function stkPushWithRetry(data, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
try {
return await mpesa.stk.push(data);
} catch (error) {
if (i === maxRetries - 1) throw error;
await new Promise(resolve => setTimeout(resolve, 1000 * (i + 1)));
}
}
}3. Validate Phone Numbers
function validateKenyanPhone(phone) {
// Remove spaces and special characters
phone = phone.replace(/[\s\-\(\)]/g, '');
// Convert to 254 format
if (phone.startsWith('0')) {
phone = '254' + phone.substring(1);
} else if (phone.startsWith('+254')) {
phone = phone.substring(1);
} else if (phone.startsWith('254')) {
// Already in correct format
} else {
throw new Error('Invalid phone number format');
}
// Validate length and format
if (!/^254[17]\d{8}$/.test(phone)) {
throw new Error('Invalid Kenyan phone number');
}
return phone;
}
// Usage
const phone = validateKenyanPhone('0712345678'); // Returns: 2547123456784. Handle Callbacks Properly
// Express.js example
app.post('/mpesa/callback', async (req, res) => {
// Always respond immediately
res.json({ ResultCode: 0, ResultDesc: 'Accepted' });
// Process callback asynchronously
try {
const { Body } = req.body;
if (Body?.stkCallback) {
const { CheckoutRequestID, ResultCode, ResultDesc } = Body.stkCallback;
if (ResultCode === 0) {
// Payment successful
await processSuccessfulPayment(CheckoutRequestID);
} else {
// Payment failed
await handleFailedPayment(CheckoutRequestID, ResultDesc);
}
}
} catch (error) {
console.error('Callback processing error:', error);
}
});5. Log Transactions
async function makePayment(orderData) {
const startTime = Date.now();
try {
const response = await mpesa.stk.push({
amount: orderData.amount,
phone: orderData.phone,
reference: orderData.orderId
});
// Log successful request
console.log({
timestamp: new Date().toISOString(),
orderId: orderData.orderId,
checkoutRequestId: response.CheckoutRequestID,
duration: Date.now() - startTime,
status: 'initiated'
});
return response;
} catch (error) {
// Log error
console.error({
timestamp: new Date().toISOString(),
orderId: orderData.orderId,
error: error.message,
duration: Date.now() - startTime,
status: 'failed'
});
throw error;
}
}📚 Examples
Complete Payment Flow
const OpenMpesa = require('openmpesa-sdk');
const mpesa = new OpenMpesa(process.env.OPENMPESA_API_KEY);
async function processOrder(order) {
try {
// 1. Initiate STK Push
console.log('Initiating payment...');
const payment = await mpesa.stk.push({
amount: order.amount,
phone: order.customerPhone,
reference: order.orderId,
description: `Payment for order ${order.orderId}`
});
console.log('Payment initiated:', payment.CheckoutRequestID);
// 2. Save checkout request ID to database
await saveToDatabase({
orderId: order.orderId,
checkoutRequestId: payment.CheckoutRequestID,
status: 'pending'
});
// 3. Poll for status (optional - better to use callbacks)
let attempts = 0;
const maxAttempts = 6; // 60 seconds total
while (attempts < maxAttempts) {
await new Promise(resolve => setTimeout(resolve, 10000)); // Wait 10s
const status = await mpesa.stk.query({
checkoutRequestId: payment.CheckoutRequestID
});
if (status.ResultCode === '0') {
console.log('Payment successful!');
await updateOrderStatus(order.orderId, 'paid');
return { success: true, status: 'paid' };
} else if (status.ResultCode !== undefined && status.ResultCode !== '0') {
console.log('Payment failed:', status.ResultDesc);
await updateOrderStatus(order.orderId, 'failed');
return { success: false, reason: status.ResultDesc };
}
attempts++;
}
console.log('Payment timeout');
return { success: false, reason: 'timeout' };
} catch (error) {
console.error('Payment error:', error.message);
await updateOrderStatus(order.orderId, 'error');
throw error;
}
}
// Usage
processOrder({
orderId: 'ORD-123',
amount: 1500,
customerPhone: '254712345678'
});Bulk Payments (B2C)
async function processBulkPayments(payments) {
const results = [];
for (const payment of payments) {
try {
const response = await mpesa.b2c.initiate({
amount: payment.amount,
phone: payment.phone,
remarks: payment.remarks
});
results.push({
phone: payment.phone,
status: 'success',
transactionId: response.ConversationID
});
// Rate limiting: wait 1 second between requests
await new Promise(resolve => setTimeout(resolve, 1000));
} catch (error) {
results.push({
phone: payment.phone,
status: 'failed',
error: error.message
});
}
}
return results;
}
// Usage
const payments = [
{ phone: '254712345678', amount: 1000, remarks: 'Salary' },
{ phone: '254798765432', amount: 1500, remarks: 'Salary' },
{ phone: '254723456789', amount: 2000, remarks: 'Salary' }
];
const results = await processBulkPayments(payments);
console.log('Bulk payment results:', results);Subscription Payments
async function chargeSubscription(subscription) {
try {
const response = await mpesa.stk.push({
amount: subscription.amount,
phone: subscription.customerPhone,
reference: `SUB-${subscription.id}`,
description: `${subscription.plan} subscription`
});
// Save for webhook processing
await saveSubscriptionCharge({
subscriptionId: subscription.id,
checkoutRequestId: response.CheckoutRequestID,
amount: subscription.amount,
status: 'pending'
});
return response;
} catch (error) {
// Handle failed charge
await logFailedCharge(subscription.id, error.message);
throw error;
}
}🔧 Troubleshooting
Issue: "Invalid API Key"
Solution:
- Verify your API key is correct
- Check if API key is properly loaded from environment variables
- Ensure no extra spaces or newlines in the API key
Issue: "Request timeout"
Solution:
- Check your internet connection
- Verify the base URL is correct
- Increase timeout in axios configuration
Issue: "Phone number invalid"
Solution:
- Use format:
254XXXXXXXXX(12 digits) - Remove spaces, dashes, and parentheses
- Ensure number starts with 254 (not +254 or 0)
Issue: "Insufficient funds"
Solution:
- Check your M-Pesa account balance
- Ensure you have enough float for B2C transactions
- Contact Safaricom to increase your transaction limits
Issue: "Rate limit exceeded"
Solution:
- Implement request throttling
- Upgrade your subscription plan
- Use batch processing for bulk operations
📞 Support
Documentation
- Full API Docs: https://openmpesa.yourdudeken.com/docs
- API Reference: https://openmpesa.yourdudeken.com/api
Community
- GitHub Issues: github.com/yourdudeken/OpenMpesa/issues
- Email Support: [email protected]
Contributing
Contributions are welcome! Please read our Contributing Guide first.
📄 License
MIT © OpenMpesa Team
🙏 Acknowledgments
Built with ❤️ by the OpenMpesa Team. Special thanks to all contributors and the M-Pesa developer community.
📊 API Endpoints Quick Reference
| Endpoint | Method | Description |
|----------|--------|-------------|
| stk.push() | POST | Initiate STK Push payment |
| stk.query() | POST | Query STK Push status |
| c2b.initiate() | POST | Simulate C2B payment |
| c2bRegister.register() | POST | Register C2B URLs |
| b2c.initiate() | POST | Business to Customer transfer |
| b2cTopup.initiate() | POST | B2C Top Up |
| b2b.initiate() | POST | Business to Business transfer |
| b2bExpress.initiate() | POST | B2B Express transfer |
| b2p.initiate() | POST | Business to Pochi transfer |
| paybill.initiate() | POST | Pay to Paybill |
| buygoods.initiate() | POST | Pay to Till Number |
| balance.get() | POST | Check Account Balance |
| reversal.initiate() | POST | Reverse Transaction |
| transactionStatus.query() | POST | Query Transaction Status |
| tax.remit() | POST | Tax Remittance to KRA |
| qr.generate() | POST | Generate Dynamic QR Code |
| ratiba.manage() | POST | Manage Standing Orders |
| billmanager.manage() | POST | Manage Bills |
| pullTransactions.query() | POST | Query Transactions |
| pullTransactions.register() | POST | Register for Pull Transactions |
| iotSim.manage() | POST | IoT SIM Management |
| iotLocation.get() | POST | Get IoT Device Location |
Ready to integrate M-Pesa? Get started now! 🚀
