Errors

How Scan to Pay returns errors — HTTP status codes, the platform-specific 4xx error catalogue, and how to handle each.

Scan to Pay returns errors using standard HTTP status codes plus an extended set of 4xx codes in the 431–481 range for platform-specific failures. The error body, when present, contains a human-readable explanation of what went wrong.

This page is the canonical reference for every error code the API can return.

📘

For transaction outcomes, see Transaction states. Errors here are protocol-level failures (your request couldn't be processed). Once a transaction reaches the platform successfully, its outcome is communicated via TxState or NotificationStatus, not HTTP error codes.


Standard HTTP statuses

StatusMeaningTypical cause
200 OKSuccessRequest processed and returned data
400 Bad RequestMalformed requestMissing required field, invalid JSON, validation failure. Inspect the response body for the specific error
401 UnauthorizedAuthentication failedWrong username or password, missing Authorization header, expired JWT
403 ForbiddenAuthenticated but not permittedYour account doesn't have the role required for this path — see Authentication for the role / path mapping
404 Not FoundResource doesn't existCode, transaction, or merchant not found at the requested ID
406 Not AcceptableOperation not allowed in current stateMost commonly: calling queryRef on a merchant that has webhook delivery configured
500 Internal Server ErrorPlatform errorSomething failed inside Scan to Pay. Retry with exponential backoff. If it persists, raise a support ticket

Scan to Pay extended error codes

Every Scan to Pay error returns one of the codes below in the 431–481 range. The HTTP status code identifies the error; the response body provides additional detail.

Code-related errors (431–456)

CodeNameMeaning
431CODE_NOT_FOUNDThe referenced code doesn't exist. Check the code value.
432INVALID_AMOUNTThe amount is zero, negative, exceeds the maximum, or doesn't match the code amount on a fixed-amount code.
432INSUFFICIENT_BALANCEWallet has insufficient balance to cover the amount (wallet flows only).
433INVALID_NETWORKThe network specified in a Network Purchase request isn't recognised.
434CLIENT_SUSPENDEDThe customer's account has been suspended.
436INVALID_TRANSACTION_REFThe transaction reference doesn't match any known transaction.
438CARD_ERRORCard validation or tokenisation failed. Body includes specific reason.
439INVALID_CODEThe code format is wrong.
440VALIDATION_ERRORGeneric field validation failed. Body includes the specific field.
441INVALID_MERCHANTThe merchant ID is unknown or inactive.
442INVALID_CLIENTThe client (cardholder) ID is unknown.
443CODE_LOCKEDThe code is currently locked (being paid in another flow). Wait or use a different code.
444NOTHING_TO_REVERSENo active transaction to reverse.
445INVALID_CLIENT_CARDThe card reference is invalid for this client.
446INVALID_PASSWORDProfile password validation failed.
447INVALID_BASKETThe cart items provided don't validate against the rules (e.g. AIRTIME basket without destination).
448CLIENT_LIMIT_EXCEEDEDThe customer hit their daily/monthly limit.
449CARD_HASH_FAILEDThe card-to-MSISDN hash check failed (anti-fraud).
450INVALID_PAYMENTThe payment type isn't supported by this merchant or for this card.
451FRAUD_DETECTIONTransaction blocked by fraud detection.
452CODE_RESERVEDCode is in RESERVED state and can't be used.
453CODE_DELETEDCode has been deleted.
454CODE_ACTIVEOperation not valid on an active code.
455CODE_USEDCode has already been used (use-once code).
456CODE_BLOCKEDCode is blocked. Unblock first, or use a different code.

Merchant and transaction errors (460–481)

CodeNameMeaning
460MERCHANT_NOT_FOUNDThe merchant doesn't exist or isn't reachable.
461INVALID_REQUESTThe request body or query string is malformed in a way that doesn't match a more specific validation rule.
462CNP_VALIDATION_STEP_UP_REQUIREDCard-not-present transaction requires additional authentication (step-up to 3DS).
463INVALID_TOKENThe provided token is invalid or expired.
464FAIL_OR_RETRY_TX_BANK_DECLINEThe bank declined. Body includes the bank response code — see ISO response codes.
465MISSING_MSISDNAn MSISDN is required for this operation but wasn't provided.
466INVALID_OR_MISSING_PAYMANT_TYPEPayment type missing or unrecognised.
467NOT_ALLOW_PAYMENT_TYPEThe merchant doesn't accept this payment type (typically CNP).
468WIG_CNP_PURCHASE_FAIL_CARD_EXPIRY_BIN_NOT_WHIELISTUSSD CNP purchase failed: card expiry or BIN not whitelisted.
469WIG_3DS_PURCHASE_FAIL_CARD_EXPIRY_BIN_NOT_WHIELISTUSSD 3DS purchase failed: card expiry or BIN not whitelisted.
470INVALID_QR_COLOR_CODEQR colorCode query param must be 0–3.
471EXTERNAL_API_ERRORA downstream service (acquirer, BIN lookup, etc.) returned an error.
472MERCHANT_WHITELIST_FAILUREThe merchant whitelist check failed for this card/wallet.
473INSUFFICIENT_INFO_FOR_MERCHANT_WHITELISTNot enough data to evaluate the whitelist rule.
474INVALID_VOUCHER_CODEThe voucher code is invalid.
475VOUCHER_ERRORA voucher purchase failed for another reason.
476BIN_NOT_FOUNDThe card BIN isn't in the BIN tables.
477CCPI_ERRORA recurring (CCPI) purchase failed.
478PROXY_ERRORAn upstream proxy returned an error.
479CUSTOM_ERRORA custom error specified by the merchant configuration. Body explains.
480PAYSHAP_ERRORA PayShap rapid-payments transaction failed.
481INVALID_CARD_EXPIRYThe card expiry date is invalid or in the past.

Platform errors (5xx range)

These are platform-side errors rather than client mistakes. They're worth distinguishing because the right response is "retry" rather than "fix your request."

CodeNameMeaning
500INTERNAL_SERVER_ERRORGeneric platform error. Body usually contains a request ID. Retry with backoff.
512SYSTEM_ERRORAn internal Scan to Pay system error. Investigate via support if persistent.
513SECURITY_VIOLATIONAn operation was attempted that the platform considers a security violation (e.g. one merchant trying to access another's transaction).
514UNABLE_TO_REVERSEThe platform attempted to reverse but couldn't (outside the window, already reversed, etc.).
515UNABLE_TO_REFUNDThe platform attempted to refund but couldn't (different card type, already refunded, outside acquirer window).
516INTERRUPTEDThe operation was interrupted mid-processing. Check the transaction state via getTransactionState before retrying.

Error response format

When the platform returns a 4xx or 5xx, the response body usually contains a JSON object with the error code and a message:

{
  "status": "FAILED",
  "errorCode": 432,
  "errorMessage": "Invalid Amount: amount must be greater than zero"
}

Older endpoints return a simpler shape:

{
  "error": "Validation error: merchantReference is required"
}

Always check the HTTP status code first, then the body for detail. The status code is the source of truth; the body is human help.

📘

Codes that return no errorMessage: 462 CNP_VALIDATION_STEP_UP_REQUIRED, 463 INVALID_TOKEN, 472 MERCHANT_WHITELIST_FAILURE, 479 CUSTOM_ERROR, and 480 PAYSHAP_ERROR return only the HTTP status and errorCode — there is no message string in the response body. Identify these by errorCode alone.


Handling errors in your integration

A simple decision tree for what to do with each class of error:

ClassExamplesWhat to do
Authentication401Verify credentials are correct and not rotated. Refresh JWT if using Bearer.
Authorisation403Wrong role for the endpoint. Check Authentication for the path/role table.
Validation400, 432, 440, 447Fix your request. Do not retry the same payload — it'll fail again.
State conflict406, 443, 444, 452–456The resource isn't in a state where the operation is allowed. Inspect with query first, take corrective action.
Bank/Network434, 438, 448, 464, 468, 469, 481Customer-side problem. Display a friendly message and let the customer retry with different data or a different card.
Card data438, 445, 449, 476, 481Card-specific failure. Customer should try a different card.
Fraud / risk448, 451, 472, 473Don't retry. The platform blocked the transaction deliberately.
Transient platform500, 512, 516Retry with exponential backoff. After 3 attempts, escalate to support with the request ID.
Code lifecycle431, 443, 452–456Inspect the code state and create a fresh code if needed.

Best practices

  • Always log the response body, not just the HTTP status. The body is where the specific reason lives.
  • Map error codes to customer-facing copy in your UI. Never show CODE_LOCKED or CARD_HASH_FAILED to an end user. See ISO response codes for suggested customer messages.
  • Distinguish retriable from non-retriable. 5xx and transient 4xx (516 INTERRUPTED) → retry. Validation 4xx → do not retry the same request.
  • Capture request IDs if the response includes one — support tickets are much faster to resolve with a request ID attached.
  • Use idempotent retries. When you retry, use the same merchantReference so the platform recognises it as the same logical attempt. See Idempotency.
  • Don't catch and swallow. Especially on webhook handlers, surface errors prominently so on-call sees them.

What's next