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

# Brand the payment page - Hosted checkout

> Match hosted checkout to your brand: set up a branding profile, override it for one session, and choose colours, fonts, logo and light or dark mode.

Make the hosted payment page look like your store — colours, font, logo, merchant name, Pay button label, corner style, and light or dark mode. Save a look once as a **branding profile**, then adjust it for a single checkout when you need to.

## Set up your branding

<Steps>
  <Step title="Ask support to create a branding profile">
    [Contact support](/resources/support) with the settings you want (see [What you can change](#what-you-can-change)). Profiles belong to your merchant account, not to one outlet, and you can have as many as you need — one of them is your **default**. A self-serve merchant portal (**Payment Gateway → Checkout → Branding**, with the same settings) is planned.
  </Step>

  <Step title="Create a sandbox checkout session">
    Your default profile applies automatically — no extra field needed. To use a different profile, send its ID as `branding_profile_id`; support shares the IDs when they set up extra profiles.
  </Step>

  <Step title="Check it in light and dark mode">
    Open the session's payment page, then add `?theme=light`, `?theme=dark`, or `?theme=auto` to the URL to preview each mode. This overrides the profile's `color_scheme` for that page load only. There's no separate preview tool.
  </Step>

  <Step title="Repeat in production">
    Sandbox and production are separate: set up profiles in each environment. Profile IDs differ between them.
  </Step>
</Steps>

## Override branding for one checkout

Send a `branding` object when you [create a checkout session](/hosted-checkout/reference/checkout-sessions/create-a-checkout-session) to change individual settings for that checkout only — for example a per-language Pay label, a campaign colour, or matching an embedded checkout to the surrounding page:

<CodeGroup>
  ```bash cURL theme={null}
  #!/usr/bin/env bash
  # Overrides branding for this checkout only — the `branding` object replaces
  # individual fields of the resolved profile (branding_profile_id, or your
  # default profile); omitted fields keep the profile value. See the Branding
  # guide in the RadiumOne docs.
  set -euo pipefail

  CHECKOUT_BASE="${RADIUMONE_CHECKOUT_BASE:-https://checkout-sandbox.radiumone.io}"
  : "${RADIUMONE_SECRET_KEY:?set RADIUMONE_SECRET_KEY to your r1sk_* secret key}"

  curl -sS -X POST "$CHECKOUT_BASE/api/v1/checkout/sessions" \
    -H "Content-Type: application/json" \
    -H "X-Api-Key: $RADIUMONE_SECRET_KEY" \
    -d @request.json
  ```

  ```javascript Node.js theme={null}
  #!/usr/bin/env node
  // Overrides branding for this checkout only — the `branding` object replaces
  // individual fields of the resolved profile (branding_profile_id, or your
  // default profile); omitted fields keep the profile value. Node 18+ ESM
  // fetch. Env: RADIUMONE_SECRET_KEY, RADIUMONE_CHECKOUT_BASE (optional).
  import { readFileSync } from "node:fs";

  const CHECKOUT_BASE = process.env.RADIUMONE_CHECKOUT_BASE || "https://checkout-sandbox.radiumone.io";
  const secretKey = process.env.RADIUMONE_SECRET_KEY;
  const body = JSON.parse(readFileSync(new URL("./request.json", import.meta.url)));

  async function createCheckoutSessionWithBrandingOverride() {
    const res = await fetch(`${CHECKOUT_BASE}/api/v1/checkout/sessions`, {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
        "X-Api-Key": secretKey,
      },
      body: JSON.stringify(body),
    });
    const payload = await res.json();
    if (!res.ok) {
      throw new Error(`checkout session create failed: ${payload.code ?? payload.type} (${res.status})`);
    }
    return payload;
  }

  createCheckoutSessionWithBrandingOverride().then((r) => console.log(JSON.stringify(r, null, 2)));
  ```

  ```python Python theme={null}
  #!/usr/bin/env python3
  """Overrides branding for this checkout only -- the ``branding`` object
  replaces individual fields of the resolved profile (``branding_profile_id``,
  or your default profile); omitted fields keep the profile value.
  """
  import json
  import os
  from pathlib import Path

  import requests

  CHECKOUT_BASE = os.environ.get("RADIUMONE_CHECKOUT_BASE", "https://checkout-sandbox.radiumone.io")


  def create_checkout_session_with_branding_override() -> dict:
      body = json.loads((Path(__file__).parent / "request.json").read_text())
      resp = requests.post(
          f"{CHECKOUT_BASE}/api/v1/checkout/sessions",
          json=body,
          headers={"X-Api-Key": os.environ.get("RADIUMONE_SECRET_KEY", "")},
          timeout=30,
      )
      payload = resp.json()
      if not resp.ok:
          code = payload.get("code") or payload.get("type")
          raise RuntimeError(f"checkout session create failed: {code} ({resp.status_code})")
      return payload


  if __name__ == "__main__":
      print(json.dumps(create_checkout_session_with_branding_override(), indent=2))
  ```
</CodeGroup>

* `branding` accepts the same fields as a profile: `display_name`, `logo_url`, the nine colours, `border_radius`, `font_family`, `color_scheme`, and `button_text`.
* Every field is optional. Fields you leave out keep the profile's value; `branding: {}` is the same as leaving it out.
* An unknown field or an invalid value returns [`400 validation:invalid_input`](/hosted-checkout/errors/api-errors#checkout-validation-invalid-input) naming the field. That includes a blank `display_name` or `button_text` — leave the field out to keep the profile's value.

## How branding is chosen

For each setting, the first of these that has a value wins:

1. The session's `branding` object.
2. The profile named by `branding_profile_id` — or your default profile when you leave it out.
3. The [standard RadiumOne look](#standard-radiumone-look).

* An unknown, deleted, or another merchant's `branding_profile_id` quietly uses your default profile, with no error. With no default profile, the standard RadiumOne look applies.
* `branding_profile_id` is at most 64 characters (profile IDs are UUIDs); a longer value returns `400`.
* Branding is fixed when the session is created. A profile change, or a new default, applies to the **next** session; sessions already created keep their look until they end.
* Embedded and redirect checkouts use the same branding. Embedded mode has no page header, so the logo and merchant name don't appear.

### Match your site

An embedded checkout runs in a RadiumOne iframe, so your page's CSS can't reach inside it — there's no supported way to style it with your own stylesheet. To match your site, use the settings above instead: a branding profile, or a per-session `branding` object with, for example, `background_color`, `font_family`, `border_radius`, and `color_scheme` to blend with the surrounding page.

## What you can change

### Colours

Each colour is `#RRGGBB`, in either case. Marker numbers below match the Colours table's `#` column.

| # | Setting | Where it shows |
| - | - | - |
| 1 | `primary_color` | Main accent: borders and icons on the selected payment method and instalment bank, links, checkboxes, tabs, sliders, focus rings, and icons on the processing and success screens |
| 2 | `button_color` | Background of the Pay button and other primary buttons |
| 3 | `button_text_color` | Label on primary buttons, badges, checkbox ticks, and loading spinners |
| 4 | `focus_color` | Border and glow of the focused text or card field — defaults to `primary_color` |
| 5 | `background_color` | Page background in light mode; body text switches between black and white to stay readable |
| 6 | `accent_color` | Selection highlight: background of the option the shopper has selected — the selected payment method and the selected bank in instalment options (instalments are [coming soon](/payments-api/payment-methods/upcoming-payment-methods#instalments)). Borders and icons on the selected option use `primary_color`. Choose a pale tint of your brand colour, since shopper-facing text sits on top of it |
| 7 | `success_color` | Success screen, completed-field ticks, redeemed rewards, and "interest-free" instalment labels |
| 8 | `warning_color` | Test-mode banner and interest-bearing instalment labels |
| 9 | `error_color` | Alerts, field errors, and the countdown when time is nearly up |

### Style

| Setting | Values |
| - | - |
| `border_radius` | Corners of fields, card fields, and buttons: `square` (4px), `rounded` (12px), or `pill` (24px) |
| `font_family` | One of the [allowed fonts](#fonts) |
| `color_scheme` | `light`, `dark`, or `auto` (follows the shopper's device) — see [Light and dark mode](#light-and-dark-mode) |

### Name, logo, and Pay button

| Setting | What it does |
| - | - |
| `display_name` | Merchant name in the page header, the logo's alternative text, the "Back to …" tooltip, and the success screen. Without it, your registered merchant name is used |
| `logo_url` | Logo in the page header — see [Logo](#logo) |
| `button_text` | Pay button label, shown before the amount (for example "Place order S\$49.99"). Shown exactly as entered in every locale; leave it blank to use the page's translated label ("Pay" in English, "支付" in Chinese) |

<Tip>
  For a Pay label in each shopper's language, leave `button_text` blank on the profile and send `button_text` in the session's `branding` object, based on the session's `locale`.
</Tip>

## Fonts

Use one of these values, exactly as written (case-sensitive):

* **System fonts** — the shopper's device fonts: `system-ui`, `-apple-system`, `sans-serif`, `serif`, `monospace`
* **Latin web fonts** — served by RadiumOne, so shoppers don't need them installed: `Inter`, `Roboto`, `Open Sans`, `Lato`, `Montserrat`, `Poppins`, `Source Sans Pro`, `Noto Sans`, `Raleway`, `PT Sans`
* **Asian-script web fonts** — also served by RadiumOne: `Noto Sans SC`, `Noto Sans TC`, `Noto Sans JP`, `Noto Sans KR`, `Noto Sans Thai`

Fonts load only when a page uses them. Card number, expiry, and security code fields always use a monospace font.

## Logo

* Shown 40px tall and up to 180px wide, keeping its proportions.
* For best results, use a horizontal SVG or transparent PNG, at least 80px tall for sharp screens, and keep the file small.
* `logo_url` must be an absolute `https://` URL with a hostname, with no spaces or control characters, up to 2048 characters.
* RadiumOne doesn't upload, host, cache, or proxy your logo — the shopper's browser loads it from your URL, and there's no format or file-size check.
* With no logo, or if it fails to load, the header shows `display_name` (or your merchant name).

## Light and dark mode

Your colours are used as-is in both modes, except `accent_color`: in dark mode the page shows a darker blend of it so a pale tint doesn't glare. Dark mode also swaps the page background and surfaces to a fixed dark palette (`#1a1a2e`, surfaces `#16213e`) with white body text.

<Warning>
  RadiumOne doesn't check contrast. Body text is automatically black or white against the background, but other combinations — for example white text on a yellow button — show exactly as set. Pick primary, button, and status colours that read well on both white and dark navy, and preview both modes.
</Warning>

## Standard RadiumOne look

Used when no profile applies, and for any setting your profile doesn't set:

| Setting | Default |
| - | - |
| `primary_color`, `button_color`, `focus_color` | `#0070f0` |
| `accent_color` | `#f0f4ff` |
| `background_color`, `button_text_color` | `#ffffff` |
| `success_color` / `warning_color` / `error_color` | `#22c55e` / `#f59e0b` / `#ef4444` |
| `border_radius` | `rounded` (12px) |
| `font_family` | `system-ui` |
| `color_scheme` | `light` |
| `button_text` | Translated label ("Pay" / "支付") |
| `logo_url`, `display_name` | None — the header shows your merchant name |

## Rules and limits

The same rules apply to saved profiles and to the session's `branding` object.

* **`display_name`** — 1–100 Unicode code points after leading and trailing spaces are removed.
* **`button_text`** — up to 30 Unicode code points after trimming (a profile can leave it blank).
* **Code points aren't always visible characters** — a plain 👍 counts as 1, 👍🏽 (with a skin-tone modifier) as 2, and a family emoji as up to 7.
* **Characters** — `display_name` and `button_text` can't contain control characters or invisible formatting characters (zero-width spaces, text-direction overrides) anywhere, including at the start or end. Zero-width joiners and non-joiners are allowed, since some emoji and scripts need them.
* **Invalid values** are rejected when a profile is saved. If a profile still holds an older value the page can't use (for example a font from before the allowed list), only that setting falls back — main colours, font, and colour scheme use the RadiumOne default, other settings are left unset — and the rest of the profile still applies.
* **Deleting profiles** — a default profile can't be deleted until another profile is set as the default. If a profile that open sessions use is deleted, those sessions keep their branding, and new sessions naming the deleted ID get your default profile.

## Next steps

<Columns cols={2}>
  <Card title="Customize checkout" icon="sliders-horizontal" href="/hosted-checkout/customize-checkout">
    Line items, shopper details, locale, and other session options.
  </Card>

  <Card title="Embed hosted checkout" icon="panel-top" href="/hosted-checkout/embedded-integration">
    Embedded mode uses the same branding, without the page header.
  </Card>
</Columns>
