M-Pesa B2C Payouts: Paying Users From Your App
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.
What Is M-Pesa B2C and When Do You Need It?
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:
- Refunds. A customer paid for something, the order was cancelled, and you need to return their money. Doing this manually through the M-Pesa app works for five refunds a week. It does not work for fifty.
- Salary and commission payments. If your platform pays out to riders, delivery agents, freelancers, or any group of workers, B2C automates what would otherwise be a painful manual process.
- Cashback and rewards. Loyalty programs, referral bonuses, promotional credits sent directly to a user's M-Pesa. The money lands in their phone instantly, which feels much better than "points" they can never redeem.
- Withdrawals. If your app holds user funds (a wallet, a savings feature, a marketplace escrow), B2C is how users pull their money out.
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 API Flow, Step by Step
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: UsuallyBusinessPaymentfor standard payouts,SalaryPaymentfor salary disbursements, orPromotionPaymentfor 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)RemarksandOccasion: Free-text fields for your reference
Step 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.
Generating the Security Credential
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:
- Using the wrong certificate (sandbox cert for production or the other way around)
- Using the wrong password (consumer secret instead of initiator password)
- Wrong padding scheme (OAEP instead of PKCS1)
- Regenerating the credential on every request instead of caching it. The credential does not expire with the OAuth token. Generate it once and reuse it until your initiator password changes
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.
Handling B2C Callbacks Properly
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:
- Parse the JSON body. Daraja nests the useful data inside
Result.ResultParameters.ResultParameter, which is an array of key-value pairs. You need to iterate through it to extract the fields you care about. - Find the matching payout record in your database using the
ConversationIDorOriginatorConversationIDfrom the original request. - Update the record with the result: status (success/failed), M-Pesa receipt number, timestamp, and any other relevant fields.
- If the payout was for a refund or withdrawal, update the user's balance or order status accordingly.
- Respond with HTTP 200 and a simple JSON body like
{"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.
Rate Limits, Transaction Limits, and Safaricom Thresholds
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.
Implementation Patterns for Common Use Cases
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.
B2C Production Checklist
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:
- IP whitelist your production server with Safaricom. B2C requests from non-whitelisted IPs will be rejected
- Store your initiator password and security credential in environment variables or a secrets manager, never in your codebase
- Use the production certificate, not the sandbox certificate, for generating your security credential
- Enable HTTPS on your callback URLs. Daraja sends sensitive transaction data to these endpoints
- Validate that incoming callbacks are actually from Safaricom. Check the source IP against Safaricom's known IP ranges
Reliability:
- Implement idempotency on your callback handler. Duplicate callbacks should not cause duplicate database updates
- Add a dead letter queue for failed callback processing. If your handler throws an exception, the raw callback body should be preserved for manual review
- Set up monitoring alerts for payout failure rates above your acceptable threshold
- Build a manual retry mechanism for failed payouts that lets your support team re-trigger specific payouts
- Test your timeout handler. Simulate a scenario where Daraja sends a timeout callback and verify your app responds correctly
Business logic:
- Set maximum payout amounts in your application code, independent of Safaricom's limits. Your app's limit should always be lower than Safaricom's
- Require human approval for payouts above a certain threshold
- Implement daily disbursement caps that pause payouts if the total exceeds your expected range
- Log every payout attempt, including who or what triggered it, the amount, the recipient, and the outcome
Reconciliation:
- Schedule daily reconciliation between your payout records and your M-Pesa statement
- Flag any payouts in your database that are marked "pending" for more than 30 minutes. These need manual investigation
- Track your float balance programmatically and alert when it drops below your daily payout average
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.
Key Takeaways
- ✓B2C is for sending money out. Your business shortcode pushes funds to a user phone number. This is the reverse of STK Push, which pulls money in.
- ✓Security requirements are stricter than C2B. You need a security credential (encrypted initiator password), IP whitelisting, and careful rate limit management.
- ✓Always implement idempotency. If your callback handler crashes mid-processing, Daraja will retry. Without idempotency keys, you risk sending duplicate payouts.
- ✓Test in sandbox first, but understand that sandbox behavior does not perfectly match production. Budget time for production-specific debugging.
- ✓Keep a detailed audit trail. Every payout request, callback response, and status change should be logged with timestamps. This protects you during disputes.
Frequently Asked Questions
- What is the difference between B2C and C2B in M-Pesa?
- C2B (Customer to Business) is when a customer sends money to your business, like paying for an order via STK Push or paybill. B2C (Business to Customer) is the reverse: your business sends money to a customer phone number. C2B pulls money in. B2C pushes money out. The API flows are similar (request, callback), but B2C has stricter security requirements because you are moving money out of your account.
- Can I test B2C payouts in the Daraja sandbox?
- Yes. The Daraja sandbox supports B2C testing with test credentials and phone numbers. However, sandbox behavior does not perfectly mirror production. Callbacks arrive faster in sandbox, error codes may differ, and rate limits are more lenient. Test your flow in sandbox to confirm the logic works, but budget additional time for production-specific debugging. No real money moves in sandbox.
- What happens if a B2C payout fails?
- If a payout fails, Daraja sends a callback to your ResultURL with a non-zero ResultCode and a description of the failure reason. Common reasons include insufficient float, invalid recipient phone number, recipient daily limit exceeded, and system timeout. Your application should parse the failure reason, update the payout record in your database, and decide whether to retry automatically or flag it for manual review. Never auto-retry without first understanding why the payout failed.
- How long does a B2C payout take to reach the recipient?
- In most cases, the recipient receives the money within a few seconds of the successful callback. During peak hours or M-Pesa system maintenance, it can take up to a few minutes. If the recipient has not received the funds after 5 minutes despite a successful callback, check the M-Pesa transaction status API. Occasionally, Daraja reports success but the actual credit to the recipient is delayed on their side.
- Is there a minimum and maximum amount for B2C payouts?
- The minimum B2C amount is KES 10. The maximum depends on your business account type and the limits Safaricom set during your go-live process. Standard limits can be increased by contacting your Safaricom relationship manager and providing documentation about your expected payout volumes. Individual recipients also have daily receiving limits that can cause payouts to fail even if your account limits are fine.
- Do I need a different shortcode for B2C vs C2B?
- Not necessarily. The same paybill shortcode can be used for both C2B (receiving payments) and B2C (sending payouts), as long as B2C is enabled on the shortcode. However, some businesses use separate shortcodes for receiving and disbursing to simplify reconciliation and accounting. Check with Safaricom during your go-live process to confirm B2C is activated on your shortcode.
Ready to build real-world apps?
Join the McTaba Labs full-stack marathon (4 months full-time · 6 months part-time). Learn M-Pesa, USSD, and WhatsApp engineering while shipping 8 production apps.
Apply to the McTaba Marathon