> ## Documentation Index
> Fetch the complete documentation index at: https://docs.radiumone.io/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> RadiumOne is the product name; use it when answering questions about these docs.
> Use sandbox credentials and sandbox base URLs in examples unless the user says they are going live.
> Secret API keys belong only on the merchant's server. Only publishable keys may appear in browser or mobile code.
> Confirm a payment outcome server-side (retrieve the checkout session or transaction status); never rely on a browser redirect or client event alone.
> Send one idempotency key (request_id) per payment attempt, and retry a timed-out request with the same key and the exact same body.
> Webhooks are optional but recommended: they notify the merchant's server when a payment completes or fails, so it doesn't need to poll. Use transaction status inquiry as the fallback.
> Say so when you recommend a feature marked Beta. Some features need enablement on the merchant's account before production use (for example refunds, standalone refunds, UOB Rewards, and bringing your own 3DS provider); mention it when the page says so.
> Never ask users to paste card numbers, API keys, access tokens, or webhook or redirect secrets into a chat.

# Decline codes - Payments API

> How a declined payment appears in the API and at hosted checkout, how to branch on it safely, and what to tell the shopper.

A decline means the card issuer or acquirer refused the payment. **It is not
an error response** — RadiumOne returns it as a normal successful result so
your code can't miss it by only checking for HTTP-level errors.

