Skip to Content
Pesa VoucherDeveloper Documentation

Withdrawals

Process instant B2C payouts or internal voucher credits using your float balance.


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).

Endpoint

POST /b2c

Production
https://payments.pesavoucher.com/api/v1/b2c

Sandbox
https://sandbox.payments.pesavoucher.com/api/v1/b2c

Request Example

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"
}'

Request Parameters

ParameterTypeRequiredDescription
phone_numberstringYesRecipient phone in 254XXXXXXXXX format (auto-converts 07xx / +254)
amountfloatYesExact amount with 2 decimal places (e.g., 1500.00)
command_idstringYesType of payment – see table below
remarksstringYesShort description shown to recipient (max 100 chars)
occasionstringYesReason/context of payment (e.g., "Birthday Bonus")
order_idstringNoYour internal order ID (max 500 chars recommended)

Command ID Options

Command IDRecommended Use Case
BusinessPaymentCommissions, refunds, settlements, general payouts
SalaryPaymentEmployee salaries, wages
PromotionPaymentCashbacks, rewards, referral bonuses

Amount Rules

  • Minimum: 10.00 KES
  • Maximum: 150,000.00 KES per transaction
  • Must have exactly 2 decimal places

Success Response (Initiation)

{
"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: true response 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.

Error Response

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 Codes

error_codeHTTP StatusMeaningDid the payout reach the payment gateway?
INVALID_REQUEST400Missing/invalid required field, bad command_id, or unparseable JSON bodyNo
AMOUNT_TOO_LOW400amount is below the KES 10.00 minimumNo
AUTH_MISSING_CREDENTIALS400X-API-Key / X-API-Secret headers missingNo
GATEWAY_DNS_TIMEOUT400Our server could not reach the payment gateway before the connect timeout elapsed (DNS resolution failure)No — request never left our servers
GATEWAY_UNREACHABLE400Network-level failure reaching the payment gateway (connection refused, TLS failure, other network error)No — request never reached the gateway
GATEWAY_AUTH_UNREACHABLE400Could not reach the payment gateway's authentication endpoint to obtain an access tokenNo
GATEWAY_AUTH_FAILED400Gateway authentication call succeeded but did not return a valid access tokenNo
GATEWAY_BAD_RESPONSE400The payment gateway responded, but the response body was not valid JSONAmbiguous — treat as failed, verify manually
GATEWAY_ERROR400The payment gateway accepted the connection but rejected the request (bad credentials, invalid account configuration, etc.)Request reached the gateway, was rejected
INTERNAL_ERROR400Unexpected server-side error not covered aboveUnknown — 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.

B2C Webhook Callback (Final Result)

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"
}

Important Fields in Callback

FieldDescription
statusSuccess → credit sent, Failed → retry or notify
result_code0 on success; for gateway-side failures this may be GATEWAY_UNREACHABLE, GATEWAY_DNS_TIMEOUT, or a gateway-specific result code
transaction_idOfficial receipt/reference number from the payment method used — absent when status is Failed
payment_idCorrelates this callback to the original request/response

Test Phone Numbers (Sandbox Only)

Phone NumberBehavior
254708374149Always succeeds instantly
254711223344Always fails (insufficient funds)
254722334455Simulates timeout

Rate Limits

  • 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!