Failure details
There is no substatus field in the order contract. When an Order has
status: "FAILED", the additional detail is carried by two free-form, nullable string
fields on the order itself: failure_code and failure_reason.
Fieldsβ
| Field | Type | Description |
|---|---|---|
failure_code | string | null | A short, free-form code identifying the cause. Not a closed catalog β treat it as an opaque label for logging/support, not a value to switch on exhaustively. |
failure_reason | string | null | A human-readable explanation of the failure. |
They are populated on reads (GET /orders/{id}, GET /orders) once something goes
wrong; the response to the initial POST /orders call may not carry them yet, since
the order has not reached a terminal state at that point.
Two things to code defensively around:
FAILEDis not the only status that carries them. An order that failed and had its funds returned reportsstatus: "COMPENSATED"with thefailure_codeof the original failure still attached. Do not gate reading these fields onstatus === "FAILED".failure_reasoncan be an empty string, not justnull. A check likereason === nullwill miss it; use a falsy check.
Exampleβ
{
"id": "cpg_txn_a1b2c3d4e5f6",
"status": "COMPENSATED",
"failure_code": "PROVIDER_CANCELLED",
"failure_reason": ""
}
The same pair arrives on the webhookβ
You do not have to poll to see this. The order status webhook carries the identical two values under different names:
| On the order | On the webhook event |
|---|---|
failure_code | reason |
failure_reason | message |
Same strings, same source, no translation in between. Whatever you learn to recognize in one place works in the other.
Payout failure codes (RET-*)β
When a payout is rejected at the disbursement network, failure_code carries a
RET- code. These come from our long-standing payout platform and their meanings
have not changed, so they are safe to recognize β but keep reading the caveat
below the table before you build on them.
| Code | What happened | Cause lies with |
|---|---|---|
RET-001 | The phone number is invalid, not registered, or cannot receive payments. | Beneficiary data |
RET-002 | The attempt timed out β either waiting, or on the provider's response. | Provider |
RET-003 | The recipient's account type is wrong or not valid for this operation. | Beneficiary data |
RET-004 | The recipient's person type does not meet the defined criteria. | Beneficiary data |
RET-005 | The transaction was rejected as invalid or unauthorized. | Provider |
RET-006 | The recipient cancelled the transaction before it completed. | Recipient |
RET-007 | The service is unavailable, or only operates in person (over the counter). | Provider |
RET-008 | The phone and the recipient's ID/RIF do not match, or are not registered. | Beneficiary data |
RET-009 | One or more fields have format errors. | Beneficiary data |
RET-010 | The selected financial institution is unavailable right now. | Provider |
RET-012 | The recipient's ID or RIF is invalid. | Beneficiary data |
RET-013 | The recipient's bank account is invalid or not registered. | Beneficiary data |
RET-018 | The nationality on the ID/RIF is not valid. | Beneficiary data |
RET-019 | The authorization network is unavailable right now. | Provider |
RET-020 | The submitted data has errors or does not meet the expected requirements. | Beneficiary data |
RET-026 | The recipient's number is wrong or not enrolled in the system. | Beneficiary data |
RET-028 | Available balance is insufficient to complete the transaction. | Balance |
RET-029 | The destination bank is not enrolled, authorized, or active for mobile payments. | Provider |
RET-033 | The transaction exceeds the daily amount allowed. | Limits |
RET-034 | The transaction exceeds the monthly amount allowed. | Limits |
RET-035 | The daily count of allowed payments has been exceeded. | Limits |
RET-036 | The monthly count of allowed payments has been exceeded. | Limits |
RET-044 | The recipient's bank account is closed. | Beneficiary data |
RET-045 | The recipient's bank account is inactive. | Beneficiary data |
RET-050 | The amount exceeds the maximum allowed for a single transaction. | Limits |
RET-055 | The phone number is not enabled to receive payments. | Beneficiary data |
RET-056 | The operation amount is above the maximum allowed. | Limits |
RET-9999 | An unexpected error occurred. Try again, or contact support. | Unknown |
"Cause lies with" is a reading aid, not a field. It is our grouping of the
descriptions above, to save you triaging 28 rows by hand; nothing in the API
returns it. Beneficiary data means a corrected payment_instructions can
succeed. Limits and Balance mean the request was well-formed but did not fit.
Provider means retrying later may work and nothing on your side needs changing.
failure_code and failure_reason are free-form strings, not a fixed error catalog β
for the structured error contract returned on failed API calls (4xx/5xx responses),
see Error codes.