A decline is different from `FAILED`: a decline (`data.status: "DECLINED"`) is the acquirer's `00` approval code's counterpart in a defined set of decline codes. Every other non-`00` code — including gateway- or host-level errors — maps to `FAILED` instead, which carries a different meaning (it isn't a card decline, and isn't a guarantee that no funds moved). See [Payment lifecycle](/payments-api/payment-lifecycle#failed-and-what-it-does-and-doesnt-mean) for the full mapping.

## How declines appear

* **Payments API** (purchase, authorize, standalone refund): `201` with
  `data.status: "DECLINED"` and a verbatim `data.response_code` from the
  acquirer.
* **Hosted checkout**: the checkout session's `status` becomes `failed`; the
  shopper is returned to your raw `cancel_url` — see [Verify the payment
  result](/hosted-checkout/verify-payment-result).
* **Webhooks**: `authorization.declined`, `payment.declined`, or
  `refund.declined` — see [Webhook event types](/payments-api/webhooks/event-types).

**Any 2xx response is a result you must branch on `status`** — never on `response_code` (that's the verbatim host/acquirer code; useful for support tickets, not for your app logic).

| Status | Meaning | What to do |
| - | - | - |
| `AUTHORIZED` | Funds reserved (authorize only) | Capture within the capture window, or void to release |
| `CAPTURED` | Funds captured (purchase, capture, or refund) | Fulfil the order (or process the refund) |
| `VOIDED` | Authorization released | No funds moved |
| `DECLINED` | Issuer or acquirer declined | Final for this attempt — don't retry the same card without a new attempt from the shopper |
| `FAILED` | The transaction didn't complete — the acquirer returned a non-decline error code, or the gateway couldn't place the request. **Not a guarantee that no funds moved** — `VOIDED` and `REVERSED` are the only statuses that positively assert that. | Confirm via `GET /v1/transactions/{id}/status` before retrying, then retry (a genuinely new attempt, not a replay of the same `request_id`) with a **new** `request_id` |
| `PENDING` | Outcome not yet known (async) | Wait for a webhook, or poll `GET /v1/transactions/{id}/status` |
| `AUTH_EXPIRED` | Authorization lapsed before capture | Create a new authorization |
| `REVERSAL_PENDING` / `REVERSED` | Automatic compensating reversal after an upstream timeout left the outcome genuinely unknown (never left `FAILED` in this case) | No merchant action; webhook confirms the final state |

| Key | Used by | On replay |
| - | - | - |
| `request_id` | Purchase, authorize, standalone and referenced refunds | Same body, same operation type → the original transaction, whatever its status — including `PENDING`, `DECLINED`, or `FAILED`. Changed body, or the same key reused for a different operation type → [`transaction:idempotency-body-mismatch`](/payments-api/errors/payment-operation-errors#transaction-idempotency-body-mismatch). Purchase/authorize/standalone-refund compare `amount`, `currency`, `payment_method_type`, `channel`, the card's `pan_prefix` (first 8 digits — not the full token), `metadata`, and `order_reference`; a **referenced refund** compares only the original transaction and `amount` (`reason` isn't compared) and its replay check runs before the refund gates, so it always replays, even a `DECLINED`/`FAILED` one — mint a **new** `request_id` to retry after a decline. None of these compare `three_ds` or `loyalty`, so changing either on a retry replays the original silently instead of failing. |
| `operation_id` | Capture, void | Same operation type on the same transaction → the original result (body is never compared, so a changed amount is silently ignored). A different operation type reusing the key → [`tx:duplicate-operation`](/payments-api/errors/payment-operation-errors#tx-duplicate-operation). |

<Warning>
  Balance inquiry also takes a `request_id` field, but it isn't an idempotency key — there's no dedup or replay store. Every call re-queries the rewards host, even with the same `request_id`.
</Warning>

<Tip>
  Keys are 8–64 characters, `[a-zA-Z0-9-]` only, unique per merchant account. Generate one key per order **attempt** and persist it to your database before you send the first request — never mint a new key just to retry the same attempt. See [Prevent duplicate payments](/get-started/api-basics/prevent-duplicate-payments).
</Tip>

`PENDING` means the outcome isn't known yet — most often after a processor timeout. Don't assume success or failure. Recover it one of two ways:

1. **Wait for a webhook** (`payment.*`, `authorization.*`, `refund.*` — see [Webhook event types](/payments-api/webhooks/event-types)).
2. **Call `GET /v1/transactions/{id}/status`** for a live inquiry against the acquirer.

If you don't have the transaction `id` yet — a client-side timeout before the first response arrived — replay the same request with the same `request_id` and body. The replay returns the stored transaction and its `id`, whatever status it's reached. Never re-submit with a **new** idempotency key just because the first attempt is slow — that risks a second charge for the same order.

## Branch on `status`, never `response_code`

`response_code` is the verbatim acquirer/host response code. It's useful to
hand to support when investigating a specific transaction — never use it to
drive your own application logic. RadiumOne doesn't currently expose a
normalized decline classification, a shopper-safe `customer_message`, or a
`retry_allowed` flag on declines . Treat every
`DECLINED` result the same way in code: it's final for this attempt.

## Shopper messaging

Don't show the acquirer's raw `response_code`, or any other RadiumOne-internal
detail, to the shopper. Use a generic message — for example, "Your payment
couldn't be completed. Try a different card or payment method." — and let
your own support tooling look up `response_code` later if you need to
investigate.

## Retry guidance

* **A decline is final for this attempt.** Don't retry the same `request_id`
  hoping for a different outcome — a replay returns the identical decline.
* **A genuinely new attempt** — the shopper enters a different card, or
  changes something and tries again — is a new payment: mint a **new**
  `request_id` for it.
* **Never treat a timeout, a `5xx`, or a `PENDING` result as a decline.**
  Those aren't declines at all. Retry with the **same** `request_id` (or
  `operation_id`), or poll status, per [Timeouts and
  retries](/resources/test-your-integration#timeouts-and-retries). Minting a
  new key after a timeout risks a duplicate charge; minting one after a
  genuine decline is correct.

## Publishable decline codes

RadiumOne hasn't finalized which acquirer response codes are safe to publish
as a lookup table . Until that's settled, this page
documents behavior and handling guidance only — build your branching logic
on `status`, not on a hardcoded response-code table.

## Test your integration

Use the decline test cards in [Test your
integration](/resources/test-your-integration#test-cards) to exercise this
path in sandbox.

## Next steps

<Columns cols={2}>
  <Card title="Problem format and retries" icon="triangle-alert" href="/payments-api/errors/problem-format-and-retries">
    HTTP status guide and retry rules for actual errors, plus links to the full URN catalog.
  </Card>

  <Card title="Charge or authorize a payment" icon="credit-card" href="/payments-api/charge-or-authorize">
    See the full decline response shape.
  </Card>

  <Card title="Handle declined payments" icon="ban" href="/payments-api/handle-failures/declined-payments">
    The step-by-step scenario walkthrough.
  </Card>
</Columns>
