ZonmPay API v2

Disbursement API

Send funds to mobile wallets, bank accounts and tills.

PAYOUT · CREATE + APPROVECreating a payout does not send it. Every payout waits for approval from a whitelisted IP.

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 IPA one-off setup step. Without it your code cannot approve anything, and every approval returns 403.
  • Step 2 — Create the payoutPOST /api/v2/payment/disbursement returns code 1120 and AWAITING_APPROVAL. No money has moved yet.
  • Step 3 — Approve itPOST /api/v2/payment/approve approves and dispatches in one call, and returns the debit block showing exactly what left your wallet.
Step 1POST

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.

POST https://zonmpay.com/api/v2/payment/disbursement

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)

ParameterTypeRequiredDescription
amountintegerYesAmount to send in TZS. Minimum TZS 5,500, maximum TZS 3,000,000.example: 50000
servicestringYesmobile, account or merchant.example: mobile
accountstringYesRecipient phone number, bank account or till id.example: 255684118011
providerstringNoOptional for mobile, required for everything else.example: CRDB
descriptionstringNoFor your own records.example: Mobile disbursement
beneficiaryNamestringNoRecipient name, for your records only.example: Asha Mwinchumu
referencestringNoYour own payout reference.example: PAY-2001
bash
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.

Step 2POST

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.

POST https://zonmpay.com/api/v2/payment/approve

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)

ParameterTypeRequiredDescription
referencestringYesThe ZonmPay payout reference, not your own.example: SP009142
bash
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.

POST

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.

POST https://zonmpay.com/api/v2/payment/disbursement/batch

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)

ParameterTypeRequiredDescription
descriptionstringNoA label for the whole batch.example: March payouts
recordsarrayYesEach 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.
bash
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 }
    ]
  }'
POST

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.

POST https://zonmpay.com/api/v2/project/whitelist

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)

ParameterTypeRequiredDescription
ip_addressstringYesIPv4 or IPv6 address, exact.example: 196.192.0.10
namestringNoName every entry after the system it belongs to.example: Production API server
bash
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.