M-Pesa B2C payouts let your application send money from your business shortcode to a customer phone number. You authenticate with Daraja, call the B2C endpoint with the recipient number and amount, then handle the result callback. Security is critical because you are moving real money out of your account.
B2C stands for Business to Customer. It is the M-Pesa API that lets your application send money from your business account to a customer's phone number. If you have implemented STK Push before, B2C is the opposite direction: instead of pulling money from a user, you are pushing money to them.
The most common use cases in Kenyan applications:
B2C is not the only way to send money via M-Pesa. For business-to-business transfers, there is a separate B2B API. For bulk salary payments, Safaricom offers a bulk disbursement feature through the portal. But for programmatic, real-time payouts triggered by your application logic, B2C through the Daraja API is what you need.
One thing to understand upfront: B2C is more sensitive than C2B (receiving payments). When you accept payments, the worst case is a stuck transaction. When you send payments, the worst case is money leaving your account and going to the wrong person, or going twice. The security requirements are higher for a reason.
The B2C flow has more moving parts than STK Push, but the pattern is similar: you send a request, Daraja processes it asynchronously, and you receive the result on a callback URL. Here is the complete sequence.
Step 1: Generate an access token. Same as any Daraja API call. Send your consumer key and consumer secret to the OAuth endpoint. You get back a token that lasts one hour. Cache this token and refresh it before it expires. Do not generate a new token for every request.
Step 2: Generate your security credential. This is where B2C differs from STK Push. You need to encrypt your initiator password using Safaricom's public certificate. The certificate is different for sandbox and production. Download the correct one from the Daraja portal. Use OpenSSL or a crypto library in your language to perform RSA encryption on your initiator password, then Base64-encode the result. This encrypted string is your SecurityCredential parameter.
Step 3: Send the B2C request. POST to the B2C endpoint with these key parameters:
InitiatorName: The name of the API operator set up on the Daraja portalSecurityCredential: The encrypted password from step 2CommandID: Usually BusinessPayment for standard payouts, SalaryPayment for salary disbursements, or PromotionPayment for promotional payoutsAmount: The amount to send (minimum KES 10, maximum depends on your account limits)PartyA: Your business shortcodePartyB: The recipient phone number in 254 formatQueueTimeOutURL: Daraja calls this if the request times out in their queueResultURL: Daraja calls this with the final result (success or failure)Remarks and Occasion: Free-text fields for your referenceStep 4: Handle the synchronous response. Daraja immediately returns a response confirming it received your request. This is NOT confirmation that the money was sent. It just means Daraja accepted your request into its processing queue. You get back a ConversationID and OriginatorConversationID that you should store for tracking.
Step 5: Handle the callback. Daraja processes the payout and sends the result to your ResultURL. The callback contains the final status: success or failure, the transaction ID, and the recipient's name as registered on M-Pesa. Parse this callback, update your database, and respond with a 200 status to acknowledge receipt. If you do not respond with 200, Daraja will retry the callback.
The time between step 4 and step 5 is usually a few seconds, but it can stretch to a minute or more during peak hours. Your application needs to handle this gap gracefully. Show the user a "processing" state, not a success message, until you receive the callback.
Get weekly developer tips
Join 25,000+ developers. Practical guides, job tips, and new content — straight to your inbox.
No spam. Unsubscribe anytime.
The security credential trips up almost every developer the first time. It is an RSA-encrypted version of your initiator password, and the process is different from anything you do with STK Push. Here is exactly what to do.
Download the certificate. Go to the Daraja portal. Under "Going Live" or "Certifications," download the public certificate. There are two certificates: one for sandbox, one for production. Use the correct one for your environment. If you encrypt with the sandbox certificate and hit the production endpoint (or vice versa), you will get a cryptic error that does not mention certificates at all.
Encrypt your initiator password. The initiator password is the one you set when creating your API operator on the Daraja portal. Not your consumer secret. Not your passkey. The initiator password. Here is how to encrypt it in Node.js:
const fs = require('fs');
const crypto = require('crypto');
const certificate = fs.readFileSync('certificate.cer');
const password = 'YourInitiatorPassword';
const encrypted = crypto.publicEncrypt(
{
key: certificate,
padding: crypto.constants.RSA_PKCS1_PADDING,
},
Buffer.from(password)
);
const securityCredential = encrypted.toString('base64');
In Python, you would use the cryptography library or M2Crypto to accomplish the same thing. The important detail is RSA PKCS1 padding. Some libraries default to OAEP padding, which will produce a credential that Daraja rejects silently.
Common mistakes with the security credential:
If your B2C request returns a "Bad Request" or "Invalid Security Credential" error, check all four of those items before debugging anything else. Nine times out of ten, the problem is one of those four.
The callback is where the real work happens. Daraja sends a POST request to your ResultURL with the outcome of the payout. Your callback handler needs to be rock-solid because if it fails, you lose visibility into whether the money was actually sent.
What the callback contains: The result callback includes a ResultCode (0 for success, other values for various failures), the TransactionID (the M-Pesa receipt number), the amount, the recipient phone number, and the recipient's registered name. For successful transactions, you also get the recipient's available M-Pesa balance (though this field is not always populated).
Processing the callback:
Result.ResultParameters.ResultParameter, which is an array of key-value pairs. You need to iterate through it to extract the fields you care about.ConversationID or OriginatorConversationID from the original request.{"ResultCode": 0, "ResultDesc": "Success"}.Idempotency is not optional. If your server crashes after processing the callback but before sending the 200 response, Daraja will send the callback again. Without idempotency, you might credit a user's account twice or mark a refund as completed twice. The simplest approach: before processing any callback, check if you have already processed a callback with this ConversationID. If yes, return 200 immediately without doing anything else.
Timeout callbacks. If Daraja cannot process your request within its internal timeout, it sends a separate callback to your QueueTimeOutURL. Handle this by marking the payout as "timed out" in your database. A timeout does not mean the payout failed. It might still process. You will need to query the transaction status API to find out the final state. Never automatically retry a timed-out payout without first checking whether the original went through. That is how you accidentally send money twice.
Logging. Log the full raw callback body before you parse it. If something goes wrong with your parsing logic, you want the original payload available for debugging. Store this in a separate log table or a file, not just stdout. You will need it during production incidents.
B2C has stricter limits than C2B, and Safaricom enforces them without much warning. Understanding these limits before you hit them saves you from customer complaints and failed payouts.
Transaction limits. The minimum B2C amount is KES 10. The maximum per transaction depends on your business account type and the agreement you have with Safaricom. Standard paybill accounts typically have a per-transaction limit that Safaricom sets during your go-live process. If you need to send more than the limit in a single payout, you will need to request a limit increase through your Safaricom relationship manager.
Rate limits. Daraja enforces rate limits on API calls. The exact numbers depend on your account tier and agreement, but hitting the limit returns a throttling error. Your application should implement exponential backoff when you encounter rate limit responses. If you are doing bulk payouts (paying 500 riders at the end of the day, for example), space out your requests rather than firing them all at once. A simple queue with a configurable delay between requests works well.
Daily and monthly caps. Beyond per-transaction limits, Safaricom may set daily or monthly disbursement caps on your account. If your business scales beyond these caps, the API will start rejecting requests. Monitor your cumulative daily disbursements and alert your team when you approach the threshold, rather than discovering the cap when payouts start failing at 4 PM.
Recipient limits. Individual M-Pesa users also have daily receiving limits. If a user has already received their daily maximum through M-Pesa, your B2C payout will fail even though your account has funds and your request is valid. The callback will tell you the reason, but your UX should communicate this possibility to the user.
Float management. B2C payouts come out of your M-Pesa business float. If your float is insufficient, the payout fails. This sounds obvious, but it catches teams during high-volume periods. If your platform runs a daily payout cycle, make sure your float is loaded before the cycle starts. Automate float balance checks and alert your finance team when the balance drops below your expected daily disbursement total.
Build a monitoring dashboard that tracks: number of payouts per hour, total amount disbursed today, current float balance, failure rate, and average processing time. Without this visibility, you are flying blind.
The B2C API is the same regardless of your use case, but the application logic around it differs significantly. Here is how to think about each major pattern.
Refunds. A refund should be tied to the original transaction. Store the original payment's M-Pesa receipt number, amount, and phone number. When initiating a refund, validate that the refund amount does not exceed the original payment amount. Support partial refunds if your business model requires them. Set the CommandID to BusinessPayment and include the original transaction reference in the Remarks field for your own tracking. Important: M-Pesa does not have a native "refund" concept. A refund is just another B2C payout. Safaricom does not link it to the original transaction. The linking happens in your database.
Salary and commission payments. For batch payouts, build a queue. Create a payroll record in your database with all the recipients and amounts. Process them one by one (or in controlled batches if your rate limits allow). Track the status of each individual payout within the batch. If some fail and others succeed, you need to know exactly which ones to retry. Do not retry the entire batch. Many teams run payroll at a scheduled time (say, every Friday at 6 PM). Build in a review step where a human approves the batch before it fires. Automated payouts with no human checkpoint are a risk you do not want.
Cashback and rewards. These are usually triggered by an event in your application (user completed 10 rides, user referred a friend, user made their first purchase). The B2C call is identical, but the trigger logic sits in your business rules. A common mistake: sending cashback before confirming the triggering event is final. If a user makes a purchase and receives cashback, then reverses the purchase, you have sent money you cannot recover. Build a delay between the trigger and the payout. Wait for the triggering transaction to be fully settled before disbursing the reward.
Wallet withdrawals. If users hold balances in your app, withdrawals are B2C payouts. The key architectural decision is whether your wallet balances are backed by actual M-Pesa float or are just database numbers. If they are backed by float, you need to reconcile your aggregate wallet balances against your M-Pesa float daily. If they are just database numbers, you need enough float to cover withdrawal requests. Either way, implement withdrawal limits (daily, per-transaction) and consider adding a short processing delay for large withdrawals to give your fraud detection time to flag suspicious activity.
Before you turn on B2C payouts in production, run through every item on this list. Missing any one of these can cost you real money.
Security:
Reliability:
Business logic:
Reconciliation:
If you want to learn M-Pesa integration hands-on, with mentor support and production deployment, our M-Pesa Integration for Developers course (KES 9,999) walks you through STK Push, B2C, callbacks, and reconciliation with a real Daraja sandbox project.
KES 9,999
Master Daraja API, STK Push, C2B, B2C, and callback handling. Build a real payment flow from scratch. 10 modules, 57 lessons.
Enrol in M-Pesa Integration