If you followed our STK Push guide, you already know how to prompt a customer to pay from your app. C2B (Customer to Business) works differently. Instead of your app initiating the payment request, the customer pays directly to your Paybill or Till number from their phone, and Daraja sends your server a notification when that payment arrives.
This is the integration that powers most Kenyan e-commerce checkout flows, utility bill payments, school fee collection, and any system where a customer pays independently and the business needs to be notified automatically. A customer sends money to your Paybill, your server receives a callback, you update the order status, and you send a confirmation. No polling, no manual reconciliation, no staff checking M-Pesa messages.
This guide covers the complete C2B integration on Daraja 3.0 using Node.js, URL registration, the validation flow, the confirmation flow, handling callbacks, and the production gotchas that the official documentation skips over.
How C2B Works: The Flow
C2B is fundamentally different from STK Push in one critical way: the payment is customer-initiated, not app-initiated. That means you cannot control when the payment arrives, you can only listen for it.
The flow works like this:
1. You register two URLs with Safaricom, a Validation URL and a Confirmation URL, against your shortcode (Paybill or Till number)
2. A customer pays your Paybill or Till from their M-Pesa menu
3. Safaricom hits your Validation URL (if you have enabled validation) asking: "Should I accept this payment?"
4. You respond with an accept or reject decision
5. Safaricom processes the payment and then hits your Confirmation URL with the completed transaction details
6. You update your database, order fulfilled, payment recorded, receipt issued
If you do not enable validation, steps 3 and 4 are skipped. Most integrations start without validation and add it later for cases where they need to verify account numbers or reference codes before accepting payment.
Prerequisites
Before writing a single line of code, you need:
A Daraja 3.0 account at developer.safaricom.co.ke
A sandbox app created on the portal with the C2B API product enabled
Your Consumer Key and Consumer Secret from the sandbox app
A publicly accessible HTTPS server for your callback URLs,
localhostwill not work. Use a tunnelling tool like ngrok for local development, but read the production warning below carefully.Node.js 18 or above
The
axiosandexpresspackages
Create a .env file:
Step 1: Generate an Access Token
Every Daraja API call requires a Bearer token. The token expires after one hour so you need to generate a fresh one before each request, or cache it and refresh when it expires.
Step 2: Register Your C2B URLs
This is the step most tutorials gloss over and where most C2B integrations fail silently. Before Safaricom can send you any payment notifications, you must register your Validation and Confirmation URLs against your shortcode. This registration must be done once per environment (sandbox and production are separate registrations).
The ResponseType field explained:
This field tells Safaricom what to do if your Validation URL is unreachable or times out:
Completed— Safaricom accepts the payment automatically if your validation URL is downCancelled— Safaricom rejects the payment if your validation URL is down
For most integrations, Completed is the safer choice in production, a payment that goes through while your server is briefly down is better than a customer whose money bounces. You can reconcile later. A failed payment at checkout means a lost customer.
Run registration once:
Step 3: Set Up Your Express Server With Callback Handlers
Now build the server that listens for Safaricom's callbacks.
Step 4: Simulate a C2B Payment in Sandbox
Safaricom provides a simulation endpoint to test your integration without real money. This hits your registered confirmation and validation URLs exactly as a real payment would.
Understanding the Callback Payloads
Validation callback, what Safaricom sends to your Validation URL:
Confirmation callback, what Safaricom sends to your Confirmation URL after successful payment:
Note that OrgAccountBalance is populated in the confirmation but empty in the validation, you can use it to log your running balance after each transaction.
The Complete Project Structure
Production Gotchas — Read Before Going Live
1. Never use M-Pesa, Safaricom, or variants in your callback URLs.
Safaricom's system actively filters and blocks URLs containing those words. https://yourapp.com/mpesa/confirmation will be silently rejected. Use neutral paths like /c2b/confirmation or /payments/confirm.
2. Never use ngrok, mockbin, or requestbin in production.
These public tunnelling and inspection services are blocked by Safaricom's API in the production environment. They are fine for local sandbox testing but must be replaced with your actual server URL before go-live.
3. Localhost URLs are always rejected.
Your callback URLs must be publicly accessible HTTPS endpoints. No exceptions.
4. URL registration is per environment.
Registering URLs in sandbox does not carry over to production. You must run the registration script separately for each environment using your production credentials and shortcode.
5. Validation must be explicitly activated.
The C2B Validation feature is not enabled by default. If you want to use the Validation URL to accept or reject payments before processing, you must contact Safaricom M-Pesa Support or API Support to have it activated for your shortcode. Without activation, Safaricom ignores your Validation URL entirely and goes straight to confirmation.
6. Always respond to callbacks immediately.
Safaricom expects a response from your callback URLs within a few seconds. If your server is doing heavy processing ( database writes, email sends, external API calls ) do that work asynchronously after responding. Accept the callback, queue the work, respond immediately. A slow or unresponsive callback URL will cause Safaricom to retry, leading to duplicate processing on your end.
7. Build idempotency into your confirmation handler.
Safaricom may send the same confirmation more than once in rare cases. Use TransID as your unique key and check for duplicates before updating your database. Processing the same payment twice will cause reconciliation headaches you do not want.
8. The number masking update.
As of February 2026, Safaricom masked phone numbers in consumer-facing M-Pesa transactions. Based on current developer community reports, the MSISDN field in C2B API callbacks still returns the full phone number, this is a backend API callback, not a consumer-facing display. However, confirm this behaviour in your sandbox testing before relying on it for customer identification in production.
Switching to Production
When you are ready to go live:
1. Create a production app on Daraja 3.0 and link it to your live Safaricom business shortcode
2. Swap your .env credentials to production Consumer Key and Consumer Secret
3. Update CONFIRMATION_URL and VALIDATION_URL to your live server endpoints
4. Run registerC2BUrls('production') once
5. Change all 'sandbox' references in your code to 'production'
6. Test with a real Ksh 1 payment before going fully live
Running into issues with your C2B integration? Drop your error message in the comments and we will help you debug it. For the full Daraja 3.0 documentation, visit developer.safaricom.co.ke
Comments