Moses Sabila

cd ..

M-PESA Daraja C2B Integration with Node.js: Production-Ready Guide

Comments
3 min

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 Daraja C2B Node.js integration architecture showing payment callbacks and transaction processing

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.log

Getting Started

Install dependencies:

npm install

Run in production mode:

npm start

Run in development mode:

npm run dev

The 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/token

2. Register Callback URLs

curl -X POST http://localhost:3000/admin/register-urls

3. 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/transactions

5. 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:3000

Without ngrok, Safaricom sends callbacks to the registered URL, but your local server never receives them.

Step 1: Install ngrok

Download ngrok from:

ngrok Official Website

Create a free account and complete the installation.


Step 2: Start ngrok

Run:

ngrok http 3000

Example output:

Forwarding https://abcd-1234.ngrok-free.app -> http://localhost:3000

Copy 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/validation

Step 4: Restart the Application

Ctrl + C

npm start

Step 5: Register URLs and Simulate Payment

Register URLs:

curl -X POST http://localhost:3000/admin/register-urls

Simulate 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/transactions

Step 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 forwarding

Understanding 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 timestamp

The 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=production

Additional 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 .env file to .gitignore and never store credentials in source control.

Validate Bill References

  • Always verify that the incoming BillRefNumber exists in your system before accepting payments.

Log Every Callback

  • Maintain audit logs for all incoming validation and confirmation requests.

Implement Idempotency

  • Use the TransID as 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

Support

Do you feel like supporting my work? 🙂Buymeacoffee button