Collection API
Charge a customer on any channel through one endpoint.
Authentication
Every request is authenticated with your secret key in the apiToken header. There is one base URL and one key: your account behaves as a sandbox until go-live is approved, on the same URL. Keep the key server-side only, and note that rotating it expires the previous one immediately.
Required Headers
apiToken
sp_sk_prod_XXXXXXXXXXXX
Content-Type
application/json
Overview
Mobile money, bank accounts, TANQR and till / Lipa Numbers all go through POST /api/v2/payment/collection. You describe who is paying with service + account, and the channel is routed for you: provider is optional for mobile because the network is detected from the number. The call returns immediately with PROCESSING and the final outcome arrives on your webhook.
- Step 1 — Collection — POST /api/v2/payment/collection with service + account + amount. You describe who is paying, not how the plumbing works — the channel is routed for you. It returns immediately with status PROCESSING.
- Step 2 — Webhook — When the payment reaches a final state we POST payment.confirmed / payment.failed to your URL. Verify the X-SpeedPesa-Signature header before you trust it.
- Step 3 — Reconcile — If your webhook was unreachable, poll GET /api/v1/payment/reference/{reference} — no more than once every 10 seconds and stop after 5 minutes.
Collection (Unified)
Charges a customer on any channel through one endpoint. Mobile money, bank accounts, TANQR and till / Lipa Numbers all go through here. Returns immediately with status PROCESSING — the customer has not paid yet, and the final outcome arrives on your webhook.
Use zonmpay.com specifically (without www the request is redirected and some HTTP clients drop the POST body — which causes a server error).
Request Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| amount | integer | Yes | Amount to collect in TZS. Minimum TZS 100.example: 50000 |
| service | string | Yes | How the payer is identified: mobile, account, tanqr or merchant.example: mobile |
| account | string | Yes | The identifier itself: phone number, bank account number, or till id. 0712345678 and 255712345678 are both accepted.example: 255712345678 |
| provider | string | No | The channel, e.g. VODACOM, TIGOPESA, AIRTELMONEY, HALOPESA, TTCL, CRDB. Optional for service: mobile because the network is detected from the number; required for everything else.example: VODACOM |
| reference | string | No | Your own order id. Repeating the same reference returns the existing payment instead of charging again.example: ORDER-001 |
| description | string | No | Shown to the customer and on your statement.example: Payment for Order #001 |
| amountType | string | No | FULL requires the exact amount, FIXED accepts a single payment of a set amount, FLEXIBLE accepts part payments until the total is met.example: FULL |
| push | boolean | No | Set false to create the payment without prompting the customer immediately (collect later via a payment link).example: true |
curl -X POST https://zonmpay.com/api/v2/payment/collection \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"reference": "ORDER-001",
"description": "Payment for Order #001",
"amount": 50000,
"service": "mobile",
"account": "255712345678",
"amountType": "FULL"
}'Note: Store data.reference. It is how you match the webhook and how support traces the payment. Repeating the same reference is idempotent: it returns the existing payment instead of charging twice.
Get Payment by Reference
Poll-based status check, for when your webhook is unreachable. Use it no more than once every 10 seconds and stop after 5 minutes: a collection that has not resolved within the 90-second USSD window is not going to resolve because you asked again.
Use zonmpay.com specifically (without www the request is redirected and some HTTP clients drop the POST body — which causes a server error).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
| reference | string | Yes | The ZonmPay reference returned when you created the payment.example: SP008985 |
curl https://zonmpay.com/api/v1/payment/reference/SP008985 \
-H "apiToken: SPP_live_YOUR_API_KEY"Note: feeAmount, feePercent and netAmount are omitted entirely on older payments. Read them with a null check, not a falsy check — an absent feeAmount is not the same as a fee of zero.
Cross-channel Payment (Pay Now)
Re-routes an existing reference to a different channel — for example a customer who created the payment on M-Pesa but wants to pay from an Airtel Money wallet instead.
Use zonmpay.com specifically (without www the request is redirected and some HTTP clients drop the POST body — which causes a server error).
Request Body (JSON)
| Parameter | Type | Required | Description |
|---|---|---|---|
| reference | string | Yes | The existing payment reference.example: SP008985 |
| service | string | Yes | mobile, account, tanqr or merchant.example: mobile |
| account | string | Yes | The new payer identifier.example: 255684118011 |
| provider | string | No | Required for anything other than mobile.example: AIRTELMONEY |
curl -X POST https://zonmpay.com/api/v2/payment/pay \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reference": "SP008985", "service": "mobile", "account": "255684118011" }'Transaction Statuses
PENDING
Created, but the customer has not acted yet: the push is waiting for a PIN, or a link has not been opened.
Action: Wait. Collection only.
PROCESSING
Dispatch is in flight inside ZonmPay.
Action: Wait — no webhook fires on this state.
PROCESSED
Handed to the operator, who accepted it and will call back with the final outcome.
Action: Wait for the final webhook.
AWAITING_APPROVAL
Payout created and waiting for approval. No money has moved.
Action: Approve it with POST /api/v2/payment/approve.
APPROVED
Approved and debited from your wallet; being dispatched.
Action: Wait for payout.confirmed or payout.failed.
SUCCESSFUL
Completed. Money has moved.
Action: Terminal — fulfil the order.
PAID
The requested amount has been received in full.
Action: Terminal — collection only.
PARTIAL
Some of the amount has been paid. Compare paidAmount with amount.
Action: FLEXIBLE collections only. Keep waiting for the balance.
FAILED
Could not complete. failureReason says why. A failed payout is refunded automatically.
Action: Terminal — retry with a new reference.
CUSTOMER_REJECTED
The customer declined the prompt.
Action: Terminal.
REJECTED
Rejected by ZonmPay or the operator before reaching the customer.
Action: Terminal — read the message.
CANCELLED
Cancelled before completion.
Action: Terminal.