# Integrate the Embedded Components onramp Step-by-step integration guide for the Embedded Components onramp. # Web > This is a Web for when platform is web. View the full page at https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide?platform=web. This guide provides step-by-step instructions for you to build your integration. Use this when you need full control over the onramp flow, want to understand each API, or want to customize the flow for your website. Alternatively, see the [quickstart](https://docs.stripe.com/crypto/onramp/embedded-components-quickstart.md) for a minimal example that shows the full flow. ## Before you begin - The Embedded Components onramp is only available to users in the US (excluding New York). - The Embedded Components API is in private preview. No API calls succeed until onboarding is complete, including in a *sandbox* (A sandbox is an isolated test environment that allows you to test Stripe functionality in your account without affecting your live integration. Use sandboxes to safely experiment with new features and changes). To request access: 1. [Submit your application](https://docs.stripe.com/crypto/onramp.md#submit-your-application). 1. [Sign up to join the waitlist](https://docs.stripe.com/crypto/onramp.md#sign-up). 1. Work with your Stripe account executive or solutions architect to complete onboarding before you start your integration. This includes, but isn’t limited to: - Confirm that your account is enrolled in the required feature gates for the Embedded Components onramp APIs and Link OAuth APIs. - Enable Link as a payment method in your [Dashboard](https://dashboard.stripe.com/settings/payment_methods). - Obtain your OAuth client ID and client secret. Stripe provisions these credentials, and you need them for the [authentication flow](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#authentication). - After onboarding is complete, obtain your secret key and [publishable API key](https://docs.stripe.com/keys.md#obtain-api-keys) from the [API keys page](https://dashboard.stripe.com/apikeys). ## SDK configuration ### Step 1: Install the Stripe Crypto SDK ```shell npm install @stripe/crypto ``` Or with yarn: ```shell yarn add @stripe/crypto ``` ### Step 2: Load and initialize the SDK (Client-side) Call `loadCryptoOnrampAndInitialize` with your publishable key and optional configuration to initialize the SDK. You can customize the appearance (for example, colors) so the minimal Stripe UI matches your website. ```javascript import { loadCryptoOnrampAndInitialize } from '@stripe/crypto'; const onramp = await loadCryptoOnrampAndInitialize('pk_test_...', { theme: 'stripe', }); ``` ## Authentication ### Step 1: Check for a Link account (Server-side) The customer must have a [Link](https://link.com) account to use the onramp APIs. Create a `LinkAuthIntent` to determine if the customer’s email is associated with an existing Link account. - If they have an account, proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-authorize). - If they don’t, use [Register a new Link user](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-register-a-new-link-user-if-needed), then proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-authorize). A `LinkAuthIntent` tracks scopes of the OAuth requests and the status of user consent. Your back end calls the [Create a LinkAuthIntent](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#create-a-linkauthintent) API with your `OAUTH_CLIENT_ID` and the onramp OAuth scopes. LinkAuthIntent returns an `authIntentId`, which your back end can share with your client application. #### Client-side ```javascript const response = await fetch("/create-link-auth-intent", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email: email }), }); if (response.ok) { // Successfully created a LinkAuthIntent, proceed to Authorize } if (response.status === 404) { // Request to POST https://login.link.com/v1/link_auth_intent returned a 404 response // There isn't an existing Link account associated with the email // Handle registering a new Link User } ``` #### Server-side ```javascript app.post('/create-link-auth-intent', async (req, res) => { const { email } = req.body; const linkRes = await fetch('https://login.link.com/v1/link_auth_intent', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, }, body: JSON.stringify({ email, oauth_scopes: process.env.LINK_OAUTH_SCOPES, oauth_client_id: process.env.LINK_OAUTH_CLIENT_ID, }), }); const data = await linkRes.json(); if (data.id) { res.json({ authIntentId: data.id }); } else { res.status(linkRes.status).json(data); } }); ``` ### Step 2: Register a new Link user (if needed) (Client-side) If the customer doesn’t have a Link account, use `registerLinkUser` to create one with the customer information collected from your UI. Upon successful account creation, proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-authorize). ```javascript const userInfo = { email: 'user@example.com', phone: '+12125551234', // E.164 formatted phone number country: 'US', fullName: 'John Smith', }; const registerResult = await onramp.registerLinkUser(userInfo); if (registerResult.created) { // Proceed to authorization. } ``` ### Step 3: Authorize (Client-side) Call the `authenticate` SDK method, passing the LinkAuthIntent Id and a callback to start the authentication flow. Your callback is called when the user completes authentication. If the user successfully authenticates, the callback parameter contains `crypto_customer_id`. The `crypto_customer_id` is a unique identifier for the user that you need to [Create Onramp Session](https://docs.stripe.com/api/crypto/onramp_sessions/create.md) later. `authenticate` returns an HTMLElement. Present this to the user to have them authenticate. We recommend presenting this to them as a modal. If authentication isn’t required, your callback is called immediately. Don’t present the HTMLElement to the user in this case. ```javascript const authenticationElement = await onramp.authenticate(linkAuthIntentId, async (result) => { if (result.result === 'success') { if (result.crypto_customer_id) { // The user successfully authenticated // persist their crypto_customer_id in your backend, you will need it later to onramp } } else if (result.result === 'abandoned') { // The user cancelled. Dismiss and let them try again } else if(result.result === 'declined') { // The user declined the OAuth Consent Screen. Explain they need to consent to continue, or let them try again } }); // `authenticate` returns a Promise // Render this in your UI, we recommend presenting this in a modal document.getElementById('auth-container').replaceChildren(authenticationElement); ``` #### Request access tokens After the user authenticates, your back end calls the [Retrieve Access Tokens](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens) API to request access tokens. Store the access token and use it on all subsequent onramp API requests (for example, in the `Stripe-OAuth-Token` header). ```javascript async function exchangeTokens(authIntentId) { const res = await fetch(`https://login.link.com/v1/link_auth_intent/${authIntentId}/tokens`, { method: 'POST', headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}` }, }); const data = await res.json(); if (!data.access_token) throw new Error('Token exchange failed'); const oauthToken = data.access_token; return oauthToken; } ``` ## Identity For details on KYC tiers and their identity requirements, see the [KYC integration guide](https://docs.stripe.com/crypto/onramp/kyc-integration-guide.md). ### Step 1: Check if KYC collection is needed (Server-side) Your back end calls the [Retrieve a CryptoCustomer](https://docs.stripe.com/api/crypto/customers/retrieve.md) API with the `customerId`. Inspect the response `verifications` array. If it includes an entry with type `kyc_verified` and status `not_started`, proceed to [Collect KYC](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-collect-kyc-if-needed). ### View example ```javascript async function getCryptoCustomer(req, res) { const { id } = req.params; const response = await fetch( `https://api.stripe.com/v1/crypto/customers/${id}`, { headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Stripe-OAuth-Token': oauthToken, 'Stripe-Version': '2026-03-25.dahlia;crypto_onramp_beta=v2', }, } ); const customer = await response.json(); const verifications = customer.verifications ?? []; const kycVerified = verifications.find((v) => v.name === 'kyc_verified'); const idDocVerified = verifications.find( (v) => v.name === 'id_document_verified' ); res.json({ customerId: customer.id, providedFields: customer.provided_fields ?? [], kycStatus: kycVerified?.status ?? 'not_started', idDocStatus: idDocVerified?.status ?? 'not_started', }); } ``` ### Step 2: Collect KYC (if needed) (Client-side) If the customer needs KYC verification, your client calls `submitKycInfo` to collect and submit user KYC data. Present your own interface to the user to collect this KYC information. ```javascript const kycInfo = { given_name: 'John', surname: 'Smith', id_number: { value: "000000000", // Full ID number — for US, only SSN is currently supported. type: 'us_ssn', }, date_of_birth: { // Object with numeric fields, not a date string. day: 1, // Day of month (1-31). month: 1, // Month of year (1-12). year: 1990, // Full 4-digit year. }, address: { line1: '123 Main St', line2: 'Apt 4B', city: 'San Francisco', state: 'CA', postal_code: '94111', country: 'US', }, }; try { await onramp.submitKycInfo(kycInfo); } catch (e) { // Handle KYC Submission Errors } ``` ### Step 3: Verify identity (if needed) (Client-side) Some users must verify their identity before continuing with checkout. When required, use the `verifyDocuments` method. It presents a Stripe-hosted flow where the user uploads an identity document and a selfie. Verification is asynchronous. After the user completes the flow, your back end can call the [Retrieve a CryptoCustomer](https://docs.stripe.com/api/crypto/customers/retrieve.md) API and inspect the verifications array to see the results. ```javascript const result = await onramp.verifyDocuments(); if (result === 'abandoned') { // User canceled. Dismiss and let them try again. } else { // Identity verified. Proceed to payment flow (register wallet, collect payment method). } ``` ## Payment ### Step 1: Register a crypto wallet (Client-side) (Server-side) All wallet addresses must be registered to the user’s account before you can onramp to it. Your back end can call the [List ConsumerWallets](https://docs.stripe.com/api/crypto/consumer_wallets/list.md) API to see whether the user already has wallets on file. If the list is empty or the user wants to add another address, have the client call `registerWalletAddress` with the user’s chosen address and network. You can reuse a previously registered wallet in future sessions. For all valid network values, see [Network](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#supported-networks-and-currencies). #### List ConsumerWallets ```javascript const response = await fetch( `https://api.stripe.com/v1/crypto/customers/${req.params.id}/crypto_consumer_wallets`, { headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Stripe-OAuth-Token': oauthToken, 'Stripe-Version': '2026-03-25.dahlia;crypto_onramp_beta=v2', }, } ); const data = await response.json(); ``` #### Register a wallet ```javascript try { const result = await onramp.registerWalletAddress('3CgQg…', 'solana'); } catch { // Registration failed } ``` ### Step 2: Collect a payment method (Client-side) (Server-side) You must first collect a payment method before a transaction can occur. Your back end can call the [List PaymentTokens](https://docs.stripe.com/api/crypto/payment_tokens/list.md) API to see which payment methods the user already has. If the list is empty or the user wants to use a different method, have the client call `collectPaymentMethod`. Cards and bank accounts are supported. `collectPaymentMethod` presents the Stripe wallet user interface, which lists existing stored payment methods, allows the user to add new ones, or select one. Upon successful payment method selection, it returns a result with a `displayData` property (icon, label, sublabel) that you can use in your UI to show the selected payment method. #### List PaymentTokens ```javascript const response = await fetch( `https://api.stripe.com/v1/crypto/customers/${req.params.id}/payment_tokens`, { headers: { 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Stripe-OAuth-Token': oauthToken, 'Stripe-Version': '2026-03-25.dahlia;crypto_onramp_beta=v2', }, } ); const data = await response.json(); ``` #### Collect a payment method ```javascript try { const result = await onramp.collectPaymentMethod({payment_method_types: ['card'], wallets: {applePay: 'auto', googlePay: 'auto'}}, (result) => { const cryptoPaymentToken = result.cryptoPaymentToken; // Use this crypto payment token when Creating an Onramp Session }); } catch { // Payment collection failed } ``` ### Step 3: Create a crypto onramp session (Server-side) From your UI, determine the amount, source currency (for example, `usd`), destination currency (for example, `usdc`), and network. Your back end calls the [Create a CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/create.md) API to create a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md). The example below shows how a client application might call your back end. Adapt it to your use case. #### Client-side ```javascript // createOnrampSession is a client-side function you must implement. // Call your back end to create a CryptoOnrampSession using the API. const result = await createOnrampSession({ uiMode: 'headless', cryptoCustomerId, cryptoPaymentToken, sourceAmount: 100.0, // Pass source_amount OR destination_amount, not both. sourceCurrency: 'usd', destinationCurrency: 'usdc', destinationNetwork: 'solana', // Singular: pins the transaction to this network. destinationNetworks: ['solana'], // Array: must be set when walletAddress is set. walletAddress, customerIpAddress, }); if (result.success) { const sessionId = result.data.id; // Call performCheckout with sessionId. } else { // Creation failed. Show error and let the user retry. } ``` #### Server-side ```javascript app.post('/create-onramp-session', async (req, res) => { const { authIntentId, crypto_customer_id, payment_token, source_amount, source_currency, destination_currency, destination_network, wallet_address, } = req.body; const params = new URLSearchParams({ ui_mode: 'headless', crypto_customer_id, payment_token, source_amount: String(source_amount), source_currency, destination_currency, 'destination_currencies[]': destination_currency, destination_network, 'destination_networks[]': destination_network, wallet_address, customer_ip_address: req.socket.remoteAddress, }); const stripeRes = await fetch('https://api.stripe.com/v1/crypto/onramp_sessions', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Stripe-OAuth-Token': oauthToken, 'Stripe-Version': '2026-03-25.dahlia;crypto_onramp_beta=v2', }, body: params, }); const data = await stripeRes.json(); if (data.id) { res.json({ id: data.id, quote_expires_at: data.quote?.expires_at }); } else { res.status(stripeRes.status).json(data); } }); ``` ### Step 4: Perform checkout (Client-side) (Server-side) Call `performCheckout` to run the checkout flow for a crypto onramp session. It handles any required actions such as 3DS in the browser. You must implement the client-side callback, which the SDK invokes to retrieve the checkout client secret. Have it call your back end, which calls the [onramp session checkout endpoint](https://docs.stripe.com/api/crypto/onramp_sessions/checkout.md) with the session ID. The response includes the `client_secret`, which your callback returns to the SDK. For users paying with ACH, you also need to pass `mandate_data` and collect the user’s ip address and user agent. #### Client-side ```javascript const result = await onramp.performCheckout(sessionId, async (onrampSessionId) => { // Your backend calls POST /v1/crypto/onramp_sessions/{sessionId}/checkout. // Return the client secret from your backend response. const { client_secret } = await fetch(`/checkout/${onrampSessionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }).then(r => r.json()); return client_secret; }); if (result.successful) { // Purchase complete. Show success and that crypto was sent to their wallet. } ``` #### Server-side ```javascript app.post('/checkout/:sessionId', async (req, res) => { const stripeRes = await fetch( `https://api.stripe.com/v1/crypto/onramp_sessions/${req.params.sessionId}/checkout`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Bearer ${process.env.STRIPE_SECRET_KEY}`, 'Stripe-OAuth-Token': oauthToken, 'Stripe-Version': '2026-03-25.dahlia;crypto_onramp_beta=v2', }, // pass mandate_data if the user is paying with ACH body: new URLSearchParams({ 'mandate_data[customer_acceptance][type]': 'online', 'mandate_data[customer_acceptance][accepted_at]': String(Math.floor(Date.now() / 1000)), 'mandate_data[customer_acceptance][online][ip_address]': req.ip || req.socket.remoteAddress, 'mandate_data[customer_acceptance][online][user_agent]': req.headers['user-agent'], }), } ); const data = await stripeRes.json(); res.status(stripeRes.status).json({client_secret: data.client_secret}); }); ``` When the API returns 200 or 202 but the purchase isn’t done, the response body includes the [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions.md) object with `transaction_details.last_error` set. Use that value to decide the next step: | **last\_error** | **Description** | **How to handle** | | ------------------------------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------ | | `action_required` | The user must complete a payment step (for example, 3D Secure). | The SDK handles 3DS in the browser. After the user completes the step, call checkout again. | | `missing_kyc` | KYC verification is required. | Have the user complete KYC with `submitKycInfo`. Then call checkout again. | | `missing_document_verification` | Identity document verification is required. | Have the user complete verification with `verifyDocuments`. Then call checkout again. | | `charged_with_expired_quote` | Quote expired. | Call [Refresh a Quote](https://docs.stripe.com/api/crypto/onramp_sessions/quote.md) API, then call checkout again. | | `transaction_limit_reached` | User’s limit exceeded. | Display an error message. | | `location_not_supported` | User’s location isn’t supported. | Show that the service isn’t available in their region. | | `transaction_failed` | Generic failure. | Display a generic error message. | | `missing_consumer_wallet` | The wallet address doesn’t exist for the current user. | Have the user register the wallet, then call checkout again. | The following pseudocode shows how to handle these cases in a retry loop: ```javascript async function handleCheckout() { // Step 1: Create a new session on your backend before starting checkout. let sessionId = await createSessionOnBackend(); for (let attempt = 0; attempt < 5; attempt++) { try { const result = await onramp.performCheckout(sessionId, async (onrampSessionId: string) => { // Your backend calls POST /v1/crypto/onramp_sessions/{sessionId}/checkout. // Return the client secret from your backend response. const { client_secret } = await fetch(`/checkout/${onrampSessionId}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, }).then(r => r.json()); return client_secret; }); if (result.successful) { // Purchase complete. return; } } catch { // Step 2: Inspect last_error from the session to decide the next action. const session = await fetchSessionFromBackend(sessionId); const lastError = session.transaction_details?.last_error; if (lastError === 'missing_kyc') { // Prompt user to provide KYC info, then retry checkout on the same session. await onramp.submitKycInfo({ given_name, surname, id_number, date_of_birth, address }); } else if (lastError === 'missing_document_verification') { // Prompt user to complete identity verification, then retry on the same session. await onramp.verifyDocuments(); } else if (lastError === 'missing_consumer_wallet') { // Prompt user to register a wallet, then retry on the same session. await onramp.registerWalletAddress(walletAddress, network); } else if (lastError === 'charged_with_expired_quote') { // Quote expired. Refresh the quote on your backend, then retry checkout on the same session. await refreshQuoteOnBackend(sessionId); } else { // Terminal errors: transaction_limit_reached, location_not_supported, transaction_failed. // Do not retry. Show an appropriate error message. showError(lastError); return; } } } } ``` ## SDK reference ### loadCryptoOnrampAndInitialize(publishableKey, options) Loads and initializes the SDK. Returns a configured `OnrampCoordinator` instance. | Parameter | Type | Required | Description | | ---------------- | ------------------------- | -------- | ---------------------------- | | `publishableKey` | string | Yes | Your Stripe publishable key. | | `options` | `CryptoOnrampInitOptions` | No | SDK configuration options. | **`CryptoOnrampInitOptions`** | Property | Type | Required | Description | | -------- | --------------------------------- | -------- | --------------------------------------------- | | `theme` | `'stripe'` | `'night'` | `'flat'` | Yes | Visual theme for Stripe-provided UI elements. | Returns: `Promise` ### OnrampCoordinator #### registerLinkUser(email, phone, country, fullName?) Creates a new Link account for the user. | Parameter | Type | Required | Description | | ---------- | ------ | -------- | --------------------------------------------------------------------- | | `email` | string | Yes | The user’s email address. | | `phone` | string | Yes | The user’s phone number in E.164 format, for example, `+12125551234`. | | `country` | string | Yes | Two-letter ISO country code, for example, `US`. | | `fullName` | string | No | The user’s full name. | Returns: `Promise` | Property | Type | Description | | --------- | ------- | ----------------------------------------- | | `created` | boolean | `true` if a new Link account was created. | #### authenticate(linkAuthIntentId, onCompletion) Presents the OTP consent screen. Calls `onCompletion` when the user completes, abandons, or declines the flow. | Parameter | Type | Required | Description | | ------------------ | ---------------------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- | | `linkAuthIntentId` | string | Yes | The `id` returned from [Create a LinkAuthIntent](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#create-a-linkauthintent). | | `onCompletion` | `(result: AuthenticationResult) => void` | Yes | Callback invoked when the flow completes. | **`AuthenticationResult`** | Property | Type | Description | | -------------------- | ------------------------------------------ | ------------------------------------------------------------------------------ | | `result` | `'success'` | `'abandoned'` | `'declined'` | Outcome of the authentication flow. | | `crypto_customer_id` | string | Present when `result` is `'success'`. Use for all subsequent onramp API calls. | Returns: `Promise` #### submitKycInfo(params) Submits KYC information for the user. | Parameter | Type | Required | Description | | --------- | --------- | -------- | --------------------------------------------------------------------------------------------------------------------------- | | `params` | `KycInfo` | Yes | KYC data to submit. See [KycInfo](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#kyc-info). | Returns: `Promise` #### registerWalletAddress(walletAddress, network) Registers a wallet address for the user on the given network. | Parameter | Type | Required | Description | | --------------- | --------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | | `walletAddress` | string | Yes | The user’s crypto wallet address. | | `network` | `CryptoNetwork` | Yes | The blockchain network for this wallet. See [CryptoNetwork](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#crypto-network). | Returns: `Promise` | Property | Type | Description | | ---------------- | -------------------------- | --------------------------------------------- | | `id` | string | Unique identifier for the wallet. | | `object` | `'crypto.consumer_wallet'` | Object type. Always `crypto.consumer_wallet`. | | `wallet_address` | string | The registered wallet address. | | `network` | string | The blockchain network. | #### deleteWalletAddress(walletId) Removes a previously registered wallet address. | Parameter | Type | Required | Description | | ---------- | ------ | -------- | ---------------------------------------------- | | `walletId` | string | Yes | The `id` from a `CryptoConsumerWallet` object. | Returns: `Promise` #### collectPaymentMethod(options, onCompletion) Presents the Stripe wallet UI for selecting a payment method. | Parameter | Type | Required | Description | | -------------- | ------------------------------------------------------------ | -------- | -------------------------------------------------------- | | `options` | `CollectPaymentMethodOptions` | Yes | Payment method configuration. | | `onCompletion` | `(request: CollectPaymentMethodOnCompletionRequest) => void` | Yes | Callback invoked when the user selects a payment method. | **`CollectPaymentMethodOptions`** | Property | Type | Required | Description | | ---------------------- | -------------------- | -------- | -------------------------------------------------------------------------- | | `payment_method_types` | string[] | Yes | Allowed payment method types, for example, `['card']`. | | `wallets.applePay` | `'auto'` | `'never'` | Yes | Whether to show Apple Pay. `'auto'` shows it when the device supports it. | | `wallets.googlePay` | `'auto'` | `'never'` | Yes | Whether to show Google Pay. `'auto'` shows it when the device supports it. | **`CollectPaymentMethodOnCompletionRequest`** | Property | Type | Description | | -------------------- | ------ | ----------------------------------------------------------- | | `cryptoPaymentToken` | string | The payment token to pass when creating the onramp session. | Returns: `Promise` #### verifyDocuments() Presents the Stripe-hosted identity document verification flow. Returns: `Promise` | Property | Type | Description | | -------- | --------------------------- | ------------------------------------------ | | `result` | `'success'` | `'abandoned'` | Outcome of the document verification flow. | #### performCheckout(onrampSessionId, checkout) Runs the checkout flow for an onramp session, handling any required payment actions such as 3DS in the browser. | Parameter | Type | Required | Description | | ----------------- | ---------------------------------------------- | -------- | --------------------------------------------------------------------------- | | `onrampSessionId` | string | Yes | The `id` of the `CryptoOnrampSession` to check out. | | `checkout` | `(onrampSessionId: string) => Promise` | Yes | Callback that calls your back end to retrieve the checkout `client_secret`. | Returns: `Promise` | Property | Type | Description | | ------------ | ------- | ------------------------------------------------ | | `successful` | boolean | `true` when the purchase completed successfully. | #### destroy() Destroys the `OnrampCoordinator` instance and cleans up all associated resources. Returns: `void` ### Types #### KycInfo | Property | Type | Required | Description | | --------------- | ------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ | | `given_name` | string | No | The user’s given name. | | `surname` | string | No | The user’s surname. | | `address` | `Address` | No | The user’s address. See [Address](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#address). | | `id_number` | `IdNumber` | No | Government-issued ID number. See [IdNumber](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#id-number). | | `date_of_birth` | `DateOfBirth` | No | The user’s date of birth. See [DateOfBirth](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#date-of-birth). | | `nationalities` | string[] | No | ISO country codes for the user’s nationalities, for example, `['US']`. | | `birth_country` | string | No | ISO country code of the user’s birth country. | | `birth_city` | string | No | The user’s city of birth. | #### Address | Property | Type | Required | Description | | ------------- | ------ | -------- | ----------------------------------------------------- | | `country` | string | Yes | Two-letter ISO country code, for example, `US`. | | `city` | string | No | City, district, suburb, town, or village. | | `line1` | string | No | Address line 1 (street address or PO box). | | `line2` | string | No | Address line 2 (apartment, suite, unit, or building). | | `postal_code` | string | No | ZIP or postal code. | | `state` | string | No | State, county, province, or region. | | `town` | string | No | Town. | #### IdNumber | Property | Type | Required | Description | | -------- | ---------- | -------- | ------------------------------------------------------- | | `type` | `'us_ssn'` | Yes | ID number type. Currently only `'us_ssn'` is supported. | | `value` | string | Yes | The ID number value. | #### DateOfBirth | Property | Type | Required | Description | | -------- | ------ | -------- | ------------------------------------------ | | `day` | number | Yes | Day of the month (1–31). | | `month` | number | Yes | Month of the year (1–12). | | `year` | number | Yes | Full four-digit year, for example, `1990`. | #### CryptoNetwork | Value | Network | | -------------- | ----------- | | `'bitcoin'` | Bitcoin | | `'ethereum'` | Ethereum | | `'solana'` | Solana | | `'polygon'` | Polygon | | `'stellar'` | Stellar | | `'avalanche'` | Avalanche | | `'base'` | Base | | `'aptos'` | Aptos | | `'optimism'` | Optimism | | `'worldchain'` | World Chain | | `'xrpl'` | XRP Ledger | | `'sui'` | Sui | | `'tempo'` | Tempo | # React Native > This is a React Native for when platform is react-native. View the full page at https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide?platform=react-native. This guide provides step-by-step instructions for you to build your integration. Use this when you need full control over the onramp flow, want to understand each API, or want to customize the flow for your app. Alternatively, see the [quickstart](https://docs.stripe.com/crypto/onramp/embedded-components-quickstart.md) for a minimal example that shows the full flow, or explore the [example app](https://github.com/stripe-samples/crypto-embedded-components-onramp?platform=react-native) for a complete React Native project that demonstrates the full crypto purchase flow. ## Before you begin - The Embedded Components onramp is only available to users in the US (excluding New York). - The Embedded Components API is in private preview. No API calls succeed until onboarding is complete, including in a *sandbox* (A sandbox is an isolated test environment that allows you to test Stripe functionality in your account without affecting your live integration. Use sandboxes to safely experiment with new features and changes). To request access: 1. [Submit your application](https://docs.stripe.com/crypto/onramp.md#submit-your-application). 1. [Sign up to join the waitlist](https://docs.stripe.com/crypto/onramp.md#sign-up). 1. Work with your Stripe account executive or solutions architect to complete onboarding before you start your integration. This includes, but isn’t limited to: - Confirm that your account is enrolled in the required feature gates for the Embedded Components onramp APIs and Link OAuth APIs. - Enable Link as a payment method in your [Dashboard](https://dashboard.stripe.com/settings/payment_methods). - Obtain your OAuth client ID and client secret. Stripe provisions these credentials, and you need them for the [authentication flow](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#authentication). - Confirm that your app is registered as a trusted application. We require this before you can use the SDK, including for simulator testing. - After onboarding is complete, obtain your secret key and [publishable API key](https://docs.stripe.com/keys.md#obtain-api-keys) from the [API keys page](https://dashboard.stripe.com/apikeys). ## Mobile SDK configuration ### Step 1: Install the Stripe React Native SDK For Expo projects, run the following to automatically install the version compatible with your Expo SDK: ```shell npx expo install @stripe/stripe-react-native ``` For bare React Native projects, follow the installation instructions in the [README](https://github.com/stripe/stripe-react-native?tab=readme-ov-file). See the [requirements section](https://github.com/stripe/stripe-react-native?tab=readme-ov-file#requirements) for the minimum compatible Expo SDK, React Native, iOS, and Android versions. ### Step 2: Add the onramp dependency (Client-side) By default, the onramp dependency isn’t included in the Stripe React Native SDK to reduce bundle size. Include it as follows, depending on your platform. #### Bare React Native App ```shell # android/gradle.properties StripeSdk_includeOnramp=true # ios/Podfile – add pod pod 'stripe-react-native/Onramp', path: '../node_modules/@stripe/stripe-react-native' ``` #### Expo ```javascript // [Expo] Add `"includeOnramp": true` (default false) { "expo": { ... "plugins": [ [ "@stripe/stripe-react-native", { "merchantIdentifier": string | string [], "enableGooglePay": boolean, "includeOnramp": boolean } ] ] } } ``` ```shell # Add Expo BuildProperties (https://docs.expo.dev/versions/latest/sdk/build-properties/) npx expo install expo-build-properties ``` If you’re testing on a physical device, install [expo-dev-client](https://docs.expo.dev/versions/latest/sdk/dev-client/) to avoid Metro bundler connection issues: ```shell npx expo install expo-dev-client ``` ### Step 3: Use StripeProvider (Client-side) Wrap your app with [StripeProvider](https://stripe.dev/stripe-react-native/api-reference/functions/StripeProvider.html) at a high level so Stripe functionality is available throughout your component tree. Key properties: - `publishableKey`: Your Stripe publishable key. - `merchantIdentifier`: Your Apple Merchant ID (required for Apple Pay). - `urlScheme`: Required for return URLs in authentication flows. You need this component to initialize the Stripe SDK in your React Native application before using payment-related features. ```javascript import { StripeProvider } from '@stripe/stripe-react-native'; function App() { return ( {/* Your app components */} ); } ``` ### Step 4: Configure the onramp SDK (Client-side) Before you can successfully call any onramp APIs, you need to configure the SDK using the `configure` method. It’s provided by the [useOnramp()](https://stripe.dev/stripe-react-native/api-reference/functions/useOnramp.html) hook. The `configure` method takes an instance of [Onramp.Configuration](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.Configuration.html) to customize your business display name and lightly customize elements in Stripe-provided interfaces, such as the user’s wallet, one-time passcode authorization, and identity verification UI. ```javascript import { useOnramp } from '@stripe/stripe-react-native'; function OnrampComponent() { const { configure } = useOnramp(); React.useEffect(() => { const setupOnramp = async () => { const result = await configure({ merchantDisplayName: 'My Crypto App', appearance: { lightColors: { primary: '#2d22a1', contentOnPrimary: '#ffffff', borderSelected: '#07b8b8' }, darkColors: { primary: '#800080', contentOnPrimary: '#ffffff', borderSelected: '#526f3e' }, style: 'ALWAYS_DARK', primaryButton: { cornerRadius: 8, height: 48 } } }); if (result.error) { console.error('Configuration failed:', result.error.message); } }; setupOnramp(); }, [configure]); return null; } ``` ## Authentication ### Step 1: Check for a Link account (Client-side) The customer must have a [Link](https://link.com) account to use the onramp APIs. Use `hasLinkAccount` to determine if the customer’s email is associated with an existing Link account. See the [HasLinkAccountResult](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.HasLinkAccountResult.html) for the return type and the [OnrampError](https://stripe.dev/stripe-react-native/api-reference/enums/OnrampError.html) for the error type. - If they have an account, proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-authorize). - If they don’t, use [Register a new Link user](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-register-a-new-link-user-if-needed), then proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-authorize). ```jsx const { hasLinkAccount, registerLinkUser, authorize } = useOnramp(); const linkResult = await hasLinkAccount('user@example.com'); if (linkResult.error) return; if (linkResult.hasLinkAccount) { // Proceed to authorization. } else { // Register the user first (see next step). } ``` ### View example ```javascript function AuthComponent() { const { hasLinkAccount, registerLinkUser } = useOnramp(); const handleAuth = async () => { const linkResult = await hasLinkAccount('user@example.com'); if (linkResult.error) { // Lookup failed. Show linkResult.error.message and stop. return; } if (linkResult.hasLinkAccount) { // User has Link account. Proceed to authorization. } else { const userInfo = { email: 'user@example.com', phone: '+12125551234', country: 'US', fullName: 'John Smith', }; const registerResult = await registerLinkUser(userInfo); if (registerResult.error) { // Registration failed. Show registerResult.error.message and let the user fix the data. } else if (registerResult.customerId) { // User registered. Proceed to authorization. } } }; return