Skip to Content
Pesa VoucherDeveloper Documentation


PesaVoucher Paybill Integration Guide

This document explains what a merchant should expect when a customer pays via the PesaVoucher paybill toolkit, and what payload will be sent to the merchant's callback URL once a transaction resolves.

1. Why Paybill Matters — Fallback When the M-Pesa Gateway Is Down

STK Push (the "prompt on your phone" flow) depends on Safaricom's push gateway, which can occasionally be slow or unavailable. The paybill toolkit itself is a separate, always-on channel — it does not depend on the STK push gateway being healthy. Because of this, paybill should be treated as the reliable fallback deposit method: even during periods where STK Push initiation is failing or degraded, customers can still pay directly via paybill and the transaction will be picked up normally.

This also means merchants should surface the paybill number and account number to customers as a standing deposit option, not just as a backup shown only during outages.

2. Deposits From Other Banking Apps

Customers are not limited to M-Pesa. Any banking app or platform that supports paying a Safaricom paybill (bank apps, other mobile money apps, etc.) can be used to deposit, by entering:

  • Paybill/Business number: PesaVoucher's paybill number
  • Account number: the customer's player ID

The account number field is how the deposit gets matched to the correct player — the customer must enter their player ID as the account number for the deposit to be attributed correctly. This is true regardless of which app or bank they're paying from.

3. Overview of the Flow

  1. A customer initiates a payment either via STK Push (prompt on their phone) or by paying directly through the paybill number with their player ID as the account number.
  2. For STK Push: Safaricom sends an asynchronous callback to PesaVoucher once the transaction completes (success, failure, or cancellation) — this can take anywhere from a few seconds to a couple of minutes after the prompt is shown.
  3. For direct paybill payments: Safaricom notifies PesaVoucher of the C2B payment as soon as it clears, matched against the account number/player ID entered.
  4. PesaVoucher processes the result server-side (updates the payment record, moves merchant balances, records commission) and then forwards a normalized notification to the merchant's callback_url.

The merchant does not talk to Safaricom directly — everything needed is in the forwarded callback described below.

4. What Triggers a Callback to the Merchant

A callback is sent to the merchant's registered URL exactly once per payment, as soon as PesaVoucher has processed Safaricom's result. This happens server-side, independent of whether the customer is still on any checkout page — so the merchant will be notified even if the customer closes their browser immediately after paying.

5. Callback Payload

PesaVoucher sends an HTTP POST with Content-Type: application/json and header X-Callback-Type: STK-Push to the merchant's callback_url. Example payload:

{
"payment_id": "b0e2f7c1-....",
"order_id": "ORDER-12345",
"merchant_request_id": "29115-34620561-1",
"checkout_request_id": "ws_CO_191220191020363925",
"status": "Success",
"result_code": "0",
"result_description": "The service request is processed successfully.",
"initial_amount": 500,
"actual_amount": 500,
"mpesa_receipt_number": "NLJ7RT61SV",
"transaction_date": "2026-07-15 17:20:31",
"phone_number": "2547XXXXXXXX",
"payer_phone": "2547XXXXXXXX",
"payer_name": "JOHN",
"account_reference": "ORDER-12345",
"voucher_code": null,
"timestamp": "2026-07-15 17:20:35"
}

Direct paybill deposits use the same callback structure, since Safaricom notifies PesaVoucher of these C2B payments the same way it does STK results. The one difference: because the customer initiated the payment themselves (rather than the merchant initiating an STK push against a known order), there is no order_id field — PesaVoucher has no order to attach the payment to. The account_reference field carries the account number the customer entered, which is the player ID, and that's what the merchant should use to attribute the deposit. Example:

{
"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",
"timestamp": "2025-11-20 14:32:50"
}

Field reference

FieldDescription
payment_idPesaVoucher's internal payment record ID — use this for reconciliation/support queries.
order_idThe order reference the merchant supplied when initiating an STK push. Not present on direct paybill deposits, since those are customer-initiated with no order for PesaVoucher to attach — use account_reference (the player ID) instead to attribute those.
merchant_request_id / checkout_request_idSafaricom's own identifiers for the push.
statusOne of Success, Failed, Cancelled (see status table below).
result_code / result_descriptionRaw Safaricom result code and human-readable description.
initial_amountAmount requested at push initiation (STK) or amount paid (direct paybill).
actual_amountAmount actually confirmed paid by Safaricom — merchants should treat this as the source of truth.
mpesa_receipt_numberSafaricom's M-Pesa receipt code — only present on Success.
transaction_dateTimestamp Safaricom reports the transaction completed.
phone_number / payer_phonePhone number the push was sent to / the number that paid.
payer_nameName on the M-Pesa account, if returned by Safaricom.
voucher_codeOnly populated if the payment was made through PesaVoucher's Hosted Checkout flow and a voucher was issued. For direct paybill/STK merchant integrations, this will normally be null.
timestampTime PesaVoucher sent this callback (not the transaction time).

Status values

statusresult_codeMeaning
Success0Payment completed — funds confirmed, safe to credit the player.
Cancelled1032Customer cancelled the STK prompt or didn't respond in time (not applicable to direct paybill deposits).
Failed1, 1037, 2001, or any other non-zero codePayment did not go through — do not credit the player.

6. What the Merchant Should Do on Receipt

  • Verify payment_id and order_id/player ID match a pending or valid player account before crediting anything.
  • Only act on status: "Success". Treat Failed and Cancelled as no-ops other than updating the transaction's status.
  • Respond with a 2xx HTTP status promptly so PesaVoucher doesn't log the delivery as failed. PesaVoucher does not currently guarantee automatic retries on non-2xx responses, so a dropped/500 response on the merchant's side can result in the notification being lost — reconciliation against payment_id via PesaVoucher support is the fallback if that happens.
  • Idempotency: it is possible (though rare) for a callback to be delivered more than once for the same payment_id. The merchant's handler should be safe to receive the same payment_id twice without double-crediting.
  • Educate customers on the account number requirement: since direct paybill deposits rely entirely on the account number to identify the player, merchants should make it very clear in their UI/support materials that the player ID must be entered exactly as the account number — a wrong or missing player ID means the deposit can't be automatically attributed.

7. Timing Expectations

  • STK prompts typically resolve within 5–60 seconds of being shown to the customer.
  • Direct paybill payments (via M-Pesa or other banking apps) are typically confirmed within seconds of the transaction clearing on the payer's side.
  • Safaricom can occasionally take longer or time out on STK; if no callback is received within ~2 minutes, the merchant can query payment status via PesaVoucher's status endpoint rather than assuming failure.

8. Support / Reconciliation

For any transaction dispute or missing callback, provide PesaVoucher with the payment_id (preferred) or checkout_request_id/account number used — these map directly to the authoritative payment record and callback log on PesaVoucher's side.