Withdrawals – B2C Payments (Send Money to Customer)
Use the B2C endpoint to disburse funds from your business account directly to a customer’s mobile money wallet (e.g., commissions, refunds, salaries, cashbacks, bonuses).
POST /b2c
Productionhttps://payments.pesavoucher.com/api/v1/b2c
Sandboxhttps://sandbox.payments.pesavoucher.com/api/v1/b2c
curl -X POST https://payments.pesavoucher.com/api/v1/b2c \-H "Content-Type: application/json" \-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxx" \-H "X-API-Secret: sk_live_xxxxxxxxxxxxxxxx" \-d '{"phone_number": "254708374149","amount": 2500.00,"command_id": "BusinessPayment","remarks": "November affiliate commission","occasion": "Monthly payout - Nov 2025","order_id": "sample_12345678"}'
| Parameter | Type | Required | Description |
|---|---|---|---|
phone_number | string | Yes | Recipient phone in 254XXXXXXXXX format (auto-converts 07xx / +254) |
amount | float | Yes | Exact amount with 2 decimal places (e.g., 1500.00) |
command_id | string | Yes | Type of payment – see table below |
remarks | string | Yes | Short description shown to recipient (max 100 chars) |
occasion | string | Yes | Reason/context of payment (e.g., "Birthday Bonus") |
order_id | string | No | Your internal order ID (max 500 chars recommended) |
| Command ID | Recommended Use Case |
|---|---|
BusinessPayment | Commissions, refunds, settlements, general payouts |
SalaryPayment | Employee salaries, wages |
PromotionPayment | Cashbacks, rewards, referral bonuses |
- Minimum:
10.00KES - Maximum:
150,000.00KES per transaction - Must have exactly 2 decimal places
{"success": true,"message": "B2C payment initiated successfully","data": {"payment_id": "550e8400-e29b-41d4-a716-446655440001","order_id": "sample_12345678","conversation_id": "AG_20251120_000123456789","originator_conversation_id": "OC_20251120_987654321","amount": 2500.00,"recipient_phone": "254708374149","status": "Processing"}}
A
200/success: trueresponse only means the request was accepted for processing — it is not a guarantee the payout succeeded. The final result (Success,Failed, etc.) is delivered to your webhook.
All errors return a non-200 HTTP status with a stable error_code you can branch on programmatically, plus a human-readable error string for logs/support tickets.
{"success": false,"error_code": "GATEWAY_DNS_TIMEOUT","error": "B2C cURL error: Resolving timed out after 10001 milliseconds","payment_id": "550e8400-e29b-41d4-a716-446655440001","timestamp": "2026-07-04 14:39:12"}
payment_id is included whenever a payment record was already created before the failure occurred — use it to correlate a failed call with a specific row/webhook, or to reference the transaction when contacting support.
error_code | HTTP Status | Meaning | Did the payout reach the payment gateway? |
|---|---|---|---|
INVALID_REQUEST | 400 | Missing/invalid required field, bad command_id, or unparseable JSON body | No |
AMOUNT_TOO_LOW | 400 | amount is below the KES 10.00 minimum | No |
AUTH_MISSING_CREDENTIALS | 400 | X-API-Key / X-API-Secret headers missing | No |
GATEWAY_DNS_TIMEOUT | 400 | Our server could not reach the payment gateway before the connect timeout elapsed (DNS resolution failure) | No — request never left our servers |
GATEWAY_UNREACHABLE | 400 | Network-level failure reaching the payment gateway (connection refused, TLS failure, other network error) | No — request never reached the gateway |
GATEWAY_AUTH_UNREACHABLE | 400 | Could not reach the payment gateway's authentication endpoint to obtain an access token | No |
GATEWAY_AUTH_FAILED | 400 | Gateway authentication call succeeded but did not return a valid access token | No |
GATEWAY_BAD_RESPONSE | 400 | The payment gateway responded, but the response body was not valid JSON | Ambiguous — treat as failed, verify manually |
GATEWAY_ERROR | 400 | The payment gateway accepted the connection but rejected the request (bad credentials, invalid account configuration, etc.) | Request reached the gateway, was rejected |
INTERNAL_ERROR | 400 | Unexpected server-side error not covered above | Unknown — contact support with payment_id |
For GATEWAY_DNS_TIMEOUT and GATEWAY_UNREACHABLE specifically: these mean the failure happened before the request reached the payment gateway, so no transaction was ever created and no funds were disbursed. The associated payment record is marked Failed automatically — no manual reconciliation needed on your end.
Sent once the payout is confirmed one way or the other by the payment gateway, or immediately if it never reached the gateway at all (see GATEWAY_DNS_TIMEOUT / GATEWAY_UNREACHABLE above).
{"payment_id": "550e8400-e29b-41d4-a716-446655440001","conversation_id": "AG_20251120_000123456789","originator_conversation_id": "OC_20251120_987654321","transaction_type": "b2c","status": "Success","result_code": "0","result_description": "The service request is processed successfully.","transaction_id": "RKJ3M9P2XQ","amount": 2500.00,"recipient_phone": "254708374149","recipient_name": "Jane Doe","command_id": "BusinessPayment","transaction_date": "2025-11-20 14:30:45","order_id": "sample_12345678","timestamp": "2025-11-20 14:30:50"}
| Field | Description |
|---|---|
status | Success → credit sent, Failed → retry or notify |
result_code | 0 on success; for gateway-side failures this may be GATEWAY_UNREACHABLE, GATEWAY_DNS_TIMEOUT, or a gateway-specific result code |
transaction_id | Official receipt/reference number from the payment method used — absent when status is Failed |
payment_id | Correlates this callback to the original request/response |
| Phone Number | Behavior |
|---|---|
254708374149 | Always succeeds instantly |
254711223344 | Always fails (insufficient funds) |
254722334455 | Simulates timeout |
- Default: 60 B2C requests per minute
- Burst up to 100/min
- Contact support for higher limits
You're now ready to send payments to your customers!