Skip to Content
Pesa VoucherDeveloper Documentation

Deposits

Accept instant user deposits via M-Pesa Direct payments (STK Push), Cross-Border Mobile Money (Uganda & Tanzania), or Voucher Redemption using the Prepaid Float.


STK Push (Accept Payments from Customers)

Trigger an instant M-Pesa payment prompt on your customer's phone. The customer enters their PIN, and funds are transferred directly to our account for float updates.

Endpoint

POST /stk_push

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

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

Request Example

curl -X POST https://payments.pesavoucher.com/api/v1/stk_push \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-API-Secret: sk_live_xxxxxxxxxxxxxxxxxxxx" \
-d '{
"phone_number": "254708374149",
"amount": 1250.00,
"account_reference": "INV-2025-0891",
"order_id": sample_12345678",
"transaction_desc": "Premium Plan – Annual Subscription",
"metadata": {
"customer_id": "CUST-8871",
"plan": "premium-annual"
}
}'

Request Parameters

ParameterTypeRequiredDescription
phone_numberstringYesCustomer phone in 254XXXXXXXXX format (auto-converts 07xx / +254)
amountfloatYesExact amount with 2 decimal places
account_referencestringYesYour internal invoice (max 12 chars recommended)
order_idstringNoYour internal order ID (max 500 chars recommended)
transaction_descstringYesMessage shown to customer on their phone
metadataobjectNoAny custom data you want attached to the transaction

Amount Rules

RuleValue
Minimum amount10.00 KES
Maximum per transaction150,000.00 KES
Decimal placesExactly 2
Examples250.00, 1099.50

Phone Number Handling

Automatically accepted & converted:

  • 254708374149 → recommended
  • 0708374149
  • +254708374149
  • 0718 374 149 (spaces ignored)

Invalid formats will return an error.

Success Response (Push Initiated)

{
"success": true,
"message": "STK push initiated successfully",
"data": {
"payment_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_request_id": "MERCHANT-20251120-001",
"checkout_request_id": "ws_CO_20112025143022123",
"customer_message": "Please check your phone and enter your PIN to complete payment",
"amount": 1250.00,
"phone_number": "254708374149",
"order_id": "sample_12345678"
}
}

The final result is delivered via your webhook.

STK Push Webhook Callback (Final Result)

{
"payment_id": "550e8400-e29b-41d4-a716-446655440000",
"merchant_request_id": "MERCHANT-20251120-001",
"checkout_request_id": "ws_CO_20112025143022123",
"status": "Success",
"result_code": "0",
"result_description": "The service request is processed successfully.",
"initial_amount": 1250.00,
"actual_amount": 1250.00,
"mpesa_receipt_number": "SKL9P2M4XQ",
"transaction_date": "20251120143245",
"phone_number": "254708374149",
"payer_phone": "254708374149",
"payer_name": "JOHN DOE",
"account_reference": "INV-2025-0891",
"order_id": "sample_12345678",
"timestamp": "2025-11-20 14:32:50"
}

Status Values & Actions

StatusMeaningRecommended Action
SuccessPayment completedCredit user account / fulfill order
FailedPayment rejectedShow error, allow retry
CancelledUser pressed CancelAllow retry
TimeoutUser didn't respond in timeAllow retry (common result_code: 1037)

Common Result Codes

CodeDescription
0Success
1Insufficient balance
1032Cancelled by user
1037Timeout – no response
2001Invalid credentials / account issue

Sandbox Test Phone Numbers

Phone NumberBehavior
254708374149Always succeeds instantly
254711223344Always fails (insufficient funds)
254722334455User cancels
254733445566Timeout (no response)

Rate Limits

  • 60 requests per minute (default)
  • Burst up to 100/min
  • Higher limits available on request

You're now ready to accept real-time mobile money payments though MPESA from customers!


XB STK Push (Cross-Border — Uganda & Tanzania)

Trigger an instant mobile money payment prompt for customers in Uganda (UGX) or Tanzania (TZS), powered by IntaSend's cross-border network. Same integration shape as the KES STK Push above — a customer confirms on their phone, and the final result is delivered to your webhook — but as a separate endpoint, since currency/country here is something you specify explicitly rather than something we can infer.

