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

# Create an onramp checkout

> Create a Swapped checkout for card, Apple Pay, bank transfer, and other fiat payment methods.

Start an onramp order for a registered account, then show Swapped's hosted checkout in your app. Headless means you do not use the Rhinestone widget; it does not remove the provider's checkout UI.

## How it works

1. Your backend starts an order with `POST /onramp/orders` and receives a hosted checkout.
2. Your app shows the checkout in an iframe or in-app browser.
3. The user completes payment and any required KYC with Swapped.
4. The order advances through its [stages](/deposits/headless/processing-and-tracking/track-onramp-and-exchange-orders#order-stages), pushed as [`ramp-order`](/deposits/headless/processing-and-tracking/webhooks#ramp-order) webhooks.
5. Swapped sends the purchased crypto, currently USDC on Base, to the account; the standard deposit pipeline then routes it to the account's target.

## Prerequisites

* The account is [registered](/deposits/headless/setup/account-registration). Starting an order for an account that is not registered to your project returns `403 Unauthorized`.
* A [webhook URL](/deposits/headless/processing-and-tracking/webhooks#configure-webhooks) is configured if you want order updates pushed rather than polled.
* Calls are made from your backend. The endpoints require your API key, which never ships in client-side code.

## Start an order

```ts theme={null}
const DEPOSIT_SERVICE_URL =
    "https://v1.orchestrator.rhinestone.dev/deposit-processor";
const API_KEY = "YOUR_RHINESTONE_API_KEY";

const response = await fetch(`${DEPOSIT_SERVICE_URL}/onramp/orders`, {
    method: "POST",
    headers: { "Content-Type": "application/json", "x-api-key": API_KEY },
    body: JSON.stringify({
        provider: "swapped",
        kind: "fiat",
        account: "0xUSER_ACCOUNT_ADDRESS",
        baseCurrencyCode: "EUR",
        baseCurrencyAmount: 100,
        method: "apple-pay", // optional — omit to let Swapped choose the best method
    }),
});

const { orderId, nextAction, expiresAt } = await response.json();
// nextAction: { type: "hosted", url: "https://…", presentation: "iframe" }
```

`provider`, `kind`, and `account` are required. Every other field is optional and prefills the checkout:

| Field | Type | Description |
| - | - | - |
| `provider` | `string` | `"swapped"` |
| `kind` | `string` | `"fiat"` for a checkout; `"exchange"` for an [exchange connection](/deposits/headless/funding/create-an-exchange-connection) |
| `account` | `string` | The registered account that receives the crypto |
| `email` | `string` | Prefills the user's email |
| `baseCountry` | `string` | User's country (ISO 3166-1 alpha-2, e.g. `"DE"`) |
| `baseCurrencyCode` | `string` | Fiat currency to charge in (e.g. `"EUR"`); required with `baseCurrencyAmount` |
| `baseCurrencyAmount` | `number` | Prefilled purchase amount in the fiat currency |
| `locale` | `string` | Checkout language |
| `method` | `string` | Preselects a payment method. Omit it to let Swapped choose. |
| `methodSelectionSource` | `string` | `"personalized"` when `method` came from [`GET /onramp/options`](#list-payment-methods); the service then checks it against the user's country |

`method` takes a Swapped **payment group**, such as `"creditcard"`, `"apple-pay"`, `"bank-transfer"`, `"pix"`, or `"google-pay"`; the set depends on the user's region. A value Swapped doesn't support for the user is ignored rather than rejected, so an unrecognized `method` never blocks checkout. The one exception is a `personalized` method the user's country doesn't offer, which returns `400`.

An unknown field returns `400`, so a typo can't be silently ignored.

The response is `202 Accepted`:

| Field | Type | Description |
| - | - | - |
| `orderId` | `string` | Identifies the order; pass it to [`GET /onramp/orders/{id}`](/deposits/headless/processing-and-tracking/track-onramp-and-exchange-orders#get-an-order) |
| `nextAction` | `object` | What to show: `{ type: "hosted", url, presentation: "iframe" }` |
| `expiresAt` | `string` | Start the checkout before this time (ISO 8601) |

The URL is signed by the service, so its parameters, such as the destination wallet and asset, can't be modified client-side. Start a fresh order for each checkout session. An order becomes visible to `GET /onramp/orders/{id}` once Swapped first reports it, which happens when the user starts paying.

Full request and response schemas: [`POST /onramp/orders`](/api-reference/deposit-service/onramp/start-an-onramp-order).

## List payment methods

To render your own method picker, list what the user's country offers:

```ts theme={null}
const response = await fetch(
    `${DEPOSIT_SERVICE_URL}/onramp/options?provider=swapped&kind=fiat`,
    { headers: { "x-api-key": API_KEY, "x-user-country": "DE" } },
);
const { country, methods, otherMethods, recommendedMethod } = await response.json();
```

`methods` is the short list the deposit widget shows as its own rows: the country's **Popular** pick, then card, bank transfer and Apple Pay where available. `otherMethods` holds every other method Swapped offers in the country (for example Google Pay, Skrill or Neteller), in Swapped's order. Show both to give users every option. Each method carries:

| Field | Type | Description |
| - | - | - |
| `method` | `string` | Swapped payment group; pass it back as `method` |
| `label` | `string` | Display name |
| `iconUrl`, `iconUrlLight` | `string \| null` | Logo for light and dark backgrounds |
| `minAmount`, `maxAmount` | `number \| null` | Order limits, **in EUR** whatever currency the customer pays in |
| `currencies` | `string[]` | ISO 4217 currencies the customer can pay this method in, in this country |
| `badge` | `"Popular" \| null` | Set on the country's recommended method |

Convert `minAmount` / `maxAmount` from EUR if you show limits in another currency; Swapped's widget enforces the exact limits at checkout.

`country` is `null` when the country or Swapped's catalog is unknown; fall back to your own defaults. Pass a chosen method back as `method` with `methodSelectionSource: "personalized"`. Full schema: [`GET /onramp/options`](/api-reference/deposit-service/onramp/list-onramp-options).

## Show the hosted checkout

Embed `nextAction.url` in an iframe with payment and camera permissions — Apple Pay and camera-based KYC need them:

```html theme={null}
<iframe src={nextAction.url} allow="payment; camera; microphone; clipboard-write" />
```

In a mobile app, open the URL in an in-app browser (`SFSafariViewController` on iOS, Chrome Custom Tabs on Android) rather than a plain WebView — Apple Pay and camera access are restricted in bare WebViews.

The URL carries no redirect or callback parameter. Detect completion through the [`ramp-order`](/deposits/headless/processing-and-tracking/webhooks#ramp-order) webhook or by [polling the order](/deposits/headless/processing-and-tracking/track-onramp-and-exchange-orders#get-an-order), then dismiss the hosted checkout from your own UI.

<Warning>
  A user who abandons checkout produces no terminal event — the order simply
  never progresses. Apply your own timeout when waiting on an order.
</Warning>

KYC, identity verification, and purchase limits are handled by Swapped inside the hosted UI.

## Migrate from the legacy route

`POST /onramp/swapped/widget-url` has been removed and returns `404`. Start checkouts with `POST /onramp/orders` instead. The mapping:

| `widget-url` | `POST /onramp/orders` |
| - | - |
| request `smartAccount` | `account`, plus `provider: "swapped"`, `kind: "fiat"` |
| response `url` | `nextAction.url` |
| response `externalCustomerId` | `<account>:<orderId>` |
| response `currencyCode`, `sandbox` | not returned; the order reports its delivery asset |

Every other request field and every error response is the same.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.