Every collection response includes a code field forwarded from the upstream provider (OGateway by default, or 360Pay or JuniPay, depending on how your business is routed), plus a normalised status (approved · pending · failed). The raw code is useful for support tickets and analytics.
code is returned as a JSON string on every endpoint, e.g. "02". Keep the leading zero — never parse it as a number.reason (for example insufficient balance, wrong PIN or timeout). It maps onto the same status as every other provider.code is a two-digit numeric string ("00", "02"…). On JuniPay it is a word ("success", "pending", "failed"). Both are normalised onto the same status, so integrate against status and treat code as diagnostic metadata only.Transaction status codes
The provider reports a transaction state on the settlement callback. We map it as follows:
| Field | Type | Description |
|---|---|---|
00 — SUCCESS | approved | Payment settled. The platform fee is applied and your net balance is credited. |
01 — FAILED | failed | Declined, wrong PIN, insufficient funds, or cancelled by the customer. |
02 — PENDING | pending | Charge created — the customer still has to authorise on their handset or complete checkout. |
03 — PROCESSING | pending | Accepted by the network and being processed. Wait for the callback. |
JuniPay codes
JuniPay returns lowercase words rather than digits. Comparison is case-insensitive on our side.
| Field | Type | Description |
|---|---|---|
success / successful / completed / paid | approved | Payment settled. |
pending / initiated / created | pending | Charge created, awaiting customer authorisation. |
processing / ongoing | pending | Accepted by the network, in flight. |
failed / declined / error | failed | Declined by the wallet, the network, or the customer. |
cancelled / canceled / abandoned | failed | Customer dismissed the prompt or the checkout expired. |
insufficient_funds | failed | Wallet balance too low. |
reversed / refunded | reversed | Settled payment clawed back — triggers collection.reversed. |
unknown code | pending | Anything we do not recognise is held as pending and re-polled — never guessed as approved or failed. |
360Pay codes
Two-digit numeric strings. Keep the leading zero.
| Field | Type | Description |
|---|---|---|
"00" | approved | Transaction successful. |
"01" | failed | Transaction failed — generic decline. |
"02" | pending | Awaiting customer authorisation on the handset. |
"03" | pending | Processing at the network. |
"04" | failed | Insufficient funds in the wallet. |
"05" | failed | Invalid or unregistered wallet number. |
"06" | failed | Customer cancelled or the prompt timed out. |
"07" | failed | Wallet limit or KYC restriction on the customer account. |
"09" | pending | Queued upstream; the callback resolves it. |
"12" | failed | Duplicate reference at the provider. |
"91" | pending | Network or issuer unavailable — re-polled, not failed. |
reversal callback | reversed | A settlement callback marking a prior success as reversed. |
unknown code | pending | Held as pending and re-polled. |
status is our contract and is exhaustive (pending · approved · failed · reversed);code and reason are diagnostic passthrough for your support desk.Institution codes
You send a friendly network value and we route it to the provider's institution code.
| Field | Type | Description |
|---|---|---|
MTN | 300591 | MTN Mobile Money. |
TELECEL | 300594 | Telecel Cash (formerly Vodafone Cash). "VODAFONE" and "VDF" are still accepted and normalised to TELECEL. |
AT | 300592 | AT Money (AirtelTigo). "ATL", "TGO" and "AIRTELTIGO" are still accepted. |
GMONEY | 300574 | G-Money wallet. |
Platform codes
| Field | Type | Description |
|---|---|---|
upstream_error | failed | We could not reach the provider (HTTP 502). Safe to retry with the same Idempotency-Key. |
insufficient_scope | error | HTTP 403 — the API key is read-only. Create a key with write access. |
provider_unsupported | error | HTTP 501 — payout endpoints are retired; payouts are settled manually from the dashboard. |
Not found
Requesting a transaction_id that does not belong to your business (or to the current mode) returns HTTP 404 with a machine-checkable error body — never a fake failed verdict:
HTTP/2 404
{
"error": "transaction_not_found",
"transaction_id": "000000000000"
}GET /v1/transactions?idempotency_key=… before assuming failure.code and the human message as reason on every transaction response and in the ledger. Filter/search by code from Transactions → Payments in your dashboard.