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

# Onramp hosting requirements

> Page-level constraints, Content Security Policy directives, container sizing rules, and webhook obligations for hosting the Onramp widget.

The Onramp widget renders inside a cross-origin iframe or popup served from a
hosted origin on Arc. Configure your host page to meet the requirements on this
page before deploying. For an end-to-end walkthrough that uses these settings,
see the [embed widget quickstart](/app-kit/quickstarts/onramp-embed-widget).

## Endpoints

Onramp exposes two environments. Use sandbox for local development and testing.
Use production for real transactions.

| Endpoint | Sandbox                         | Production               |
| -------- | ------------------------------- | ------------------------ |
| Widget   | `https://onramp-sandbox.arc.io` | `https://onramp.arc.io`  |
| API      | `https://api-test.circle.com`   | `https://api.circle.com` |

Your [API key](/app-kit/onramp#api-key) authenticates your server. Keep it
server-side only, since it grants full access to your Onramp integration.

### Widget origin

Both `createAppServerKit` on your server and `new AppKit` on your client accept
a `widgetBaseUrl` option that points at the widget origin. The default is the
production widget URL, so production integrations don't need to set it. To
target sandbox, pass the sandbox widget origin on both sides.

Server side:

```typescript theme={null}
const server = createAppServerKit({
  onramp: {
    apiKey: process.env.CIRCLE_API_KEY!,
    widgetBaseUrl: "https://onramp-sandbox.arc.io",
  },
});
```

Client side:

```typescript theme={null}
const kit = new AppKit({
  onramp: {
    widgetBaseUrl: "https://onramp-sandbox.arc.io",
  },
});
```

The server and client values must match. If they differ, `mountIframe` and
`openWindow` throw an
[`INPUT_WIDGET_URL_ORIGIN_MISMATCH`](/app-kit/references/onramp-error-handling)
error (code 1910).

## Sandbox

Sandbox is a testing environment for developing your Onramp integration without
real money movement. It exposes the same API surface as production. Only the
endpoints and the API key binding differ.

Use sandbox for local development. The production widget's Content Security
Policy blocks `localhost`, so a production widget won't load from a local dev
server.

To target sandbox, override two config values on the server and one on the
client. API keys are environment-bound. A sandbox API key won't work against
production, and a production API key won't work against sandbox.

| Setting         | Set on                        | Sandbox                         | Production                         |
| --------------- | ----------------------------- | ------------------------------- | ---------------------------------- |
| `apiKey`        | server (`createAppServerKit`) | your sandbox API key            | your production API key            |
| `baseUrl`       | server (`createAppServerKit`) | `https://api-test.circle.com`   | `https://api.circle.com` (default) |
| `widgetBaseUrl` | server and client             | `https://onramp-sandbox.arc.io` | `https://onramp.arc.io` (default)  |

### Example `.env`

```bash .env theme={null}
# --- server-only secret ---
CIRCLE_API_KEY=YOUR_API_KEY

# --- sandbox ---
ONRAMP_API_BASE_URL=https://api-test.circle.com
NEXT_PUBLIC_ONRAMP_WIDGET_BASE_URL=https://onramp-sandbox.arc.io

# --- production ---
# ONRAMP_API_BASE_URL=https://api.circle.com
# NEXT_PUBLIC_ONRAMP_WIDGET_BASE_URL=https://onramp.arc.io
```

The `widgetBaseUrl` value is safe to expose to the browser, so a `NEXT_PUBLIC_`
(or your framework's equivalent) prefix lets the client read it. The API key is
the only real secret and never gets a public prefix.

## Content security policy

If your site sends a CSP header, you must allow the widget and API origins for
the environment you target. Without these directives, the iframe silently fails
to load.

Sandbox:

```text theme={null}
frame-src https://onramp-sandbox.arc.io;
connect-src https://onramp-sandbox.arc.io https://api-test.circle.com;
```

Production:

```text theme={null}
frame-src https://onramp.arc.io;
connect-src https://onramp.arc.io https://api.circle.com;
```

## Container element

Two container-side preconditions apply when calling `mountIframe`:

### The container must be attached to the document

The App Kit SDK appends the iframe synchronously and installs a
`MutationObserver` that auto-disposes the widget if the container later
detaches. The container must be in the DOM when you call `mountIframe`:

* In React, mount from a `useEffect` (not during render) so the ref is populated
  before the call.
* In other frameworks, use that framework's mounted lifecycle.
* In plain browser code, wait for `DOMContentLoaded` or insert the container
  yourself before calling.

### The container must have an explicit, non-zero height

The iframe renders at `width: 100%; height: 100%`. A cross-origin iframe cannot
size itself to its content, so without a resolved height the iframe collapses to
0px and the user sees nothing.

```css theme={null}
#onramp-root {
  height: 720px;
}
```

Any of these resolve the height correctly:

* A fixed `height` (such as `720px`).
* A `min-height`.
* A flex child inside a parent with a sized cross-axis.

## Popup mode constraints

When you use `openWindow` instead of `mountIframe`, the host environment
matters:

* **Mobile browsers** open a new tab, not a sized popup. The `width` and
  `height` features are ignored.
* **In-app browsers** (Instagram, Facebook, TikTok, LinkedIn, WeChat, LINE)
  block popups. `openWindow` returns
  `{ status: 'blocked', reason: 'in_app_browser' }` so you can fall back to
  `mountIframe`.
* **Installed web apps in standalone mode** also block popups. `openWindow`
  returns `{ status: 'blocked', reason: 'pwa_standalone' }`.

See [Choose iframe or popup mode](/app-kit/tutorials/onramp/iframe-vs-popup) for
the full fallback flow.

## Third-party storage in iOS Safari

iOS Safari's Intelligent Tracking Prevention (ITP) can restrict the embedded
app's access to its own storage inside an iframe. If you see KYC or session
issues that only occur on iOS Safari, use `openWindow` mode on that platform.

## Webhooks are the source of truth

Browser lifecycle events such as `DEPOSIT_SUBMITTED` and `DEPOSIT_SETTLED` are
best-effort UX signals. A user can close the tab between submitting a deposit
and settlement, and the browser will never deliver the settlement event even
though the deposit completes server-side.

<Warning>
  **Do not treat the absence of `DEPOSIT_SETTLED` as "no deposit."** Reconcile
  final state from webhook events delivered to your backend. Use the in-browser
  events only to drive UI.
</Warning>

## Session lifetime

Onramp sessions are valid for 30 minutes from creation. If your app pre-fetches
or reuses a session, mint a fresh one once more than 30 minutes have passed to
avoid a stale-session error.

## Close unused widgets

Each `mountIframe()` call, and each successful `openWindow()` call, returns a
widget controller. Keep that controller and call `widget.close()` before
starting over or when the user leaves the view.
