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

# Content Security Policy - Elements SDK

> The Content Security Policy directives Elements needs for card fields and 3D Secure, with example policies, a safe rollout, and fixes for common violations.

If your site sends a Content Security Policy (CSP), it has to allow RadiumOne's origins, or the browser blocks Elements. What you add depends on what you use:

| You use | Add | On which pages |
| - | - | - |
| Card fields | [`script-src` and `frame-src`](#card-fields) for the Elements CDN | Every page that loads Elements |
| 3D Secure, `challengePresentation: "iframe"` | [`connect-src` and `frame-src`](#iframe-challenge) | Pages that call `threeDS.authenticate()` |
| 3D Secure, `challengePresentation: "redirect"` | [`connect-src` and `form-action`](#redirect-challenge) | Pages that call `threeDS.authenticate()`, and your 3DS return page |
| 3D Secure, `challengePresentation: "auto"` | The iframe directives — plus the redirect directives if you pass `returnUrl`, since `auto` can then choose a redirect | As above |

Add the 3D Secure directives only on those routes, not site-wide. For the CDN script's `integrity` hash, see [Subresource Integrity](/elements/install-and-load#subresource-integrity).

## Card fields

| Directive | Production | Sandbox |
| - | - | - |
| `script-src` | `https://js.radiumone.io` | `https://js-sandbox.radiumone.io` |
| `frame-src` | `https://js.radiumone.io` | `https://js-sandbox.radiumone.io` |

Card fields need no `connect-src`: card data and the card-binding call stay inside the RadiumOne card iframe, so your page's policy isn't involved.

## 3D Secure

3D Secure runs on your page rather than inside the card iframe, so it needs more: your page calls the RadiumOne API, and shows the bank's challenge in an iframe or sends the shopper to it.

### Iframe challenge

| Directive | Production | Sandbox |
| - | - | - |
| `connect-src` | `https://api.radiumone.io` | `https://api-sandbox.radiumone.io` |
| `frame-src` | `https://api.radiumone.io`, `https://centinelapi.cardinalcommerce.com`, `https:` | `https://api-sandbox.radiumone.io`, `https:` |

<Warning>
  **`connect-src` is the directive to check first.** The SDK reads the challenge result from the API with `GET /gateway/v1/3ds/status`. If `connect-src` blocks that call, a shopper can complete the challenge at their bank and the SDK still rejects with `urn:radiumone:three-ds:challenge-timeout` after 5 minutes.
</Warning>

Banks host their challenge pages on their own domains, which can't be listed in advance — so `frame-src` needs `https:`. That allows any HTTPS page to be framed on these routes. It doesn't let a framed page fake a result: the SDK only accepts a completion signal sent from the API origin, and even then only as a prompt to read the status from the API.

### Redirect challenge

| Directive | Production | Sandbox |
| - | - | - |
| `connect-src` | `https://api.radiumone.io` | `https://api-sandbox.radiumone.io` |
| `form-action` | `https://api.radiumone.io`, `https:` | `https://api-sandbox.radiumone.io`, `https:` |

The redirect challenge is a full-page form post to the bank, so it needs `form-action` instead of `frame-src`. Your 3DS return page loads the SDK and calls `threeDS.resume()`, so it needs `script-src` for the CDN and the same `connect-src`. See [Challenge presentation](/elements/three-d-secure/challenge-presentation).

## Example policies

Merge these with your existing policy — they show only the sources Elements needs, plus the recommended [`frame-ancestors`](#frame-ancestors). Each directive is on its own line for readability; send the header as a single line.

<Tabs>
  <Tab title="Production">
    <CodeGroup>
      ```http Card fields only theme={null}
      Content-Security-Policy:
        script-src https://js.radiumone.io;
        frame-src https://js.radiumone.io;
        frame-ancestors 'self'
      ```

      ```http Checkout with 3D Secure theme={null}
      Content-Security-Policy:
        script-src https://js.radiumone.io;
        frame-src https://js.radiumone.io https://api.radiumone.io https://centinelapi.cardinalcommerce.com https:;
        connect-src https://api.radiumone.io;
        form-action https://api.radiumone.io https:;
        frame-ancestors 'self'
      ```

      ```http 3DS return page theme={null}
      Content-Security-Policy:
        script-src https://js.radiumone.io;
        connect-src https://api.radiumone.io
      Referrer-Policy: no-referrer
      ```
    </CodeGroup>
  </Tab>

  <Tab title="Sandbox">
    <CodeGroup>
      ```http Card fields only theme={null}
      Content-Security-Policy:
        script-src https://js-sandbox.radiumone.io;
        frame-src https://js-sandbox.radiumone.io;
        frame-ancestors 'self'
      ```

      ```http Checkout with 3D Secure theme={null}
      Content-Security-Policy:
        script-src https://js-sandbox.radiumone.io;
        frame-src https://js-sandbox.radiumone.io https://api-sandbox.radiumone.io https:;
        connect-src https://api-sandbox.radiumone.io;
        form-action https://api-sandbox.radiumone.io https:;
        frame-ancestors 'self'
      ```

      ```http 3DS return page theme={null}
      Content-Security-Policy:
        script-src https://js-sandbox.radiumone.io;
        connect-src https://api-sandbox.radiumone.io
      Referrer-Policy: no-referrer
      ```
    </CodeGroup>
  </Tab>
</Tabs>

## Origins per environment

| Origin | Production | Sandbox |
| - | - | - |
| Elements CDN | `https://js.radiumone.io` | `https://js-sandbox.radiumone.io` |
| API | `https://api.radiumone.io` | `https://api-sandbox.radiumone.io` |
| 3D Secure device data (Cardinal) | `https://centinelapi.cardinalcommerce.com` | *Not yet published — allowed by `https:`* |

Your publishable key picks the environment: `r1pk_prod_` keys use production; `r1pk_test_`, `r1pk_mock_` keys use sandbox.

## Roll out with report-only first

Send your new policy as `Content-Security-Policy-Report-Only` first. The browser reports violations without blocking anything, so a missing directive shows up in your reports instead of breaking checkout. Test card fields and a 3D Secure challenge in sandbox, then switch to the enforcing `Content-Security-Policy` header once the reports are clean.

## Related headers

### frame-ancestors

Set `frame-ancestors 'self'` on your checkout page — or `'none'` if it's never framed — so another site can't load it in a frame to trick shoppers into clicking. Elements doesn't need your page to be frameable.

### Referrer-Policy

Serve your 3DS return page with `Referrer-Policy: no-referrer`, and don't load third-party scripts on it. The return URL carries a single-use `action_ref`, which can be used to look up the 3DS result, so it shouldn't leak to other sites through the `Referer` header. Elsewhere, the browser default `strict-origin-when-cross-origin` is fine.

## Troubleshooting

Look for a CSP violation in the browser console — it names the blocked directive and URL.

| Symptom | Blocked directive | See |
| - | - | - |
| Elements doesn't load; `loadRadiumOne()` rejects | `script-src` | [Handle Elements failing to load](/elements/handle-failures/sdk-fails-to-load) |
| The card field container stays empty | `frame-src` | [Fix card fields that don't render](/elements/handle-failures/card-fields-not-rendering) |
| The shopper completes the challenge, but you get `challenge-timeout` after 5 minutes | `connect-src` | [Handle 3D Secure failures](/elements/handle-failures/three-ds-failures) |
| The challenge iframe stays blank | `frame-src` | [Iframe challenge](#iframe-challenge) |
| The page doesn't go to the bank's challenge | `form-action` | [Redirect challenge](#redirect-challenge) |

## Next steps

<Columns cols={2}>
  <Card title="Install and load" icon="download" href="/elements/install-and-load#subresource-integrity">
    Load Elements with npm or a CDN script tag, with Subresource Integrity.
  </Card>

  <Card title="3D Secure with Elements" icon="shield-check" href="/elements/three-d-secure/add-three-d-secure">
    Add RadiumOne 3D Secure to your Elements integration.
  </Card>
</Columns>
