B2C
By the end of this track you can send money without paying twice.
0/3 finished
Step 1
The idea
B2C sends money from a business to a phone. It needs an initiator (an API user) and a SecurityCredential: the initiator password encrypted with Safaricom's certificate. Results come to a ResultURL.
Step 2
How it works
Who is who
B2C sends money from your shortcode to a phone: salaries, refunds, winnings. An initiator is an API user created on the M-Pesa org portal with the ORG B2C API Initiator role. Daraja needs its password encrypted, as the SecurityCredential. Payouts come from the shortcode's utility account, which must have funds.
1. Make the SecurityCredential
Encrypt the initiator password with Safaricom's public certificate (RSA, PKCS#1 v1.5), then base64 it. The sandbox and production certificates differ.
printf "%s" "$INITIATOR_PASSWORD" \ | openssl pkeyutl -encrypt -certin -inkey SandboxCertificate.cer -pkeyopt rsa_padding_mode:pkcs1 \ | base64 -w02. Send the payment
POST to /mpesa/b2c/v3/paymentrequest. CommandID is BusinessPayment, SalaryPayment or PromotionPayment. OriginatorConversationID is yours: generate it once per payout and save it before you call Daraja. Daraja rejects a duplicate (15 or 500.002.1001). Note Daraja's spelling of Occassion; it and Remarks take 1–100 characters. The minimum amount is about 10 KES.
{ "OriginatorConversationID": "payout-1042", "InitiatorName": "testapi", "SecurityCredential": "<encrypted password>", "CommandID": "BusinessPayment", "Amount": 10, "PartyA": "600996", "PartyB": "2547XXXXXXXX", "Remarks": "Refund order 1042", "QueueTimeOutURL": "https://your-domain.example/b2c/timeout", "ResultURL": "https://your-domain.example/b2c/result", "Occassion": "Refund"}3. Two answers again
First an acknowledgement with a ConversationID. Later the result arrives at your ResultURL: ResultCode 0 with a TransactionID means the money left your account.
{ "Result": { "ResultType": 0, "ResultCode": 0, "ResultDesc": "The service request is processed successfully.", "OriginatorConversationID": "payout-1042", "ConversationID": "AG_20261029_2010XXXXXXXX", "TransactionID": "TJT5XXXXXX" }}The kit's guardrails
The MCP server refuses to move live money unless DARAJA_ALLOW_LIVE_MONEY_MOVEMENT=true. DARAJA_ALLOWED_RECIPIENTS limits who can be paid, DARAJA_APPROVAL_THRESHOLD asks a person to approve larger payouts, and DARAJA_READ_ONLY removes the money tools entirely. Its B2C tool also requires an idempotency_key.
Step 3
Try it
$ mpesa pay b2c 2547XXXXXXXX 10
With the MCP server, ask your agent to use mpesa_b2c_payment
Step 4
Read the response
ConversationID and OriginatorConversationID now, and the final status later.
Step 5
Common mistakes
- Encrypting with the production certificate for the sandbox, or the reverse.
- Not saving the
OriginatorConversationIDbefore the call, so a timeout leaves nothing to query by. - Treating the acknowledgement as a completed payout.
- Reading a
QueueTimeOutURLhit as failed. It means unknown.
Go deeper
Why is B2C more dangerous than STK Push?
Answer in your own words first.
Show the answerHide the answer
Money leaves your account, so a mistake pays someone.