Disbursement API
Send funds to mobile wallets, bank accounts and tills.
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
A payout is created in AWAITING_APPROVAL and moves only when it is approved — this is not a threshold some projects can turn off. Approving from your own server uses your IP address as the second factor instead of an OTP. The payout fee is TZS 2,500 or 2.5%, whichever is higher, and the payout range is TZS 5,500 to TZS 3,000,000. A failed payout is refunded to your balance automatically.
- Step 1 — Whitelist your server IP — A one-off setup step. Without it your code cannot approve anything, and every approval returns 403.
- Step 2 — Create the payout — POST /api/v2/payment/disbursement returns code 1120 and AWAITING_APPROVAL. No money has moved yet.
- Step 3 — Approve it — POST /api/v2/payment/approve approves and dispatches in one call, and returns the debit block showing exactly what left your wallet.
Disbursement (Unified)
Creates a payout to a mobile wallet, bank account or till on any channel. Creating a payout does not send it: every payout, on every channel and at every amount, is created in AWAITING_APPROVAL and no money moves until you approve it.
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 send in TZS. Minimum TZS 5,500, maximum TZS 3,000,000.example: 50000 |
| service | string | Yes | mobile, account or merchant.example: mobile |
| account | string | Yes | Recipient phone number, bank account or till id.example: 255684118011 |
| provider | string | No | Optional for mobile, required for everything else.example: CRDB |
| description | string | No | For your own records.example: Mobile disbursement |
| beneficiaryName | string | No | Recipient name, for your records only.example: Asha Mwinchumu |
| reference | string | No | Your own payout reference.example: PAY-2001 |
curl -X POST https://zonmpay.com/api/v2/payment/disbursement \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "Mobile disbursement",
"amount": 50000,
"service": "mobile",
"account": "255684118011"
}'Note: Code 1120 with AWAITING_APPROVAL is the normal, healthy outcome. Keep data.reference — it is the only thing the approval step needs.
Approve a Payout
This one call approves and dispatches. There is no separate confirm step and no OTP, because the request must come from an IP address you registered on your project. Money leaving your wallet needs a second factor on top of the API key.
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 ZonmPay payout reference, not your own.example: SP009142 |
curl -X POST https://zonmpay.com/api/v2/payment/approve \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "reference": "SP009142" }'Note: The debit block is the authoritative record of what left your wallet. Reconcile against totalDebit, not against amount. Re-approving an already APPROVED payout is safe and does not pay twice. On a 502, do not re-submit — poll the reference.
Batch Disbursement
Creates many payouts in one request, and the records may mix channels. Each record becomes its own payment in AWAITING_APPROVAL and is approved individually by its own reference — a batch is a convenience for creation, not a way to approve in bulk.
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 |
|---|---|---|---|
| description | string | No | A label for the whole batch.example: March payouts |
| records | array | Yes | Each record needs provider, service, account and amount. provider is required on every batch record, including mobile ones — the auto-detection that makes it optional on a single payout does not apply here. |
curl -X POST https://zonmpay.com/api/v2/payment/disbursement/batch \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"description": "March payouts",
"records": [
{ "provider": "VODACOM", "service": "mobile", "account": "255712345678", "amount": 25000 }
]
}'IP Whitelist
Register every public IP your servers can call from. Entries are matched as exact strings: there is no CIDR or wildcard support, so 196.192.0.0/24 will never match. Register each application server, each NAT gateway, both addresses of any active/standby pair, and any outbound proxy.
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 |
|---|---|---|---|
| ip_address | string | Yes | IPv4 or IPv6 address, exact.example: 196.192.0.10 |
| name | string | No | Name every entry after the system it belongs to.example: Production API server |
curl -X POST https://zonmpay.com/api/v2/project/whitelist \
-H "apiToken: SPP_live_YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "ip_address": "196.192.0.10", "name": "Production API server" }'Note: GET the same path to list entries and DELETE /api/v2/project/whitelist/{id} to remove one. Whitelisting applies only to the approve endpoint — collections, payout creation and every read endpoint work from anywhere.
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.