The STK Push gets all the attention because it is what users interact with directly, a prompt appears on their phone, they enter their PIN, money moves. But a large class of business problems requires the reverse: your system needs to push money out to a customer's phone without any action on their end. Refunds, loan disbursements, withdrawal payouts, salary payments, promotional cashbacks, marketplace seller payments, all of these are B2C problems.
The Daraja Business to Customer (B2C) API handles exactly this. You call the endpoint with a phone number and an amount, Safaricom processes the transaction, and the money lands in the recipient's M-Pesa wallet. No STK prompt. No customer action required.
This guide covers the full B2C integration in Node.js, from understanding what makes B2C different from STK Push, through generating the SecurityCredential that trips most developers up, to parsing the async callback correctly. If you have already read our STK Push guide and C2B guide, the patterns here will feel familiar.
How B2C Is Different From STK Push
Before touching any code, it is worth being precise about what B2C does and does not do, because the assumptions people carry from STK Push cause the most common B2C mistakes.
STK Push is customer-initiated in the sense that the customer must actively approve the transaction by entering their PIN. Your system triggers the prompt, but the money does not move until the customer responds.
B2C is business-initiated. Your system sends the request, Safaricom validates it, and the money moves. The recipient gets a notification SMS but takes no action. This means:
You need a B2C-specific shortcode — a standard Paybill or Till number does not work. You need to apply specifically for a B2C shortcode through Safaricom.
You need an Initiator — an API operator account created on the M-Pesa business portal with the correct role assigned.
You need a SecurityCredential — an encrypted version of the Initiator's password, generated using Safaricom's public key certificate. This is the step that blocks most developers.
B2C is asynchronous. Unlike STK Push where you wait for the callback, B2C has two callback URLs: a
ResultURLfor successful responses and aQueueTimeOutURLfor when the transaction takes too long. Both must be live HTTPS endpoints when going to production.
Prerequisites
Before writing a single line of code, confirm you have all of the following:
A B2C shortcode — this is different from your standard Paybill or Till number. Log into the M-Pesa Business portal at business.safaricom.co.ke. Under your organisation's shortcodes, check whether a B2C shortcode is available. If not, you need to apply to Safaricom's M-Pesa Business team.
An Initiator account with the correct role — on the M-Pesa Business portal, create an API operator account (not a web operator). Assign it the Org Business Manager role and the B2C Org API Initiator role. The username assigned during creation is your InitiatorName. The password set is the plaintext password you will encrypt into a SecurityCredential.
A Daraja app with B2C enabled — on developer.safaricom.co.ke, create or use an existing app and ensure B2C is among the enabled APIs. Note your Consumer Key and Consumer Secret.
Public HTTPS callback URLs — for sandbox testing, tools like ngrok work. For production, these must be real HTTPS endpoints. Do not use ngrok or any tunneling tool in production.
Generating the SecurityCredential
This is the step where most B2C integrations stall. The SecurityCredential is not your Initiator password in plaintext, it is your Initiator password encrypted with Safaricom's M-Pesa public key certificate, then Base64 encoded.
Safaricom provides two certificates:
Sandbox: downloadable from developer.safaricom.co.ke under Test Credentials
Production: downloadable from the same portal under your production app
Here is how to generate the SecurityCredential in Node.js:
Important: Generate this once and store it. Do not regenerate it on every request, the certificate changes rarely and regenerating unnecessarily adds latency. Store the generated credential in your environment variables.
Also important: The Initiator password on the M-Pesa Business portal expires periodically, typically every 90 days. When it expires, your B2C requests will fail with "The Initiator Information Is Invalid." Set a calendar reminder to renew the password before expiry and regenerate the SecurityCredential immediately after.
Store credentials in a .env file:
Project Setup
Step 1: Generate an Access Token
Every Daraja API call requires a Bearer token obtained by authenticating with your Consumer Key and Consumer Secret. This is identical to the STK Push and C2B guides.
Step 2: The B2C Request
The B2C API endpoint is:
Sandbox:
https://sandbox.safaricom.co.ke/mpesa/b2c/v3/paymentrequestProduction:
https://api.safaricom.co.ke/mpesa/b2c/v3/paymentrequest
Understanding CommandID
The CommandID field tells Safaricom what type of B2C payment this is. There are three valid values:
CommandID | Use case |
|---|---|
| General payments — refunds, withdrawals, marketplace seller payouts, any ad-hoc payment |
| Employee salary disbursements |
| Cashbacks, loyalty rewards, promotional disbursements |
Use BusinessPayment for the majority of use cases unless your specific deployment is salary or promotion related. The CommandID affects how the transaction is reported in M-Pesa statements and how Safaricom classifies your usage pattern.
Step 3: Handling the Callbacks
B2C is fully asynchronous. The initial API response tells you Safaricom has accepted the request. The actual result (success or failure ) arrives later at your ResultURL or QueueTimeOutURL. You must handle both.
The Result Callback (Success or Failure)
The Timeout Callback
This fires when the transaction has been queued but Safaricom has not processed it within the timeout window. This does not necessarily mean the transaction failed, it may still process later.
Step 4: The Full Express Server
Idempotency and the OriginatorConversationID
The OriginatorConversationID is Safaricom's reference for your request. When your callback arrives, this is how you match it to the payout record in your database.
The critical pattern: store the OriginatorConversationID in your database at the moment you initiate the B2C request, before waiting for the callback. This way, when the callback arrives ( potentially minutes later ) you have a clean record to update.
Never process a callback without first confirming the OriginatorConversationID exists in your database. Unknown IDs should be logged and investigated, they may indicate replay attacks.
Common Errors and What They Mean
"The Initiator Information Is Invalid" Your SecurityCredential does not match the Initiator. Either you used the wrong password to generate it, used the sandbox certificate for a production request (or vice versa), or the Initiator password has expired on the M-Pesa Business portal. Renew the password and regenerate the SecurityCredential.
"Invalid Access Token" The Bearer token has expired (tokens last 1 hour). Implement token caching with automatic refresh rather than fetching a new token on every request.
"The request requires authentication" (HTTP 401) Your Consumer Key or Consumer Secret is wrong, or you are using sandbox credentials against the production endpoint.
"System internal error" (or empty response) Usually a Safaricom-side issue. Implement exponential backoff retry logic for the initiation request. Do not retry blindly, check your database first to avoid duplicate payouts.
No callback received Your ResultURL is not publicly accessible. In sandbox, ensure your ngrok tunnel is running and the URL in your .env matches exactly. In production, ensure your HTTPS certificate is valid and the route is live.
Going to Production Checklist
Apply for and receive a production B2C shortcode from Safaricom
Create the Initiator account on the production M-Pesa Business portal with the correct roles
Download the production certificate from the Daraja portal and regenerate SecurityCredential
Replace sandbox URLs with production URLs (
api.safaricom.co.ke)Replace
.envvalues with production Consumer Key and SecretEnsure
ResultURLandQueueTimeOutURLare live HTTPS endpoints, not localhost, not ngrokWhitelist your server IP with Safaricom (required for production)
Test with a small amount (Ksh 1) before enabling full amounts
Set a reminder to renew the Initiator password before its 90-day expiry
Implement Transaction Status API calls for timeout scenarios
What Comes Next in This Series
This guide completes the core M-Pesa payment flows for most applications. The next guide in the series covers the Transaction Status API — how to query the outcome of any B2C, C2B, or STK Push transaction after the fact. This is essential for recovering from timeout scenarios and for building reconciliation tooling.
Read the rest of the Daraja series: STK Push guide | C2B guide | Transaction Status (coming soon)
Questions about your B2C integration? Drop them in the comments, we read every one.
Comments