Async Transaction Error Codes¶
When a transaction reaches failed, the callback (and the Payment Status response) carries an errorCode and errorMessage. These differ from the synchronous errors returned at request time (those are documented per-endpoint and in Error Handling) — async error codes describe why a transaction that was successfully created later failed.
This page is the catalogue. Use it to:
- Decide what to display to the end user.
- Decide whether the transaction can be safely retried with a fresh
merchantReference. - Map provider-specific errors to a stable, documented vocabulary.
Provider's native code is also available
The providerData.errorCode and providerData.errorMessage fields carry the raw code returned by the underlying provider. Use the gateway-normalised errorCode as your primary signal — fall back to providerData only when you need provider-specific detail.
How to Read This Catalogue¶
| Column | What it tells you |
|---|---|
| Error code | The string in the errorCode field. Treat as opaque — do not parse the prefix. |
| What happened | The condition that caused the failure. |
| User-facing message | A short, suggested phrasing you can show the end user. Adapt to your tone. |
| Retryable? | Whether asking the user to try again with a new merchantReference is likely to succeed. "No" means the failure is structural; retrying without changing inputs will fail the same way. |
Treat any code not listed here as not retryable and surface a generic error.
User-Side Failures¶
Caused by something on the end user's side — funds, limits, authentication, intent.
| Error code | What happened | User-facing message | Retryable? |
|---|---|---|---|
user_insufficient_funds |
The end user's wallet did not have enough money. | "Insufficient funds. Please top up and try again." | Yes — after the user adds funds. |
user_limit_exceeded |
The end user has hit a daily/weekly/monthly account limit. | "You've reached your limit for this period. Try again later or contact your provider." | Maybe — usually next period. |
user_authorization_failed |
The end user entered a wrong PIN/OTP, or authentication failed. | "We couldn't authenticate this payment. Please try again." | Yes. |
user_timeout |
The end user did not approve the prompt in time (STK push expired). | "The payment prompt timed out. Please try again." | Yes. |
user_invalid_msisdn_for_method |
The MSISDN you submitted is not registered for this payment method. | "This phone number isn't enrolled for the selected payment option." | No — the user must pick a different method or fix the number. |
user_account_block |
The end user's account is blocked by the provider. | "We can't process payments for this account. Please contact your provider." | No. |
user_too_many_requests |
The end user has triggered too many attempts in a short window. | "Too many attempts. Please wait a few minutes and try again." | Yes — after a delay. |
user_cancelled |
The end user cancelled the prompt. | "Payment cancelled." | Yes. |
user_unknown_error |
Provider reported an unspecified user-side error. | "Something went wrong on the user's side." | Maybe. |
Provider-Side Failures¶
The end user did everything right; the upstream provider rejected the transaction or didn't respond.
| Error code | What happened | User-facing message | Retryable? |
|---|---|---|---|
provider_unavailable |
The provider's system was down or unreachable. | "Payment provider is temporarily unavailable. Please try again shortly." | Yes — after a delay. |
provider_timeout |
The provider didn't respond within the gateway's wait window. | Same as above. | Yes. |
provider_noresponse_from_method |
The specific payment method didn't return a status. | Same as above. | Yes. |
provider_connection_error |
Network-level error contacting the provider. | "Connection issue with payment provider. Try again." | Yes. |
provider_too_many_requests |
The provider rate-limited us. | "We're seeing high traffic. Try again in a moment." | Yes — backoff. |
provider_bad_request |
The provider rejected the request shape — usually a configuration mismatch. | "We couldn't process this payment. Please contact support." | No — needs investigation. |
provider_wrong_configuration |
The provider returned a configuration error. | Same as above. | No. |
provider_transactionid_duplicate |
The provider's own dedup window rejected the reference. | "Payment reference already used at the provider. Please retry with a fresh attempt." | Yes — with a new merchantReference. |
provider_cancelled |
The provider cancelled the transaction (anti-fraud, manual review, etc.). | "Payment was cancelled by the provider." | No. |
provider_limit_exceeded |
The provider's per-transaction or daily limit applied. | "Payment exceeds the limit allowed by the provider." | Maybe — try a smaller amount or later. |
provider_unknown_error |
Provider returned an error we couldn't classify. | "Payment failed. Please try again." | Maybe. |
Risk and Authentication¶
| Error code | What happened | User-facing message | Retryable? |
|---|---|---|---|
risk_blocked |
Risk engine blocked the transaction. | "Payment was declined for security reasons." | No. |
security_authentication_failed |
The credentials presented to the provider were rejected. | "Payment failed. Please contact support." | No. |
security_authorization_failed |
The caller does not have permission for this operation. | Same as above. | No. |
Configuration Failures¶
These usually indicate a setup mismatch on PayAlo's side. They should be rare in production for a well-configured brand; report them to support if you see them.
| Error code | What happened | Retryable? |
|---|---|---|
config_unsupported_currency |
The currency is not configured for this method/brand. | No — fix configuration. |
config_unsupported_country |
The country is not configured. | No. |
config_unsupported_payment_method |
The method is not enabled for this brand. | No. |
config_method_route_not_found |
No route exists for the method/country/currency triple. | No. |
config_method_transaction_min_limit |
Amount below the configured minimum. | Yes — with a higher amount. |
config_method_transaction_max_limit |
Amount above the configured maximum. | Yes — with a lower amount or split. |
config_credentials_missing |
The brand's provider credentials are missing. | No — contact support. |
Merchant-Side Failures¶
| Error code | What happened | Retryable? |
|---|---|---|
merchant_disabled |
The brand has been disabled. | No — contact your account manager. |
merchant_insufficient_funds |
The merchant float used for pay-outs has insufficient balance. | Yes — after topping up. |
merchant_limit_exceeded |
The merchant has hit a configured limit. | Maybe. |
merchant_invalid_credentials |
The merchant credentials at the provider are invalid. | No — contact support. |
merchant_cancelled_by_risk |
A risk rule cancelled this transaction. | No. |
merchant_transactionid_duplicate |
Duplicate merchantReference. (Usually returned synchronously, but may also appear async.) |
Yes — with a new reference. |
merchant_unknown_error |
Merchant-side error we couldn't classify. | Maybe. |
Gateway and Lifecycle¶
| Error code | What happened | Retryable? |
|---|---|---|
transaction_expired |
The gateway-side timeout fired (a pending transaction sat without resolving for too long). The transaction is auto-marked failed. |
Yes — with a new merchantReference. |
transaction_not_found |
Status lookup failed because the transaction does not exist for this brand. | No. |
gateway_too_many_requests |
The gateway rate-limited the request. | Yes — backoff. |
hpp_open_timeout |
The hosted payment page session was created but never opened in time. | Yes. |
Recommendations¶
- Log the full callback payload when handling a failure, including
errorCode,errorMessage, andproviderData. Provider context is often the only thing that explains the failure after the fact. - Don't show provider error messages to users verbatim. They're inconsistent across providers and often unhelpful. Map
errorCodeto your own copy. - Don't retry without changing the
merchantReference. Even on retryable codes, the originalmerchantReferenceis consumed and will hit the duplicate guard. - Treat unknown codes as not-retryable. New codes may be added in future; failing closed is safer than failing open.