Skip to Content
Pesa VoucherDeveloper Documentation

Card Payments

Accept card payments (Visa, Mastercard, and more) via a hosted checkout iframe, backed by your Pesa Voucher merchant wallet.


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

Overview

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.

Authentication

Note: unlike the other payment rails on this platform, this endpoint doesn't use X-API-KEY / X-API-SECRET headers — only Content-Type: application/json, with merchant_id passed in the request body. merchant_id must belong to an active merchant account or the request is rejected with 403.


1. Initiate Payment

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

Request Parameters

ParameterTypeRequiredDescription
merchant_idstringYesYour PesaVoucher merchant identifier
merchant_refstringYesYour internal order/reference ID (max 50 chars, alphanumeric plus - _ . :)
amountfloatYesPayment amount
currencystringNoThe paying customer's own preferred currency. Defaults to KES if omitted — always pass this specific customer's currency explicitly. See Currencies
descriptionstringYesDescription shown on the checkout page (truncated to 100 chars)
callback_urlstringYesWhere the customer's browser is sent after payment. Always supply your own — see the Payment Callback section below for why
billing_addressobjectYesMust include at least email_address or phone_number
billing_address.phone_numberstringNo*Customer phone, 254XXXXXXXXX format
billing_address.email_addressstringNo*Customer email address
billing_address.first_namestringNoCustomer first name
billing_address.last_namestringNoCustomer last name
cancellation_urlstringNoWhere the customer is sent if they cancel

* one of phone_number / email_address is required inside billing_address.

Response

{
"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.

Error Response

{
"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.


2. Check Transaction Status

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

Response

{
"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 Values

statusstatus_codeMeaning
INVALID0Order/tracking ID not recognized
COMPLETED1Payment successful — safe to credit the user
FAILED2Payment failed — allow retry
REVERSED3Payment was reversed after completing

3. Payment Callback (Browser Redirect)

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.


Advanced: Direct Processor Integration

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.


Testing

  1. Call Initiate Payment, embed the returned redirect_url in an iframe
  2. Complete a test payment using the test credentials support provides
  3. Poll Check Transaction Status with the order_tracking_id until it returns COMPLETED
  4. Test a failed/cancelled payment to confirm FAILED is handled correctly

Best Practices

  • Embed redirect_url in 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_id alongside your own merchant_ref for reconciliation
  • Handle all status values, including REVERSED
  • Never expose processor credentials if you integrate directly (see Advanced section above)