If you're on the KES route today and expanding into Uganda or Tanzania, this is an additive integration: keep calling /stk_push for KES traffic, and call /xb/stk_push for UGX/TZS traffic. The two are not interchangeable — sending a UGX request to /stk_push (or a KES request to /xb/stk_push) will be rejected.

Endpoint

POST /xb/stk_push

Production https://payments.pesavoucher.com/api/v1/xb/stk_push

Request Example

curl -X POST https://payments.pesavoucher.com/api/v1/xb/stk_push \
-H "Content-Type: application/json" \
-H "X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxx" \
-H "X-API-Secret: sk_live_xxxxxxxxxxxxxxxxxxxx" \
-d '{
"currency": "UGX",
"phone_number": "256700123456",
"amount": 25000,
"api_ref": "INV-2025-0891",
"country": "UG"
}'

Request Parameters

ParameterTypeRequiredDescription
currencystringYesUGX or TZS. No other values are accepted on this endpoint.
phone_numberstringYesCustomer phone, including country code (e.g. 256700123456, 255700123456).
amountnumberYesWhole-number amount in the smallest sensible unit for the currency — see Amount Rules below.
api_refstringNoYour internal reference for reconciliation. If omitted, one is generated for you and returned in the response — save it, since it's the only way to match this request to its eventual webhook.
countrystringNoISO country code (UG or TZ). Defaults from currency if omitted (UGXUG, TZSTZ) — only needed if you want to be explicit.

Unlike /stk_push, this endpoint does not accept order_id, transaction_desc, or metadata today — those fields aren't yet part of the cross-border integration. If you need them, use api_ref to carry your own reconciliation string and let us know if metadata support is something you need — it's a straightforward addition.

Amount Rules

CurrencyMinimumMaximum
UGX5005,000,000
TZS5005,000,000

These are conservative starting bounds and may be tuned as real transaction volume comes in. Unlike KES, amounts here are whole numbers, not fixed to 2 decimal places.

Phone Number Handling

Send the phone number with its country code, digits only — e.g. 256700123456 for Uganda, 255700123456 for Tanzania. Unlike the KES endpoint, this one does not currently auto-normalize local formats (0700123456, +256 700 123 456, etc.) — send the full international-format digit string. We validate that the number is a plausible length (9–13 digits) on our side; final acceptance/rejection of the specific number is determined by IntaSend's own network.

Success Response (Push Initiated)

{
"success": true,
"data": {
"invoice_id": "9f2c1e6a-...",
"state": "PENDING",
"api_ref": "INV-2025-0891"
}
}

The final result is delivered via your webhook — see below. invoice_id is your reference for that eventual callback if you didn't supply your own api_ref.

XB Webhook Callback (Final Result)

{
"id": "b7e4a210-...",
"invoice": {
"invoice_id": "9f2c1e6a-...",
"state": "COMPLETE",
"value": "25000",
"api_ref": "INV-2025-0891",
"currency": "UGX",
"mpesa_reference": "...",
"charges": "...",
"net_amount": "..."
}
}

Note: confirm this shape against a real IntaSend sandbox/production callback before relying on it in a client integration — this reflects the nested invoice structure our own callback handler parses today, but hasn't been independently re-verified against IntaSend's current webhook documentation for this doc page specifically.

Status Values & Actions

StateMeaningRecommended Action
COMPLETEPayment completedCredit user account / fulfill order
FAILEDPayment rejectedShow error, allow retry
CANCELLEDUser cancelledAllow retry
FAILED_VALIDATENumber/account failed validationShow error, allow retry with a corrected number
PROCESSINGStill in flight — no action needed yetKeep waiting; another callback follows

Sandbox Test Numbers

Not yet documented for this endpoint. IntaSend's cross-border sandbox likely has its own set of test numbers with fixed simulated outcomes (mirroring how Safaricom's KES sandbox works above) — check IntaSend's own developer documentation for their current sandbox test numbers rather than assuming the KES ones above apply; they don't.

Rate Limits

Not yet independently tuned for this endpoint — currently shares the same limits as /stk_push (60/min default, burst to 100/min). Let us know if your integration needs different limits for cross-border traffic specifically.