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

# Embedded Offramp Reference

> Props, events, statuses, error reasons and sizing of the CrossmintEmbeddedOfframp component

`CrossmintEmbeddedOfframp` renders the Crossmint offramp in your app for one [offramp session](https://docs.crossmint.com/offramp/api-reference/create-offramp-session.md). The React and React Native components take the same props and emit the same events. Render the component inside `CrossmintProvider`, which takes your client-side API key.

## Packages

| Platform | Package | Renders the offramp in |
| - | - | - |
| React | `@crossmint/client-sdk-react-ui` | An iframe |
| React Native | `@crossmint/client-sdk-react-native-ui` | A WebView |

Both packages export:

| Export | Kind | Description |
| - | - | - |
| `CrossmintEmbeddedOfframp` | Component | The offramp |
| `createCrossmintWalletSigner(wallet)` | Function | Returns an `EmbeddedOfframpPayer` for a Crossmint EVM or Solana wallet |
| `OfframpSignatureRejectedError` | Class | The error a custom payer throws when the user declines the transaction |
| `CrossmintEmbeddedOfframpProps`, `EmbeddedOfframpCredentials`, `EmbeddedOfframpPayer`, `OfframpStatus`, `OfframpErrorReason` | Types | The types on this page |

## Props

| Prop | Type | Required | Description |
| - | - | - | - |
| `credentials` | `{ sessionId: string; clientSecret: string }` | Yes | The `sessionId` and `clientSecret` returned by [Create Offramp Session](https://docs.crossmint.com/offramp/api-reference/create-offramp-session.md). |
| `payer` | `EmbeddedOfframpPayer` | Yes | Signs and sends the deposit. Pass `createCrossmintWalletSigner(wallet)` for a Crossmint wallet, or your own object for an external wallet. See [Payer](#payer). |
| `appearance` | `EmbeddedCheckoutV3Appearance` | No | Fonts, colors and component styles. It takes the same object as the embedded checkout's `appearance`, described in [UI Customization](https://docs.crossmint.com/onramp/guides/ui-customization.md). The identity verification step uses it too. |
| `locale` | `Locale` | No | The language of the screens, such as `en-US` or `es-ES`. Defaults to the browser or device language when it is supported, otherwise `en-US`. |
| `fill` | `boolean` | No | Fills the container's height instead of sizing to the content. Defaults to `false`. See [Sizing](#sizing). |
| `onReady` | `() => void` | No | See [Events](#events). |
| `onStatusChange` | `(event: { status: OfframpStatus; orderId?: string }) => void` | No | See [Events](#events). |
| `onComplete` | `(event: { orderId: string; txId: string }) => void` | No | See [Events](#events). |
| `onCancel` | `() => void` | No | See [Events](#events). Without it, the **Cancel**, **Continue** and **Close** buttons do nothing. |
| `onError` | `(error: { reason: OfframpErrorReason; retriable: boolean; message: string }) => void` | No | See [Events](#events). |

The component reloads the offramp when the value of `credentials`, `appearance`, `locale` or `fill` changes. `payer` and the callbacks can change without a reload.

### Payer

`EmbeddedOfframpPayer` has one method, which the component calls when the user confirms the withdrawal:

```typescript theme={null}
signAndSendTransaction(tx: { serializedTransaction: string; chain: string; payerAddress: string }): Promise<string>;
```

| Field | Description |
| - | - |
| `serializedTransaction` | The deposit to sign and broadcast: a serialized transaction on EVM chains, or a base58 legacy transaction with `feePayer` and `recentBlockhash` set on Solana. |
| `chain` | The chain the deposit was prepared for. |
| `payerAddress` | The wallet the deposit was prepared for. Your payer must not send the deposit from another wallet. See [Refuse a mismatched wallet](#refuse-a-mismatched-wallet). |

| Your method | What the component does |
| - | - |
| Resolves with the transaction ID | Waits for the deposit to confirm (`confirming-deposit`). |
| Throws `OfframpSignatureRejectedError` | Treats it as a decline: reports `wallet-rejected` through `onError` (`retriable` is `true`) and creates a new order with a fresh rate, so the user can confirm again. |
| Throws any other error, or resolves with no transaction ID | Cannot tell whether the deposit was broadcast, so it moves to the confirming screen (`confirming-deposit`) and keeps checking the order, every few seconds and about once a minute after 20 minutes. It does not call `onError` then, and the screen has no button. If no deposit has been recorded when the session's 24-hour status window ends, `onError` reports `invalid-credentials`, the status becomes `failed`, and the screen shows **This session has expired** with a **Close** button. Until then the user waits there, even when nothing was sent, unless your app closes the component with its own control. |
| Has not settled after 2 minutes | Waits for the deposit on the confirming screen, and still takes the transaction ID if the method resolves later. |

Throw `OfframpSignatureRejectedError` only when the user declined and nothing was broadcast.

### Refuse a mismatched wallet

No thrown error refuses a deposit cleanly. `OfframpSignatureRejectedError` offers a new rate for the same wallet, so a mismatch fails again on the next attempt. Any other error leaves the user on the confirming screen for a deposit that was never sent. The component sends the deposit for the `payer` wallet you passed when you created the session, so check before you render it: mount `CrossmintEmbeddedOfframp` only while your connected wallet has the same address and chain as that `payer`, and otherwise ask the user to switch wallets.

`createCrossmintWalletSigner` throws a plain error for a mismatch, so it has the same effect. Apply the same check before you mount the component when you use it.

`createCrossmintWalletSigner(wallet)` takes a Crossmint wallet, such as the one `useWallet()` returns. It supports EVM and Solana wallets, and refuses a deposit prepared for another chain or another wallet.

## Events

| Callback | Payload | Fires when |
| - | - | - |
| `onReady` | None | The offramp has loaded the session and shows its first screen. It fires once per load, also when the session fails to load, in which case `onError` reports why. It does not fire when the offramp page cannot load or its props are invalid. |
| `onStatusChange` | `{ status: OfframpStatus; orderId?: string }` | The flow moves to another step. See [Statuses](#statuses). |
| `onComplete` | `{ orderId: string; txId: string }` | The deposit for an order is confirmed and the payout to the bank account is in progress or complete. `txId` is the deposit's transaction ID. It fires once per order and load, so it fires again when the user reopens a completed withdrawal: dedupe by `orderId`. The payout can still fail afterwards: `onStatusChange` reports `payout-failed` and `onError` reports `payout-failed` for the same `orderId`. Confirm the payout on your server with [Manage Orders](https://docs.crossmint.com/offramp/guides/manage-orders.md) or webhooks. |
| `onCancel` | None | The user closes the flow: **Continue** on the Withdrawal sent screen, **Close** on a failure screen, or **Cancel** when the order's bank account cannot be loaded. Only those three buttons call `onCancel`. No other screen has a **Cancel** button, so your app must provide its own close control. Closing the view while the deposit is confirming does not stop the deposit or the payout. Keep the latest `orderId` from `onStatusChange` and follow it with [Manage Orders](https://docs.crossmint.com/offramp/guides/manage-orders.md) or webhooks. After **Continue** on Withdrawal sent, `onComplete` has already fired. Unmounting the component does not cancel a wallet request that was already sent, so keep the payer's wallet available until it settles. |
| `onError` | `{ reason: OfframpErrorReason; retriable: boolean; message: string }` | Something went wrong. See [Error Reasons](#error-reasons). `message` is for your logs, not for display. |

### Statuses

`onStatusChange` reports one of these statuses:

| Status | Meaning |
| - | - |
| `loading` | The offramp is loading the session or an order. |
| `entering-amount` | The user enters the amount to withdraw. |
| `selecting-bank` | The user picks, renames or removes one of their saved bank accounts. |
| `adding-bank` | The user adds a bank account. |
| `verifying-identity` | The order needs identity verification, and the user is completing it. |
| `identity-in-review` | The user finished identity verification, and it is in review. |
| `reviewing` | The user reviews the quote, the fees and the bank account before withdrawing. It is also reported while the order is being prepared or needs a new rate. |
| `awaiting-signature` | The component asked the payer to sign and send the deposit, and waits for the wallet. |
| `confirming-deposit` | The deposit was sent, or may have been sent, and the offramp waits for it to confirm. |
| `completed` | The deposit is confirmed and the payout to the bank account is in progress. `onComplete` fires. |
| `payout-completed` | The payout to the bank account is complete. `onComplete` fires if it has not already fired for this order. |
| `payout-failed` | The payout to the bank account failed. `onError` reports `payout-failed`. |
| `failed` | The withdrawal cannot continue. `onError` reports why. |

`orderId` is present while an order is on screen, and absent on the amount and bank account screens and when an error ends the session.

> [!NOTE]
>
> One withdrawal can span several orders. After identity verification passes, after the wallet declines the deposit, or when the user takes a new rate, the component creates a new order, so `orderId` changes. Keep the latest `orderId`.

### Error Reasons

`onError` reports one of these reasons. When `retriable` is `false`, the session cannot continue: unmount the component. When the component shows a failure screen, its **Close** button calls `onCancel`. When `retriable` is `true`, the same step can succeed on a retry, and in most cases the component shows the problem and lets the user try again. The `failed` status always ends the session, whatever `retriable` says.

| Reason | Retriable | Meaning | What to do |
| - | - | - | - |
| `invalid-credentials` | No | The session was rejected or can no longer act: its token is not valid, or its one-hour window for new withdrawals has ended. | Create a new session and pass its credentials. |
| `invalid-configuration` | No | The component is misconfigured, for example its props cannot be read, or the identity verification step failed in a way a retry cannot fix. On React Native, also an HTTP 4xx when loading the offramp. When the props cannot be read, the offramp shows no screen. | Unmount the component and fix the integration, starting with the props and the API key passed to `CrossmintProvider`. |
| `session-spent` | No | The session cannot start this withdrawal: it was already used, or an earlier withdrawal from this wallet is still being confirmed. | Wait for the earlier withdrawal to settle, then create a new session. |
| `identity-verification-required` | No | The user must be verified before withdrawing, and the component cannot run the verification, for example because your project collects KYC through the API. | Submit the user's KYC data ([Import User KYC Data](https://docs.crossmint.com/offramp/guides/import-user-kyc-data.md)), then create a new session. |
| `identity-verification-failed` | No | The user did not pass identity verification. | Do not start another withdrawal for this user. [Failure Reasons](https://docs.crossmint.com/identity/failure-reasons.md) lists why verification can fail. |
| `wallet-ownership-required` | No | The order needs proof that the user owns the payer wallet. | Link the wallet with an ownership `proof` ([Link External Wallet to User](https://docs.crossmint.com/api-reference/users/link-wallet.md)), then create a new session. |
| `bank-not-supported` | Yes | The bank account cannot receive this withdrawal, for example from the payer wallet's chain. | None. The user picks or adds another bank account. |
| `bank-not-verified` | Yes | The bank account is not verified yet. | None. The component waits for the verification, or the user picks another bank account. |
| `bank-holder-mismatch` | Yes | The bank account holder's name does not match the user's verified identity. | None. The user adds an account in their own name. |
| `amount-too-low` | Yes | The amount is below the minimum for this bank account. | None. The component shows it on the amount screen. |
| `amount-too-high` | Yes | The amount is above the user's current limit. | None. The component shows it on the amount screen. |
| `exact-payout-unsupported` | Yes | The bank account takes an amount to send, not an amount to receive. | None. The amount screen switches to the amount to send. |
| `insufficient-funds` | Yes | The wallet does not hold enough stablecoin, or enough gas, for this withdrawal. | Fund the wallet, then the user can try again. |
| `wallet-rejected` | Yes | The payer threw `OfframpSignatureRejectedError`. The component creates a new order with a fresh rate. | None. The user can confirm again. |
| `payout-failed` | No | The payout to the bank account failed. When the deposit is refunded, the screen tells the user that their funds are being returned to their wallet. | Look up the order by `orderId` to follow it ([Manage Orders](https://docs.crossmint.com/offramp/guides/manage-orders.md)). |
| `provider-error` | Yes | Crossmint could not complete a step, such as a quote, the deposit or identity verification. On React Native, also a connection failure or an HTTP 5xx when loading the offramp. | Let the user try again, and log `message` if it repeats. If the status is `failed`, the withdrawal has ended: follow the order by `orderId` ([Manage Orders](https://docs.crossmint.com/offramp/guides/manage-orders.md)). On React Native, remount the component, for example by changing its `key`, to retry a page that did not load. |
| `unknown` | Yes | An unexpected error. | Log `message`. |

## Sizing

| Mode | Height | Container |
| - | - | - |
| Content height (default) | The component is at least 650px tall plus padding on every screen, and grows when a screen needs more. | Sets the width. On React Native, put the component in a `ScrollView` if the screen can be shorter than the component. |
| `fill` | The component fills the container's height. Content taller than the container scrolls inside the offramp. | On the web, give the container a definite height, such as `100vh` or a fixed height. On React Native, give the parent `flex: 1` or a height; the component uses `flex: 1`. |

In both modes the component takes the container's full width. Changing `fill` reloads the offramp.

## Supported Markets

SEPA bank accounts appear in the component only when the payer wallet's chain has EURC and your project has euro payouts set up. In a consumer (B2C) project, the user's verified country of residence must also be in SEPA. Otherwise **Add a bank account** does not offer SEPA, and a SEPA account the user already saved is shown greyed out as unavailable.

<Snippet file="offramp-embedded-markets-note.mdx" />

## Saved Bank Accounts

Users can rename and remove their saved bank accounts in the component. Removing one deletes it from the user's saved payment methods, so it also disappears from the [bank accounts API](https://docs.crossmint.com/offramp/guides/collect-bank-accounts.md). The component refuses a removal once the session's withdrawal was sent, and while another withdrawal from the same wallet is still being confirmed.

## See Also

* [React Quickstart](https://docs.crossmint.com/offramp/quickstarts/react.md): withdraw from a Crossmint wallet in a Next.js app
* [React Native Quickstart](https://docs.crossmint.com/offramp/quickstarts/react-native.md): withdraw from a Crossmint wallet in an Expo app
* [Create Offramp Session](https://docs.crossmint.com/offramp/api-reference/create-offramp-session.md): the session the component runs on
* [UI Customization](https://docs.crossmint.com/onramp/guides/ui-customization.md): the `appearance` object
