Error Codes
When a payment fails, ODUS reports the reason as a standardized error code. Where the failure came from a payment processor, the processor-specific error is normalized into that same set — so regardless of whether the payment was routed through Stripe, Adyen, PayPal, or any other gateway, the error response uses the same codes. This lets you build a single error-handling path in your integration instead of mapping each processor's errors individually.
Error information is returned in the outcome object on the transaction.
These are declines — a request the API processed successfully, reporting a payment that did not
go through. Usually the issuer refused it. Some codes instead report a payment that was never
authorized against the payment method and cannot be by trying again; those carry declineType
not_attempted. For requests the API refuses to process at all (validation failures,
wrong resource state, missing permissions), see Error Responses.
The outcome object
When a transaction fails, the response includes an outcome object with the following fields:
| Field | Type | Visibility | Description |
|---|---|---|---|
status | string | Public | Operation result: success, partial, or failure. |
errorCode | string | Sensitive | Standardized error code from the table below. |
errorMessage | string | Sensitive | Detailed error message. May contain sensitive information (e.g. fraud). |
safeErrorMessage | string | Public | Customer-safe error message that can be displayed in your UI. |
declineType | string | Sensitive | Either soft_decline, hard_decline or not_attempted. |
retryStrategy | string | Public | Recommended retry strategy for the client. |
processorCode | string | Sensitive | Raw error code returned by the payment processor. |
processorMessage | string | Sensitive | Raw error message returned by the payment processor. |
specialCode | string | null | Public | Special status codes such as confirmation_requested for 3DS redirects. null when no special status applies. |
Sensitive fields are only visible when using a secret API key. Requests made with a publishable key will not include these fields. Never expose sensitive fields to the customer — use safeErrorMessage instead of errorMessage.
Decline types
Every error code carries a decline type: whether the payment method was refused, and whether repeating the same attempt could work.
| Type | Meaning |
|---|---|
soft_decline | The payment may succeed if retried. The issue is potentially temporary or resolvable by the customer. |
hard_decline | The payment method was refused and will not succeed on retry — it is blocked, restricted, or revoked. |
not_attempted | This payment was not authorized against the payment method, so the issuer refused nothing. The cause lies elsewhere — the stored payment method, your account, the request, the order, a policy rule, or a capture that already happened. Read errorCode for which, and the remedy. Repeating the same operation unchanged cannot succeed. v2 only. |
Error code reference
The table below lists every standardized error code, grouped by decline type.
Eight codes are returned on v2 only — see Codes added in v2. On v1 each of
them is reported as a broader code v1 already publishes, with that code's decline type and retry
strategy, so a v1 integration keeps receiving exactly the codes and retry strategies it receives
today. not_attempted is a v2 decline type for the same reason: it is never returned on v1.
Filtering follows the same rule. On v1, a latestDeclineType filter selects the payments v1
reports as that type, so a filter and the payments it returns describe a decline the same way;
not_attempted is not a value you can filter on there.
Hard declines
These are non-recoverable. Do not retry the same payment method.
| Code | Description | Retry Strategy | Safe Message |
|---|---|---|---|
fraud_suspected | The processor flagged the transaction as potentially fraudulent. | never_retry | The payment was declined. Please use a different payment method. |
card_restricted | The payment method or account is restricted — for a card, lost, stolen, or blocked by the issuer. | never_retry | The payment was declined. |
generic_hard_decline | Authorization was revoked or another non-recoverable decline occurred. | never_retry | The payment was declined. |
authentication_rejected | The issuer's authentication server refused the authentication outright, or the card has already used up its allowed challenges. | never_retry | The payment was declined. Please use a different payment method. |
Soft declines — payment method issues
The customer can resolve these by correcting or changing their payment method.
| Code | Description | Retry Strategy | Safe Message |
|---|---|---|---|
insufficient_funds | The payment method does not have enough funds to cover the transaction. | retry_after_delay | There aren't enough funds available for this payment. Please use a different payment method. |
invalid_card_details | The payment details were rejected as invalid — for a card, the number, security code or expiry. | retry_with_new_payment_method | Some of the payment details are incorrect. Please check them and try again. |
expired_card | The payment method is past its expiry date. | retry_with_new_payment_method | This payment method has expired. Please use a different one. |
authentication_failed | Authentication was required but could not be completed — a wrong one-time code, or a card the issuer will not authenticate. | retry_with_new_payment_method | The payment couldn't be verified. Please try again or use a different payment method. |
transaction_not_permitted | The payment method or account does not allow this transaction type. | retry_with_new_payment_method | The payment was declined. Please use a different payment method. |
Soft declines — processor / issuer errors
These are transient. Retrying may succeed without any customer action.
| Code | Description | Retry Strategy | Safe Message |
|---|---|---|---|
generic_issuer_error | The issuer or funding provider returned a generic error. Typically temporary. | retry_immediately | We couldn't confirm this payment in time. Please check whether it went through before trying again. |
generic_processor_error | The payment processor returned a generic error. | retry_immediately | We couldn't confirm this payment in time. Please check whether it went through before trying again. |
processor_rate_limited | The processor is rate-limiting requests. | retry_immediately | We're busy right now. Please try again in a few moments. |
processor_timeout | The processor did not respond in time. | retry_immediately | We couldn't confirm this payment in time. Please check whether it went through before trying again. |
authentication_unavailable | Authentication never ran — the issuer's authentication server timed out or failed technically. | retry_immediately | Something went wrong. Please try again. |
authentication_required | The issuer requires the payment to be authenticated and refused it without 3D Secure. | retry_immediately | This payment needs to be verified. Please try again to continue. |
authentication_cancelled, authentication_rejected and authentication_unavailable each describe
a specific way a 3D Secure authentication did not succeed. A processor may report all of them under a
single refusal reason — Adyen's "3D Not Authenticated" is one — so ODUS separates them from the
authentication result on the response, because the right response differs: a customer who dismissed
the challenge stopped the payment themselves and can start it again, an authentication server that
never answered can be retried without involving them, and an issuer that refused outright must not be
retried at all. authentication_cancelled is listed under
Soft declines — other, alongside cancelled, because the two describe the
same customer action.
authentication_required is the one case where authentication has not happened yet rather than
failed. The issuer refused an exemption and wants the payment authenticated, so retrying it with 3D
Secure is the documented remedy — there is nothing wrong with the payment method, and asking the
customer to change it will not help.
authentication_failed is the general case, and is not limited to 3D Secure: it also covers a card
the issuer will not authenticate at all, and approval failures on other payment methods such as a
PayPal billing agreement.
Soft declines — other
| Code | Description | Retry Strategy | Safe Message |
|---|---|---|---|
generic_decline | A general decline with no specific reason from the processor. | retry_after_delay | The payment was declined. Please try again or use a different payment method. |
cancelled | The customer cancelled the payment, at a 3DS challenge or at another approval step such as a PayPal approval. Where the processor identifies a cancelled 3DS challenge specifically, authentication_cancelled is reported instead. | retry_after_delay | The payment was cancelled. You can try again when you're ready. |
authentication_cancelled | The customer dismissed the authentication challenge. Reported instead of cancelled where the processor identifies a cancelled 3DS challenge specifically; both describe the same customer action and carry the same strategy. | retry_after_delay | The payment was cancelled. You can try again when you're ready. |
not_supported | The payment method or currency is not supported by the processor. | retry_after_delay | This payment method isn't available for this payment. Please use a different one. |
internal_error | An error occurred within ODUS while processing the payment. | retry_after_delay | Something went wrong. Please try again. |
Codes added in v2
Some of the codes above cover more than one outcome, and the advice attached to them cannot be true
of all of them at once. generic_hard_decline reports a payment that has already been captured
alongside genuine refusals, so "use a different payment method" invites a second charge for the same
order. card_restricted reports a problem with the store's own processor account alongside a
blocked card, so no payment method your customer chooses gets past it. And
generic_processor_error and generic_issuer_error mean the payment was left unresolved and needs
reconciling before a retry — but they also carry requests that were refused outright, where nothing
is unresolved at all.
v2 gives each of those outcomes its own code, and adds two more —
payment_method_unusable, for a stored payment method that can no longer be charged, and
stored_credential_revoked, for one the processor now refuses to let you charge. v1 is
unchanged: it keeps reporting all eight as the broader code in the last column, with that code's
decline type and retry strategy.
All eight are not_attempted with never_retry. None is a decision by the issuer about your
customer's payment method, and none is resolved by another attempt at the same thing. For all but
payment_method_unusable and stored_credential_revoked, the remedy is in the store's hands rather
than the customer's.
| Code (v2) | Description | Safe Message | Reported in v1 as |
|---|---|---|---|
payment_method_unusable | The stored payment method can no longer be charged and has to be replaced. Ask the customer for a new one; retrying this one cannot succeed. | The payment was declined. Please use a different payment method. | generic_hard_decline |
stored_credential_revoked | Permission to charge a stored payment method has been withdrawn, so the processor refuses it without asking the funding source. Ask the customer to authorize a new payment method; this one cannot be charged again. | The payment was declined. Please use a different payment method. | generic_hard_decline |
already_captured | The payment has already been taken. Nothing is owed, and charging again would take it twice. | This payment has already gone through. You don't need to pay again. | generic_hard_decline |
merchant_account_restricted | Your account with the processor is locked, closed or restricted, so it cannot accept payments. | This store can't take this payment right now. | card_restricted |
merchant_configuration_error | Your account is not set up to accept this payment — the payment method, country or currency is not enabled on it. | This store can't take this payment right now. | generic_issuer_error |
invalid_request | The processor refused the request as malformed or invalid. It never reached an authorization, so nothing is unresolved. | This payment can't be completed. | generic_processor_error |
blocked_by_policy | A risk or compliance rule blocked the payment. It is not a fraud signal about your customer's payment method. | The payment was declined. | fraud_suspected |
order_not_permitted | Something about the order is not accepted — where it ships to, what it contains, or how its amount is expressed. | This payment can't be completed. | transaction_not_permitted |
invalid_request and merchant_configuration_error are the definitive counterparts to
generic_processor_error and generic_issuer_error. Where those two warn you that the payment may
have gone through and needs checking before a retry, these two tell you it did not: the request was
refused before any money moved, so you can act on the failure immediately instead of reconciling
first.
Retry strategies
Each error code maps to a recommended retry strategy. Use this to decide how your integration should respond to a failure.
| Strategy | Meaning |
|---|---|
never_retry | Do not repeat this operation unchanged — it cannot succeed. Check errorCode for the remedy: a hard_decline means the payment method is blocked, restricted or flagged for fraud and retrying it may incur network penalties, while a not_attempted code points at the stored method, your account, or the request instead. |
retry_with_new_payment_method | The current payment method cannot be used. Ask the customer to provide a different one. |
retry_after_delay | The issue may resolve on its own (e.g. insufficient funds). Wait before retrying. |
retry_immediately | The failure is transient and no decision was reached. Your integration may retry at once only after authentication_required, authentication_unavailable or processor_rate_limited, where the request is known to have been turned away before authorisation — for authentication_required, retry with 3D Secure rather than unchanged. The other codes carrying this strategy — processor_timeout, generic_processor_error, generic_issuer_error — cover cases the processor never resolved, so the payment may already have been processed: check its current status and reconcile before retrying, or your customer can be charged twice. |
For subscription payments, ODUS uses the retry strategy automatically when scheduling retries. When a subscription renewal fails with authentication_unavailable or processor_rate_limited, ODUS also resends it unchanged within minutes, before your retry policy starts. See the retry policy guide for details.
Example response
A failed authorization returns the outcome on the latest transaction:
{
"id": "pay_a1b2c3",
"status": "open",
"displayStatus": "failed",
"latestTransaction": {
"outcome": {
"status": "failure",
"errorCode": "insufficient_funds",
"errorMessage": "The payment method has insufficient funds.",
"safeErrorMessage": "There aren't enough funds available for this payment. Please use a different payment method.",
"declineType": "soft_decline",
"retryStrategy": "retry_after_delay",
"processorCode": "card_declined",
"processorMessage": "Your card has insufficient funds.",
"specialCode": null
}
}
}
When using a publishable key, the response only includes status, safeErrorMessage, retryStrategy, and specialCode. Sensitive fields like errorCode, errorMessage, declineType, processorCode, and processorMessage are omitted.
Related
- Payment Statuses — lifecycle states and boolean flags on the payment object.
- Subscription Retry Policy — how ODUS automatically retries failed subscription payments based on the retry strategy.