Skip to content

Errors

A failed request answers with an HTTP status of 400 or more and this body:

{
"error": {
"message": "Some of the information provided is not valid.",
"code": "VALIDATION_FAILED",
"title": "Validation failed",
"details": [
{ "field": "amount", "code": "invalid", "message": "Send a positive amount in minor units (500000 = ₦5,000.00)." },
{ "field": "currency", "code": "unsupported", "message": "Only NGN is supported." },
{ "field": "customer.email", "code": "format", "message": "Enter a valid email address." }
]
},
"meta": { "request_id": "01a0fbe8-71b1-76ff-bd46-8017a630b882" }
}
Field
error.codeWhat went wrong, as a stable code. Branch on this.
error.messageA sentence for a person. Safe to show to your user; do not parse it, it may change. When exactly one field is wrong, it is that field’s message.
error.titleThe code in words.
error.detailsFor validation errors: one entry per field, with the field’s path (customer.email), a short code (required, invalid, format, length, unknown, type, unsupported) and a message.
error.metaExtra context for some codes, such as retry_after_seconds.
meta.request_idThis request’s id, also in the X-Request-Id header. Log it, and quote it when you contact us.

You may send your own X-Request-Id (up to 64 printable characters) to tie our logs to yours; we echo it back.

StatuscodeMeaningWhat to do
400VALIDATION_FAILEDA field is missing or wrong, an unknown field was sent, a query parameter is invalid, or Idempotency-Key is missing.Fix the request using details. Do not retry unchanged.
400MALFORMED_REQUESTThe body is not valid JSON, or is empty.Fix the body.
401UNAUTHENTICATEDNo Authorization: Bearer header.Send your secret key.
401INVALID_API_KEYThe key is unknown, mistyped or revoked.Check the key, or roll it.
403SECRET_KEY_REQUIREDA publishable key (pk_…) was sent.Use your secret key, from your server.
403LIVE_MODE_NOT_ENABLEDA live key before your business is verified.Use test keys until you are live.
403ACCOUNT_RESTRICTEDYour account is suspended.Contact us.
403TEST_MODE_ONLYA test helper was called in live mode.Use a test key or a test checkout.
404NOT_FOUNDNo such object in this mode, or no such endpoint. Objects of other businesses are also “not found”.Check the id and that you use the key of the right mode.
409DUPLICATE_REFERENCEYou already used this reference in this mode.Use a new reference for a new payment, or fetch the existing one.
409IDEMPOTENCY_KEY_REUSEDThis Idempotency-Key was used with a different body.Use a new key for a different request.
409REQUEST_IN_PROGRESSThe first request with this Idempotency-Key is still running.Retry the same request in a second or two.
409INVALID_STATE_TRANSITIONThe action does not fit the object’s state, such as resending a webhook for a payment that has not finished.Wait until it can apply.
409CONFLICTSomething else is in the way; the message says what (for example, no webhook endpoint is subscribed).Read the message.
413PAYLOAD_TOO_LARGEThe body is over 2 MB.Send less. Metadata is limited to 8 KB anyway.
415UNSUPPORTED_MEDIA_TYPEThe body is not sent as application/json.Set Content-Type: application/json.
422VALIDATION_FAILEDAccount-name enquiry found no such account.Check the bank and number.
429RATE_LIMITEDToo many requests.Wait Retry-After seconds. See Rate limits.
500INTERNAL_ERROROur fault.Retry with backoff. Safe for POST /v1/payments thanks to the Idempotency-Key.
503SERVICE_UNAVAILABLEA bank or rail we depend on did not answer.Retry shortly.

The hosted checkout handles these itself and shows the customer what to do. Your server never receives them, but you may see them in your dashboard or hear them from a customer:

codeMeaning
CHECKOUT_EXPIREDThe checkout passed its expires_at. Start a new payment.
CHECKOUT_CLOSEDThe payment is already complete, or closed.
PAYMENT_IN_PROGRESSAn attempt is still running; the customer must wait for it before trying again.
CHANNEL_NOT_ENABLEDThat method is not offered for this payment.
INVALID_OTPWrong one-time code, with tries left.
PAYER_NOT_FOUNDNo Nablr account with that username or phone.
ATTEMPT_FAILEDThe method was refused outright; the customer can try another.
LINK_INACTIVEThe payment link has been turned off.
  • Retry 429, 500, 503 and network errors, with exponential backoff (for example 1 s, 2 s, 4 s, then give up and alert).
  • Never retry 400, 401, 403, 404 or 409 unchanged: the answer will not change.
  • POST /v1/payments with the same Idempotency-Key is always safe to retry: see Idempotency.