> ## 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.

# Webhook event types - Payments API

> Every webhook event RadiumOne can send and the exact payload shape for each — the single payload reference for the whole site.

This page is the **only** payload reference for webhooks — the API reference links
here instead of duplicating field tables. Every event shares one envelope; the
`data` shape underneath depends on which family the event belongs to.

## Event families

| Family | Meaning | Events |
| - | - | - |
| `authorization.*` | Funds **held**, nothing captured yet | `created`, `declined`, `failed`, `expired`, `voided`, `reversed`, `capture_declined`, `capture_failed`, `void_declined`, `void_failed` |
| `payment.*` | Money **in** — a capture succeeded | `captured`, `voided`, `reversed`, `declined`, `failed`, `void_declined`, `void_failed` |
| `refund.*` | Money **out** | `captured`, `voided`, `reversed`, `declined`, `failed`, `void_declined`, `void_failed` |
| `settlement.*` | A settlement batch changed state | `settled`, `rejected`, `discarded` |

A few notes on how these map to what you did:

* **An operation is never the namespace.** A successful `capture` moves a row from
  `authorization.*` into `payment.*` (`payment.captured`); a **failed** capture leaves
  the hold intact and reports under `authorization.*` instead
  (`authorization.capture_declined` / `authorization.capture_failed`) — the row never
  became a payment, so it isn't reported as one.
* Every other operation (`void`, `refund`) reports both its success and its outcome
  events inside the same namespace it already belongs to.
* A transaction moving through settlement (`SUBMITTED` → `SETTLING` → `SETTLED`) does
  **not** re-fire `payment.captured` or `refund.captured` at each stage — those statuses
  all map to the same event you already received. Track a transaction's progress into
  settlement via `references.settlement_batch_id` plus the batch-level `settlement.*`
  event, not a per-stage payment event.
* `chargeback.created` is reserved for a future release. It's declared so you can see
  it coming, but it's never emitted today — don't build against it yet.

## Envelope

Every delivery shares this top-level shape, regardless of family:

<ResponseField name="id" type="string" required>
  Event ID, for example `evt_…`. Use this to dedupe retried and duplicate deliveries — see [Retries, ordering, and duplicates](/payments-api/webhooks/retries-and-ordering).
</ResponseField>

