M-PESA Daraja C2B Integration with Node.js: Production-Ready Guide
Learn how to build a production-ready M-PESA Daraja C2B integration using Node.js, including callbacks, ngrok testing, security, and deployment.
M-PESA remains the most popular mobile payment platform in Kenya, making integration with Safaricom's Daraja API a requirement for many businesses and developers. This project provides a production-ready Node.js implementation for receiving customer payments through the Daraja Customer-to-Business (C2B) API.
In this guide, we'll walk through the project structure, available endpoints, local testing using ngrok, and best practices for moving to production.
Project Link
Repository: https://github.com/TheODDYSEY/Mpesa-C2B-Daraja
Project Overview
The application is built using Node.js and Express and provides:
- Daraja API authentication
- Callback URL registration
- Payment validation
- Payment confirmation handling
- Transaction storage
- Logging and monitoring
- Development helper endpoints
Project Structure
mpesa-c2b/
├── index.js # Server entry point
├── .env # Environment variables (DO NOT commit)
├── src/
│ ├── app.js # Express app setup
│ ├── routes/
│ │ └── index.js # All API routes
│ ├── controllers/
│ │ ├── callback.controller.js # Handles Safaricom callbacks
│ │ └── admin.controller.js # Admin/dev helper endpoints
│ ├── services/
│ │ └── mpesa.service.js # Daraja API calls + token caching
│ └── utils/
│ ├── logger.js # Winston logger
│ └── transactionStore.js # In-memory transaction store
└── logs/
├── combined.log
└── error.logGetting Started
Install dependencies:
npm installRun in production mode:
npm startRun in development mode:
npm run devThe development mode automatically restarts the server when files change and requires Node.js 18 or newer.
API Endpoints
| Method | URL | Description |
|--------|-----|-------------|
| POST | `/payment/validation` | Called before payment is processed |
| POST | `/payment/confirmation` | Called after payment is processed |Safaricom Callback Endpoints
These endpoints must be registered with the Daraja API.
| Method | URL | Description |
|--------|-----|-------------|
| GET | `/admin/token` | Generate a fresh access token |
| POST | `/admin/register-urls` | Register callback URLs with Safaricom |
| POST | `/admin/simulate` | Simulate a C2B payment (sandbox only) |
| GET | `/admin/transactions` | List all saved transactions |
| GET | `/health` | Server health check |Admin & Development Endpoints
Step-by-Step Usage
1. Generate an Access Token
curl http://localhost:3000/admin/token2. Register Callback URLs
curl -X POST http://localhost:3000/admin/register-urls3. Simulate a Payment
curl -X POST http://localhost:3000/admin/simulate \ -H "Content-Type: application/json" \ -d '{ "amount":100, "msisdn":"254700000000", "billRefNumber":"INV001" }'4. View Transactions
curl http://localhost:3000/admin/transactions5. Test the Validation Endpoint
curl -X POST http://localhost:3000/payment/validation \ -H "Content-Type: application/json" \ -d '{ "BillRefNumber":"123", "TransAmount":100 }'Expected response:
{ "ResultCode": "0", "ResultDesc": "Accepted" }6. Test the Confirmation Endpoint
curl -X POST http://localhost:3000/payment/confirmation \ -H "Content-Type: application/json" \ -d '{ "TransID":"LHD221209DZ7F5AEC", "BillRefNumber":"INV-001", "MSISDN":"254712345678", "TransAmount":1000, "TransactionTimestamp":"20231221093500", "Result":"0" }'Expected response:
{ "ResultCode": "0", "ResultDesc": "Success" }Testing Daraja Callbacks Locally Using ngrok
When developing locally, Safaricom cannot reach your application running on localhost because it is not publicly accessible.
This is where ngrok becomes useful.
Why ngrok?
Safaricom
↓
Public HTTPS URL (ngrok)
↓
localhost:3000Without ngrok, Safaricom sends callbacks to the registered URL, but your local server never receives them.
Step 1: Install ngrok
Download ngrok from:
Create a free account and complete the installation.
Step 2: Start ngrok
Run:
ngrok http 3000Example output:
Forwarding https://abcd-1234.ngrok-free.app -> http://localhost:3000Copy the generated HTTPS URL.
Step 3: Update Environment Variables
CONFIRMATION_URL=https://abcd-1234.ngrok-free.app/payment/confirmation
VALIDATION_URL=https://abcd-1234.ngrok-free.app/payment/validationStep 4: Restart the Application
Ctrl + C
npm startStep 5: Register URLs and Simulate Payment
Register URLs:
curl -X POST http://localhost:3000/admin/register-urlsSimulate payment:
curl -X POST http://localhost:3000/admin/simulate \ -H "Content-Type: application/json" \ -d '{ "amount":100, "msisdn":"254700000000", "billRefNumber":"INV001" }'Check transactions:
curl http://localhost:3000/admin/transactionsStep 6: Verify Callback Reception
Your application logs should display:
info: ConfirmationURL hit — payment received! info: Transaction saved
The transaction should now appear in the transaction list.
Important ngrok Rules
Rule Description
URL changes after restart ngrok generates a new URL each time
Re-register URLs Safaricom continues using old URLs until re-registration
Never use ngrok in production Use a permanent HTTPS domain
Keep ngrok running Closing it stops callback forwardingUnderstanding the Confirmation Payload
Safaricom sends a confirmation payload similar to the following:
{ "TransactionType": "Pay Bill", "TransID": "UCB030CBG1", "TransTime": "20260311161727", "TransAmount": "100.00", "BusinessShortCode": "600584", "BillRefNumber": "INV001", "InvoiceNumber": "", "OrgAccountBalance": "4635316.60", "ThirdPartyTransID": "", "MSISDN": "254700000000", "FirstName": "John" }Fields You Must Save
Field Purpose
TransID Unique M-PESA transaction reference
BillRefNumber Customer account or order reference
TransAmount Amount received
MSISDN Customer phone number
TransTime Transaction timestampThe TransID should always be used to prevent duplicate transaction processing.
Going Live
Before moving to production, update your environment variables:
MPESA_BASE_URL=https://api.safaricom.co.ke
CONSUMER_KEY=your_live_consumer_key
CONSUMER_SECRET=your_live_consumer_secret
SHORTCODE=your_paybill_or_till
CONFIRMATION_URL=https://yourdomain.com/payment/confirmation
VALIDATION_URL=https://yourdomain.com/payment/validation
NODE_ENV=productionAdditional recommendations:
- Disable the simulation endpoint in production.
- Replace the in-memory transaction store with PostgreSQL or MongoDB.
- Configure centralized logging and monitoring.
- Secure all administrative endpoints.
Security Best Practices
- Building payment systems requires careful attention to security.
Never Commit Secrets
- Add your
.envfile to.gitignoreand never store credentials in source control.
Validate Bill References
- Always verify that the incoming
BillRefNumberexists in your system before accepting payments.
Log Every Callback
- Maintain audit logs for all incoming validation and confirmation requests.
Implement Idempotency
- Use the
TransIDas a unique key to prevent processing the same payment more than once. - Use HTTPS Everywhere
- All production callback URLs should use valid SSL certificates and secure HTTPS connections.
Conclusion
This Node.js M-PESA Daraja C2B integration provides a solid foundation for receiving customer payments, validating transactions, and handling payment confirmations reliably. By combining proper callback registration, secure transaction handling, and production-ready deployment practices, you can build a dependable payment infrastructure for Kenyan businesses and applications.
Whether you're building an e-commerce platform, subscription service, school payment system, SACCO portal, or SaaS application, this integration gives you a clean and scalable starting point for accepting M-PESA payments.
Comments