
Security News
Attackers Are Hunting High-Impact Node.js Maintainers in a Coordinated Social Engineering Campaign
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.
daraja-javascript-sdk
Advanced tools
A lightweight and easy-to-use JavaScript SDK for Safaricom's M-Pesa Daraja API.
npm install daraja-javascript-sdk
The SDK can be configured either through environment variables or through the constructor. Using environment variables is recommended for better security and ease of use.
Create a .env file in your project root:
# Required configurations
CONSUMER_KEY=your_consumer_key
CONSUMER_SECRET=your_consumer_secret
BUSINESS_SHORT_CODE=174379
PASS_KEY=your_pass_key
CALLBACK_URL=your_callback_url
# Optional configurations
ENVIRONMENT=sandbox # or 'production'
TIMEOUT_URL=your_timeout_url
RESULT_URL=your_result_url
INITIATOR_NAME=your_initiator_name
SECURITY_CREDENTIAL=your_security_credential
Then initialize the SDK:
const Daraja = require('daraja-javascript-sdk');
const daraja = new Daraja(); // Will use environment variables
You can also provide configuration directly:
const Daraja = require('daraja-javascript-sdk');
const daraja = new Daraja({
consumerKey: 'your_consumer_key',
consumerSecret: 'your_consumer_secret',
environment: 'sandbox', // or 'production'
businessShortCode: '174379',
passKey: 'your_pass_key',
callbackUrl: 'your_callback_url'
});
STK Push is a service that allows you to initiate a payment prompt on the customer's phone. The service will send a request to the user's phone, prompting them to enter their M-Pesa PIN to authorize the transaction.
// Basic STK Push
try {
const response = await daraja.stkPush({
phoneNumber: '254712345678', // Phone number to prompt for payment
amount: 1, // Amount to charge
accountReference: 'TEST', // Your reference for the transaction
transactionDesc: 'Test Payment'
});
console.log('STK Push Response:', response);
// Response includes CheckoutRequestID for tracking the transaction
} catch (error) {
console.error('STK Push Error:', error);
}
Here's a simple Express.js server to handle M-Pesa payment notifications:
const express = require('express');
const app = express();
// Allow JSON requests
app.use(express.json());
// Handle M-Pesa payments
app.post('/mpesa/callback', (req, res) => {
// Get the payment details
const { Body } = req.body;
if (Body.stkCallback) {
// Check if payment was successful
if (Body.stkCallback.ResultCode === 0) {
// Get payment details
const items = Body.stkCallback.CallbackMetadata.Item;
const amount = items.find(item => item.Name === 'Amount').Value;
const mpesaReceipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
const phoneNumber = items.find(item => item.Name === 'PhoneNumber').Value;
console.log('Payment Received!');
console.log('Amount:', amount);
console.log('Receipt Number:', mpesaReceipt);
console.log('Phone Number:', phoneNumber);
// TODO: Update your database
// TODO: Send confirmation to customer
} else {
// Payment failed
console.log('Payment failed:', Body.stkCallback.ResultDesc);
}
}
// Always respond to M-Pesa
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
// Start server
app.listen(3000, () => {
console.log('Server is running on port 3000');
});
Simple way to check if a payment was successful:
const Daraja = require('daraja-javascript-sdk');
const daraja = new Daraja();
// Check payment status
async function checkPayment(checkoutRequestId) {
try {
const result = await daraja.stkPushQuery({
checkoutRequestId: checkoutRequestId
});
if (result.ResultCode === 0) {
console.log('Payment was successful!');
} else {
console.log('Payment failed or pending');
}
} catch (error) {
console.log('Error checking payment:', error.message);
}
}
Here's a complete example showing how to:
const express = require('express');
const Daraja = require('daraja-javascript-sdk');
const app = express();
app.use(express.json());
// Store payments in memory (use a database in production)
const payments = [];
// Initialize Daraja
const daraja = new Daraja();
// Start payment
app.post('/pay', async (req, res) => {
try {
const { phone, amount } = req.body;
// Start STK Push
const result = await daraja.stkPush({
phoneNumber: phone,
amount: amount,
accountReference: 'Test',
transactionDesc: 'Test Payment'
});
// Save payment details
payments.push({
checkoutRequestId: result.CheckoutRequestID,
amount: amount,
phone: phone,
status: 'pending'
});
res.json({
message: 'Payment started',
checkoutRequestId: result.CheckoutRequestID
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Handle M-Pesa callback
app.post('/mpesa/callback', (req, res) => {
const { Body } = req.body;
if (Body.stkCallback) {
const { ResultCode, ResultDesc, CheckoutRequestID } = Body.stkCallback;
// Find the payment
const payment = payments.find(p => p.checkoutRequestId === CheckoutRequestID);
if (payment) {
if (ResultCode === 0) {
// Payment successful
const items = Body.stkCallback.CallbackMetadata.Item;
payment.status = 'completed';
payment.mpesaReceipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
console.log('Payment completed:', payment);
} else {
// Payment failed
payment.status = 'failed';
payment.error = ResultDesc;
console.log('Payment failed:', ResultDesc);
}
}
}
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
// Check payment status
app.get('/status/:checkoutRequestId', async (req, res) => {
const { checkoutRequestId } = req.params;
// Find payment in our records
const payment = payments.find(p => p.checkoutRequestId === checkoutRequestId);
if (!payment) {
return res.status(404).json({ error: 'Payment not found' });
}
res.json({ payment });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});
When a customer makes a payment:
Option 1: Using a Domain
const daraja = new Daraja({
callbackUrl: 'https://your-domain.com/mpesa/callback'
});
Option 2: Using Ngrok for Development
npm install -g ngrok
node server.js # Running on port 3000
ngrok http 3000
const daraja = new Daraja({
callbackUrl: 'https://your-ngrok-url.ngrok.io/mpesa/callback'
});
const express = require('express');
const Daraja = require('daraja-javascript-sdk');
const app = express();
// Parse JSON bodies
app.use(express.json());
// Initialize Daraja with your domain
const daraja = new Daraja({
callbackUrl: 'https://your-domain.com/mpesa/callback' // Your domain
});
// Endpoint to start payment
app.post('/start-payment', async (req, res) => {
try {
const result = await daraja.stkPush({
phoneNumber: '254712345678',
amount: 1,
accountReference: 'TEST',
transactionDesc: 'Test Payment'
});
res.json(result);
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Callback endpoint that M-Pesa will call
app.post('/mpesa/callback', (req, res) => {
const { Body } = req.body;
if (Body.stkCallback) {
if (Body.stkCallback.ResultCode === 0) {
// Payment successful
const items = Body.stkCallback.CallbackMetadata.Item;
const amount = items.find(item => item.Name === 'Amount').Value;
const receipt = items.find(item => item.Name === 'MpesaReceiptNumber').Value;
console.log(`Received payment of ${amount} KSH, receipt: ${receipt}`);
// TODO: Update your database
// TODO: Notify your customer
}
}
// Always respond to M-Pesa
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
# Terminal 1: Start your server
node server.js
# Terminal 2: Start ngrok
ngrok http 3000
# Copy the HTTPS URL from ngrok output
# Example: https://abc123.ngrok.io
res.json({ ResultCode: 0, ResultDesc: "Success" });
app.post('/mpesa/callback', (req, res) => {
// Log incoming callback
console.log('Received callback:', JSON.stringify(req.body));
// Process callback
// ...
// Log response
console.log('Sending response to M-Pesa');
res.json({ ResultCode: 0, ResultDesc: "Success" });
});
const processedTransactions = new Set();
app.post('/mpesa/callback', (req, res) => {
const transactionId = req.body.Body.stkCallback.CheckoutRequestID;
if (processedTransactions.has(transactionId)) {
console.log('Duplicate transaction:', transactionId);
return res.json({ ResultCode: 0, ResultDesc: "Success" });
}
processedTransactions.add(transactionId);
// Process the payment...
});
// When starting payment
const result = await daraja.stkPush({...});
// Set timeout to check status
setTimeout(async () => {
const status = await daraja.stkPushQuery({
checkoutRequestId: result.CheckoutRequestID
});
if (status.ResultCode !== 0) {
// Handle timeout
console.log('Payment timed out');
}
}, 60000); // Check after 1 minute
Remember:
Send money from your business to customers:
try {
const response = await daraja.b2c({
amount: 100,
phoneNumber: '254712345678',
commandID: 'BusinessPayment', // or 'SalaryPayment', 'PromotionPayment'
remarks: 'Refund'
});
console.log('B2C Response:', response);
} catch (error) {
console.error('B2C Error:', error);
}
Check the status of a transaction:
try {
const response = await daraja.transactionStatus({
transactionID: 'TRANSACTION_ID'
});
console.log('Status:', response);
} catch (error) {
console.error('Status Check Error:', error);
}
Query your M-Pesa account balance:
try {
const response = await daraja.accountBalance();
console.log('Balance:', response);
} catch (error) {
console.error('Balance Query Error:', error);
}
Register URLs for C2B transactions and simulate payments (simulation only works in sandbox):
// Register URLs for C2B
try {
const response = await daraja.c2bRegisterUrl({
shortCode: '123456', // Optional, uses businessShortCode by default
responseType: 'Completed', // Optional
confirmationUrl: 'https://your-domain.com/mpesa/confirmation',
validationUrl: 'https://your-domain.com/mpesa/validation'
});
console.log('C2B URLs registered:', response);
} catch (error) {
console.error('C2B URL registration failed:', error);
}
// Simulate C2B payment (sandbox only)
try {
const response = await daraja.c2bSimulate({
amount: 100,
phoneNumber: '254712345678',
billRefNumber: 'TEST123'
});
console.log('C2B Simulation:', response);
} catch (error) {
console.error('C2B Simulation failed:', error);
}
Transfer money between businesses:
try {
const response = await daraja.b2b({
amount: 1000,
receiverShortCode: '987654',
commandID: 'BusinessToBusinessTransfer', // Optional
remarks: 'Supplier Payment' // Optional
});
console.log('B2B Response:', response);
} catch (error) {
console.error('B2B Transfer failed:', error);
}
Reverse an M-Pesa transaction:
try {
const response = await daraja.reversal({
transactionID: 'ABCD1234',
amount: 100,
remarks: 'Wrong payment' // Optional
});
console.log('Reversal Response:', response);
} catch (error) {
console.error('Reversal failed:', error);
}
Check the status of an STK push request:
try {
const response = await daraja.stkPushQuery({
checkoutRequestId: 'ws_CO_123456789'
});
console.log('STK Query Response:', response);
} catch (error) {
console.error('STK Query failed:', error);
}
For testing purposes, use these sandbox credentials:
To contribute or modify the SDK:
npm install.env file with your test credentialsnpm testMIT
For support, please raise an issue in the GitHub repository or contact the maintainers.
FAQs
A JavaScript SDK for Safaricom's M-Pesa Daraja API
We found that daraja-javascript-sdk demonstrated a not healthy version release cadence and project activity because the last version was released a year ago. It has 0 open source maintainers collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Multiple high-impact npm maintainers confirm they have been targeted in the same social engineering campaign that compromised Axios.

Security News
Axios compromise traced to social engineering, showing how attacks on maintainers can bypass controls and expose the broader software supply chain.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.