Card Payments API
Live Base URL: https://payments.pesavoucher.com/api/pesapal/
No separate sandbox base URL is documented yet for these endpoints — contact support@pesavoucher.com for sandbox credentials before going live.
Live Testing Note To perform real live transactions or trigger a live test webhook, please contact us: support@pesavoucher.com Telegram: Join our channel → t.me/Dev_PesaVoucher
Accept card payments — Visa, Mastercard, and other major cards, plus mobile money and bank options depending on what's enabled on your account — through a hosted checkout iframe you embed on your site. PesaVoucher handles the underlying payment processor connection and order submission internally; you only need to call the endpoints below.
Note: unlike the other payment rails on this platform, this endpoint doesn't use
X-API-KEY/X-API-SECRETheaders — onlyContent-Type: application/json, withmerchant_idpassed in the request body.merchant_idmust belong to an active merchant account or the request is rejected with403.
Endpoint: POST /submit_order.php
Starts a card payment session and returns an iframe URL for the customer to complete payment.
curl -X POST https://payments.pesavoucher.com/api/pesapal/submit_order.php \-H "Content-Type: application/json" \-d '{"merchant_id": "b98f8d4f-532e-44ec-a282-91902cdbbe13","merchant_ref": "ORD-2026-0417","amount": 1500.00,"currency": "KES","description": "Order payment","callback_url": "https://yourshop.com/payment/complete","billing_address": {"phone_number": "254712345678","email_address": "customer@example.com","first_name": "Jane","last_name": "Doe"}}'
| Parameter | Type | Required | Description |
|---|---|---|---|
merchant_id | string | Yes | Your PesaVoucher merchant identifier |
merchant_ref | string | Yes | Your internal order/reference ID (max 50 chars, alphanumeric plus - _ . :) |
amount | float | Yes | Payment amount |
currency | string | No | The paying customer's own preferred currency. Defaults to KES if omitted — always pass this specific customer's currency explicitly. See Currencies |
description | string | Yes | Description shown on the checkout page (truncated to 100 chars) |
callback_url | string | Yes | Where the customer's browser is sent after payment. Always supply your own — see the Payment Callback section below for why |
billing_address | object | Yes | Must include at least email_address or phone_number |
billing_address.phone_number | string | No* | Customer phone, 254XXXXXXXXX format |
billing_address.email_address | string | No* | Customer email address |
billing_address.first_name | string | No | Customer first name |
billing_address.last_name | string | No | Customer last name |
cancellation_url | string | No | Where the customer is sent if they cancel |
* one of phone_number / email_address is required inside billing_address.
{"success": true,"order_tracking_id": "9c5add4c-1234-5678-9abc-def012345678","merchant_reference": "ORD-2026-0417","redirect_url": "https://pay.example-processor.com/iframe/checkout?token=9c5add4c-1234-5678-9abc-def012345678"}
Next step: Embed redirect_url in an iframe on your checkout page. The customer completes payment (card, mobile money, or bank, depending on what's enabled) inside the iframe.
{"success": false,"message": "Invalid or inactive merchant"}
Returned with 400 for missing/invalid fields, 403 for an unknown/inactive merchant_id, and 500 for a processor-side failure.
Endpoint: POST /transaction_status.php
Server-to-server merchant notification for this rail isn't wired up yet, so polling this endpoint is currently your way of confirming a payment completed — don't rely on the browser callback alone.
curl -X POST https://payments.pesavoucher.com/api/pesapal/transaction_status.php \-H "Content-Type: application/json" \-d '{ "order_tracking_id": "9c5add4c-1234-5678-9abc-def012345678" }'
{"success": true,"order_tracking_id": "9c5add4c-1234-5678-9abc-def012345678","merchant_reference": "ORD-2026-0417","status": "COMPLETED","status_code": 1,"amount": 1500.00,"currency": "KES","payment_method": "Visa","confirmation_code": "AB12CD34"}
status | status_code | Meaning |
|---|---|---|
INVALID | 0 | Order/tracking ID not recognized |
COMPLETED | 1 | Payment successful — safe to credit the user |
FAILED | 2 | Payment failed — allow retry |
REVERSED | 3 | Payment was reversed after completing |
Your callback_url is where the customer's browser is sent after they finish paying — you must supply your own, pointing back to your own site. PesaVoucher's default handler redirects to PesaVoucher's own pages, not back to you, so if callback_url is omitted the customer never returns to your checkout.
Treat this as UX only, not your source of truth. The customer can close the browser before the redirect fires, or the redirect can be interrupted. Always confirm the final status with Check Transaction Status above before crediting anything.
Some merchants prefer to integrate directly with the underlying payment processor using their own merchant account, rather than going through PesaVoucher's wrapper endpoint above. This is available on request — contact support@pesavoucher.com and we'll share the direct integration reference.
- Call Initiate Payment, embed the returned
redirect_urlin an iframe - Complete a test payment using the test credentials support provides
- Poll Check Transaction Status with the
order_tracking_iduntil it returnsCOMPLETED - Test a failed/cancelled payment to confirm
FAILEDis handled correctly
- Embed
redirect_urlin an iframe rather than opening it in a new tab — this matches the checkout's intended UX - Always supply your own
callback_url— PesaVoucher's default handler doesn't return the customer to your site - Poll Check Transaction Status as your source of truth until webhook delivery for this rail is available; the browser callback is UX only
- Store
order_tracking_idalongside your ownmerchant_reffor reconciliation - Handle all status values, including
REVERSED - Never expose processor credentials if you integrate directly (see Advanced section above)