<ResponseField name="type" type="string" required>
  The event type, for example `payment.captured`. See [Event families](#event-families) for the full catalogue.
</ResponseField>

<ResponseField name="created_at" type="string" required>
  ISO 8601 timestamp. This is the **ordering token** — see [Ordering](/payments-api/webhooks/retries-and-ordering#ordering).
</ResponseField>

<ResponseField name="payload_version" type="string" required>
  Schema version for the `data` block. Currently always `"v1"`.
</ResponseField>

<ResponseField name="data" type="object" required>
  The event body. Shape depends on the family — see [Transaction events](#transaction-events-authorization-payment-refund) and [Settlement events](#settlement-events).
</ResponseField>

<Info>
  **Unknown-field tolerance.** Treat every field as optionally growing over time:
  RadiumOne may add new fields to `data` in a future release without bumping
  `payload_version`. Parse defensively — ignore fields you don't recognize rather than
  rejecting the payload.
</Info>

## Transaction events (`authorization.*`, `payment.*`, `refund.*`)

Shared by every `authorization.*`, `payment.*`, and `refund.*` event.

<AccordionGroup>
  <Accordion title="Transaction event fields">
    <ResponseField name="data.object" type="string" required>
      Always `"transaction"` for this family. Lets you branch on payload shape without parsing the event type string.
    </ResponseField>

    <ResponseField name="data.transaction" type="object" required>
      <Expandable title="transaction fields">
        <ResponseField name="id" type="string" required>Transaction UUID.</ResponseField>
        <ResponseField name="type" type="string" required>`PURCHASE`, `AUTHORIZE`, or `REFUND` — the type of the row this event reports on. Capture and void never produce their own row; they change the `status` of the existing `PURCHASE`/`AUTHORIZE` row (`type` stays unchanged) and report under `authorization.*`/`payment.*` events instead.</ResponseField>
        <ResponseField name="status" type="string" required>Transaction lifecycle status — same enum as [Payment lifecycle](/payments-api/payment-lifecycle).</ResponseField>

        <ResponseField name="amount" type="object" required>
          <Expandable title="amount fields">
            <ResponseField name="currency" type="string" required>ISO 4217 currency code.</ResponseField>
            <ResponseField name="value" type="string" required>Amount in the currency's minor units, as a numeric **string** (avoids floating-point precision loss on large values).</ResponseField>
            <ResponseField name="minor_units" type="integer" required>The currency's decimal-place **exponent** (`2` for SGD/USD, `0` for JPY) — not a copy of `value`. `{"currency":"SGD","value":"12000","minor_units":2}` means SGD 120.00.</ResponseField>
          </Expandable>
        </ResponseField>

        <ResponseField name="created_at" type="string | null">When the transaction occurred (ISO 8601) — not when this event was raised.</ResponseField>
        <ResponseField name="leg" type="string | null">`PAYMENT` / `LOYALTY` on a paired loyalty sale; `null` on a standalone sale.</ResponseField>

        <ResponseField name="redemption" type="object | null">
          Loyalty split, present only on a paired redemption sale — see [Loyalty redemptions](#loyalty-redemptions) below.

          <Expandable title="redemption fields">
            <ResponseField name="status" type="string" required>`APPROVED` (points moved) / `DECLINED` (host refused, or moved and reversed) / `NOT_ATTEMPTED` (no confirmed redemption yet).</ResponseField>
            <ResponseField name="card_amount" type="object" required>Residual amount charged to the card. Same shape as `transaction.amount`.</ResponseField>
            <ResponseField name="points_amount" type="object" required>Value redeemed from loyalty. Same shape as `transaction.amount`.</ResponseField>
          </Expandable>
        </ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.merchant" type="object" required>
      <Expandable title="merchant fields">
        <ResponseField name="id" type="string" required>Your merchant UUID.</ResponseField>
        <ResponseField name="outlet_id" type="string | null">Outlet that owns the channel this transaction came through.</ResponseField>
        <ResponseField name="channel_id" type="string | null">Originating channel UUID.</ResponseField>
        <ResponseField name="channel_type" type="string | null">Channel type, for example `ECOMMERCE_SITE` or `PHYSICAL_STORE`.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.references" type="object" required>
      <Expandable title="references fields">
        <ResponseField name="order_reference" type="string | null">Your own order identifier, echoed back if you sent one.</ResponseField>
        <ResponseField name="request_id" type="string" required>Your idempotency key for the original request.</ResponseField>
        <ResponseField name="original_transaction_id" type="string | null">The transaction this one acts on (a refund's original sale, a void's authorization). `null` on an original sale.</ResponseField>
        <ResponseField name="transaction_group_id" type="string | null">Groups the payment and loyalty legs of one paired sale. `null` on a standalone sale.</ResponseField>
        <ResponseField name="settlement_batch_id" type="string | null">The settlement batch this transaction belongs to — join this against the corresponding `settlement.*` event. `null` before the transaction is assigned to a batch.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.processor" type="object" required>
      <Expandable title="processor fields">
        <ResponseField name="response_code" type="string | null">Verbatim host/acquirer response code (`"00"` = approved). Useful for support tickets and reconciliation footnotes — branch your application logic on `transaction.status`, never on this code.</ResponseField>
      </Expandable>
    </ResponseField>

    <Warning>
      **Any 2xx response is a result you branch on `status`, never on `response_code`.** See [Handle the result](/payments-api/webhooks/overview#build-your-handler) and the shared [transaction status table](/payments-api/payment-lifecycle#statuses).
    </Warning>
  </Accordion>
</AccordionGroup>

## Settlement events

<AccordionGroup>
  <Accordion title="Settlement event fields">
    <ResponseField name="data.object" type="string" required>
      Always `"settlement_batch"` for this family.
    </ResponseField>

    <ResponseField name="data.batch" type="object" required>
      <Expandable title="batch fields">
        <ResponseField name="id" type="string" required>Settlement batch UUID.</ResponseField>
        <ResponseField name="number" type="string" required>Composite batch identity as presented to the acquirer.</ResponseField>
        <ResponseField name="date" type="string" required>Calendar day the batch **covers** (ISO 8601) — not when it settled.</ResponseField>
        <ResponseField name="state" type="string" required>`SETTLEMENT_CONFIRMED` / `SETTLEMENT_REJECTED` / `RECONCILED`.</ResponseField>
        <ResponseField name="tx_count" type="integer" required>Transactions in the batch.</ResponseField>
        <ResponseField name="totals_provisional" type="boolean" required>`true` while the batch is still open — totals are running, not authoritative, until it closes.</ResponseField>
        <ResponseField name="closed_at" type="string | null">When the batch closed (ISO 8601).</ResponseField>
        <ResponseField name="confirmed_at" type="string | null">When the acquirer confirmed settlement (ISO 8601).</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.merchant" type="object" required>
      <Expandable title="merchant fields">
        <ResponseField name="id" type="string" required>Your merchant UUID.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.terminal" type="object" required>
      <Expandable title="terminal fields">
        <ResponseField name="device_id" type="string | null">The settling device's UUID. This webhook is the only supported way to discover a `device_id`.</ResponseField>
      </Expandable>
    </ResponseField>

    <ResponseField name="data.totals" type="object" required>
      <Expandable title="totals fields">
        <ResponseField name="presented" type="object" required>Net total in the transaction currency. Same shape as a transaction `amount`.</ResponseField>
        <ResponseField name="settled" type="object | null">Net total in the settlement currency. `null` until the batch is confirmed.</ResponseField>
      </Expandable>
    </ResponseField>
  </Accordion>
</AccordionGroup>

See [Settlement and reconciliation](/payments-api/settlement-and-reconciliation) for
how these events fit into batch tracking.

## Loyalty redemptions

On a paired sale (a card payment with a loyalty redemption), RadiumOne sends **one event per redemption sale** — the loyalty outcome rides inside that same event's payload, not as a separate `loyalty.*` event. The transaction event's `data.transaction.leg` tells you which acquirer leg this row settles to (`PAYMENT` or `LOYALTY`), and `data.transaction.redemption` carries the split:

* **`APPROVED`**: points moved. `points_amount` was redeemed from loyalty; `card_amount` is the residual charged to the card.
* **`DECLINED`**: the loyalty host refused the redemption, or it moved and was later reversed. The payment leg can still be a healthy `AUTHORIZED` — a declined redemption does **not** by itself mean the sale failed. See [Pay with points](/payments-api/payment-methods/uob-rewards/pay-with-points) for the degraded-sale behavior this reports.
* **`NOT_ATTEMPTED`**: no confirmed redemption — either none was attempted, or its outcome isn't known yet.

`leg` and `redemption` are `null` on a standalone (non-redemption) sale. A **full redemption** — points cover the whole sale, no card charge — reports as a single event with `leg: "LOYALTY"` and `data.references.transaction_group_id: null` (there's no paired card leg to group with). If a loyalty leg is later reversed, that reversal is its own separate event, not folded into the original.

<Info>
  Amounts are always integers in the currency's minor unit. For example, `5000` for `SGD` means SGD 50.00.
</Info>

## Fields RadiumOne doesn't publish yet

The gateway's internal event payload also carries `processor.rrn`, `processor.auth_code`,
and `terminal.acquirer_id`. These aren't published today, pending a decision on exposing
acquirer- and host-level identifiers to merchants. If your integration needs one of
these, [contact support](/resources/support) rather than relying on an undocumented
field appearing in a future payload — additions here are additive, but nothing
guarantees a specific field ships on a specific date.

## Next steps

<Columns cols={2}>
  <Card title="Verify webhook signatures" icon="key-round" href="/payments-api/webhooks/verify-signatures">
    Algorithm, key encoding, and generated verifier code.
  </Card>

  <Card title="Retries, ordering, and duplicates" icon="refresh-cw" href="/payments-api/webhooks/retries-and-ordering">
    Delivery semantics, the backoff schedule, and endpoint suspension.
  </Card>
</Columns>
