Private preview
Integrate the Embedded Components onramp Private preview
Step-by-step integration guide for the Embedded Components onramp.
Web
React Native
Android
iOS
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 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 . To request access:
- Submit your application .
- 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 .
- Obtain your OAuth client ID and client secret. Stripe provisions these credentials, and you need them for the [authentication flow](/the relevant part of the product#authentication) .
- After onboarding is complete, obtain your secret key and publishable API key from the API keys page .
- The server-side SDK bindings in this guide are only available in private-preview releases. Install the latest private-preview version listed in the server-side SDKs .
Configure a Stripe server-side client before using the server-side examples:
const Stripe = require('stripe');
const stripe = Stripe(process.env.STRIPE_SECRET_KEY, {
apiVersion: `${Stripe.API_VERSION};crypto_onramp_beta=v2`,
});
SDK configuration
Step 1: Install the Stripe Crypto SDK
npm install @stripe/crypto
Or with yarn:
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.
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 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 .
- If they don’t, use Register a new Link user , then proceed to Authorize .
A LinkAuthIntent tracks scopes of the OAuth requests and the status of user consent. Your back end calls the Create a LinkAuthIntent API with your the related setting and the onramp OAuth scopes. LinkAuthIntent returns an authIntentId, which your back end can share with your client application.
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.
Caution
The country field determines which KYC requirements apply. Set it to the customer’s country of residence.
const userInfo = {
email: 'user@example.com', // Standard email format, max 800 characters.
phone: '+12125551234', // E.164 formatted phone number.
country: 'US', // ISO 3166-1 alpha-2 code that's used to determine regional eligibility and KYC requirements.
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 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.
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<HTMLElement>
// 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 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).
Identity
For details on KYC tiers and their identity requirements, see the KYC integration guide. For EU-specific identity requirements, see the EU KYC integration guide.
Step 1: Check if KYC collection is needed Server-side
Your back end calls the Retrieve a CryptoCustomer 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.
View example
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.
Regional considerations EU
EU customers require additional fields such as nationalities and birth_country. See the EU KYC integration guide for the complete list of required fields.
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.
If the user uploads the wrong documents or retrieve a Crypto Customer returns a failed or rejected verification for the L2 tier, call verifyDocuments again to start a new verification session to let the user re-upload their identity documents.
Verification is asynchronous. After the user completes the flow, your back end can call the Retrieve a CryptoCustomer API and inspect the verifications array to see the results.
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 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.
Select a language
Node.js
curl
No results
const data = await stripe.crypto.customers.listConsumerWallets(
req.params.id,
{},
{headers: {'Stripe-OAuth-Token': oauthToken}}
);
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 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.
payment_method_types accepts 'card' (credit/debit card) and 'us_bank_account' (US bank account via ACH). Pass one or both values depending on the payment methods you want to offer. collectPaymentMethod presents the Stripe Payment Element for users to enter their payment details. After collecting their payment method, retrieve the Crypto Payment Token from your callback and use its ID to create the Onramp Session.
Regional considerations EU
EU sessions support card payments. Set payment_method_types to ['card']. Bank account payment methods aren’t supported for EU transactions.
Select a language
Node.js
curl
No results
const data = await stripe.crypto.customers.listPaymentTokens(
req.params.id,
{},
{headers: {'Stripe-OAuth-Token': oauthToken}}
);
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 API to create a CryptoOnrampSession. The example below shows how a client application might call your back end. Adapt it to your use case.
Regional considerations EU
For EU sessions, set source_currency to eur.
// 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.
}
Note
If the API returns an HTTP 400 error with the crypto_onramp_missing_document_verification code even though the transaction amount is within the customer’s current tier limit, the customer will need to complete an identity challenge.
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 a client-side callback that the SDK invokes to perform the onramp checkout. Have it call your back end, which calls the onramp session checkout endpoint with the session ID. Your back end returns the client_secret from the response, which your callback then returns to the SDK. This callback might be called more than once during a single checkout, for example after the SDK handles a required next action such as 3DS.
Caution
Always call the onramp session checkout endpoint from within this callback—never call it directly from your back end outside of performCheckout. The SDK invokes the callback because it must handle any required payment next actions, such as 3DS authentication, between checkout calls. Calling the checkout endpoint directly might appear to succeed in a sandbox (where next actions are rarely required), but the transaction isn’t considered finalized until performCheckout returns a successful result.
For users paying with ACH, you also need to pass mandate_data and collect the user’s ip address and user agent.
Optional Display a price estimate
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>
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<RegisterLinkUserResult>
| 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. |
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<HTMLElement | null>
submitKycInfo(params)
Submits KYC information for the user.
| Parameter | Type | Required | Description |
|---|---|---|---|
params | KycInfo | Yes | KYC data to submit. See KycInfo. |
Returns: Promise<void>
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. |
Returns: Promise<CryptoConsumerWallet>
| 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<void>
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. |
paymentMethodDetails | PaymentMethodDetails | null | Details about the collected payment method, or null if unavailable. See PaymentMethodDetails. |
Returns: Promise<HTMLElement>
verifyDocuments()
Presents the Stripe-hosted identity document verification flow. You can call this method again to let the user re-upload their identity documents (for example, after a failed or rejected verification). Each call starts a new verification session.
Returns: Promise<DocumentVerificationResult>
| 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<string> | Yes | Callback that calls your back end, which calls the onramp session checkout endpoint with the session ID, and returns the resulting client_secret. |
Returns: Promise<CheckoutResult>
| 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. |
id_number | IdNumber | No | Government-issued ID number. See IdNumber. |
date_of_birth | DateOfBirth | No | The user’s date of birth. See DateOfBirth. |
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. |
PaymentMethodDetails
A discriminated union describing the collected payment method. The type field determines which detail object is present.
| Property | Type | Description |
|---|---|---|
type | 'card' | 'us_bank_account' | The type of payment method collected. |
card | CardPaymentMethodDetails | Present when type is 'card'. See CardPaymentMethodDetails. |
us_bank_account | UsBankAccountPaymentMethodDetails | Present when type is 'us_bank_account'. See UsBankAccountPaymentMethodDetails. |
CardPaymentMethodDetails
| Property | Type | Description |
|---|---|---|
brand | string | Card brand, for example, visa, mastercard, amex. |
exp_month | number | Expiration month (1-12). |
exp_year | number | Four-digit expiration year. |
funding | string | Card funding type, for example, credit, debit, prepaid. |
last4 | string | Last four digits of the card number. |
wallet | `{ type: 'apple_pay' \ | 'google_pay' }|null` |
UsBankAccountPaymentMethodDetails
| Property | Type | Description |
|---|---|---|
account_type | 'checking' | 'savings' | null | The type of bank account. |
bank_name | string | Name of the bank. |
last4 | string | Last four digits of the bank account number. |
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 |
Supported networks and currencies
Livemode
| Currency | Network | Address |
|---|---|---|
the related setting ( usdc) | Solana ( solana) | EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v |
the related setting ( usdc) | Base ( base) | 0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913 |
the related setting ( ousd) | Base ( base) | Coming soon |
the related setting ( ousd) | Solana ( solana) | Coming soon |
the related setting ( ousd) | Ethereum ( ethereum) | Coming soon |
the related setting ( ousd) | Tempo ( tempo) | Coming soon |
the related setting ( usdc) | Sui ( sui) | 0xdba34672e30cb065b1f93e3ab55318768fd6fef66c15942c9f7cb846e2f900e7::usdc::the related setting |
the related setting.e ( usdc) | Tempo ( tempo) | 0x20c000000000000000000000b9537d11c60e8b50 |
the related setting ( usdc) | Ethereum ( ethereum) | 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48 |
the related setting ( usdb) | Solana ( solana) | ENL66PGy8d8j5KNqLtCcg4uidDUac5ibt45wbjH9REzB |
USDsui ( usdsui) | Sui ( sui) | 0x44f838219cf67b058f3b37907b655f226153c18e33dfcd0da559a844fea9b1c1::usdsui::the related setting |
the related setting ( usdc) | Arbitrum ( arbitrum) | 0xaf88d065e77c8cC2239327C5EDb3A432268e5831 |
the related setting ( usdc) | World Chain ( worldchain) | 0x79A02482A880bCE3F13e09Da970dC34db4CD24d1 |
the related setting ( usdc) | Polygon ( polygon) | 0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 |
the related setting ( usdt) | Ethereum ( ethereum) | 0xdac17f958d2ee523a2206206994597c13d831ec7 |
the related setting ( usdc) | Celo ( celo) | 0xcebA9300f2b948710d2653dD7B07f33A8B32118C |
RipUSD ( ripusd) | Solana ( solana) | ripNyDf7Vi6g4qoBp9F9ZfVSTNd1jvmFHQbRVRSXpaj |
Phantom Cash ( phantom_cash) | Solana ( solana) | CASHx9KJUStyftLFWGvEVf59SGeG9sh5FfcnZMVPCASH |
Sandbox (test mode)
| Currency | Network | Supported regions |
|---|---|---|
the related setting ( usdc) | Solana ( solana) | US |
the related setting ( usdc) | Ethereum ( ethereum) | US, EU |
the related setting ( usdc) | Base ( base) | US |
Testing
Note
You can test your integration in two ways, in a sandbox using test API keys, or in live mode using live API keys. Both require your app to be registered as a trusted application with Stripe before any SDK calls succeed, including on a simulator.
Sandbox testing
Use a sandbox to build and verify your integration without real charges or real KYC. Use your sk_test_... secret key and pk_test_... publishable key. We recommend using a sandbox account rather than legacy test mode — legacy test mode is on a deprecation path, and sandboxes are the current, supported way to test.
If you’re testing on an Android emulator, use a system image that includes Google APIs or Google Play to prevent SDK calls from failing because of app attestation errors.
Test values
Use the following values when testing each step of the flow in sandbox:
| Step | Field | Test value |
|---|---|---|
| Authentication | SMS / OTP code | 000000 |
| KYC | Name | John Verified |
| KYC | ID Number (SSN) | 000000000 |
| KYC | Address line 1 | address_full_match |
| KYC | State | Two-letter code (for example, WA, not Washington) |
| Payment | Credit card number | 4242 4242 4242 4242 |
When testing, use a purchase amount of 100 USD or less. The liquidity partner enforces a 100 USD limit in the testing environment.
In a sandbox, the source amount used to execute the trade is hardcoded. As a result, the destination_crypto_amount returned in the API response reflects the original quote and might not match the amount that was actually executed. This is expected sandbox behavior. In production, the settled amount reflects what was actually executed.
Verification
To test verification behavior in a sandbox:
- For phone verification, use
Verifiedas the last name. This returns a verified result. Phone verification is asynchronous, so your integration should poll the verification status until the status is verified or rejected. - For ID document verification, the
verifyIdentitySDK presents a testmode UI that lets you select the verification outcome directly, without uploading a real document or selfie. This lets you test all verification outcomes (success, failure, and so on) without manual review delays.
Live mode testing
Live mode testing validates production mode and has stricter requirements than sandbox testing.
Requirements
- Live API keys : Use your sk _ live _... secret key and pk _ live _... publishable key. Test keys don’t work in live mode.
- Real card charges : Live mode transactions charge a real payment method. Test card numbers don’t work.
- Real KYC : Users must complete real identity verification. Sandbox test values don’t apply in live mode.
LinkAuthIntent APIs
Create a LinkAuthIntent
Creates a LinkAuthIntent to start a Log in with Link flow. Send the OAuth client id and scopes you need. The API returns an intent id and expiration.
To obtain your the related setting, contact your Stripe account executive or solutions architect. Stripe provisions the credential as part of your onboarding.
OAuth scopes used when creating a LinkAuthIntent:
| Scope (string) | Description |
|---|---|
kyc.status:read | Read the customer’s KYC verification status. |
crypto:ramp | Add crypto wallets to deposit from the customer’s account on their behalf. |
auth.persist_login:read | Allow your app to create authentication tokens for seamless sign-in on future visits. (For Android, iOS, and React Native) |
// Response
{
"id": "lai_xxxx",
"expires_at": 1756238966
}
Parameters
| Parameter | Type | Description |
|---|---|---|
email | string (required) | The user’s email for looking up an existing Link customer. Provide either email or hashed_email, not both. |
hashed_email | string (required*) | A the related setting hash of the plain text email for privacy-sensitive flows. Provide either email or hashed_email, not both. |
oauth_client_id | string (required) | Your OAuth client id (for example, from Link). Identifies your application in the OAuth flow. |
oauth_scopes | string (required) | Comma-separated list of OAuth scopes (for example, kyc.status:read,crypto:ramp). Defines the permissions you’re requesting. |
data_sharing_merchant | string (optional) | When set, the recipient business ID for data-sharing (for example, crypto onramp). Must be a valid business ID enabled to receive OAuth tokens. |
Returns
| Field | Type | Description |
|---|---|---|
id | string | Unique identifier for the LinkAuthIntent (for example, lai_xxx). |
expires_at | integer | Unix timestamp when the intent expires. |
Errors
| HTTP status | Cause |
|---|---|
| 400 | Missing or invalid request body. |
| 403 | The CreateLinkAuthIntent isn’t enabled for the business or the API key is invalid or missing. |
| 404 | Can’t find the OAuth client for authIntentId, or the provided email has no active Link customer. |
| 409 | The Link customer previously revoked the connection with this partner. |
Retrieve access tokens
The Retrieve Access Tokens API returns the OAuth tokens associated with a consented LinkAuthIntent: an access token and, when issued, a refresh token. After the user completes authorization, your back end can call this endpoint when it needs the tokens or securely store them for reuse. To limit credential exposure, we recommend that you don’t send OAuth access tokens or refresh tokens to the client. Use the access token (for example, in the Stripe-OAuth-Token header) in subsequent onramp API requests for that user.
// Response
{
"access_token": "liwltoken_xxx",
"expires_in": 3600,
"token_type": "Bearer",
"scope": "kyc.status:read crypto:ramp",
"refresh": {
"refresh_token": "liwlrefresh_xxx",
"expires_in": 7776000
}
}
Parameters
| Parameter | Type | Description |
|---|---|---|
id | string (required) | The Link Auth Intent id (for example, lai_xxx). |
Returns
| Field | Type | Description |
|---|---|---|
access_token | string | OAuth access token. Send it on subsequent API requests for this user (for example, in the Stripe-OAuth-Token header). |
token_type | string | Token type. Always Bearer. |
expires_in | integer | Seconds until the access token expires. |
scope | string | The OAuth scopes granted, separated by spaces. |
refresh | object (optional) | Present when a refresh token was issued. Use it to obtain a new access token when the current one expires. See Refresh an Access Token. |
refresh.refresh_token | string | OAuth refresh token. Store it securely and use it to obtain new access tokens. See Refresh an Access Token. |
refresh.expires_in | integer | Seconds until the refresh token expires. |
Errors
| HTTP status | Cause |
|---|---|
| 403 | Feature not available. |
| 403 | LinkAuthIntent hasn’t been consented by the user. |
| 403 | Invalid or missing API key. |
| 404 | LinkAuthIntent not found (an invalid id, or the intent belongs to another business). |
Refresh an access token
Exchanges a refresh token for a new access token. When your access token expires, use the refresh token you received from Retrieve Access Tokens API to obtain a new access token without requiring the user to re-authorize.
To obtain your the related setting, contact your Stripe account executive or solutions architect. Stripe provisions the credential as part of your onboarding.
// Response
{
"access_token": "liwltoken_xxx",
"refresh_token": "liwlrefresh_xxx",
"token_type": "Bearer",
"expires_in": 3600,
"scope": "kyc.status:read,crypto:ramp"
}
Parameters
| Parameter | Type | Description |
|---|---|---|
grant_type | string (required) | Must be refresh_token. |
refresh_token | string (required) | The refresh token previously obtained from the Retrieve Access Tokens API. |
client_id | string (required) | Your OAuth client ID provided by Link. |
client_secret | string (required) | Your OAuth client secret provided by Link. |
Returns
| Field | Type | Description |
|---|---|---|
access_token | string | OAuth access token. Expires in 1 hour. Send it on subsequent API requests (for example, in the Stripe-OAuth-Token header). |
refresh_token | string | A new OAuth refresh token. Store it securely for obtaining new access tokens when the current one expires. |
token_type | string | Token type. Always Bearer. |
expires_in | integer | TTL in seconds (3600, that is, 1 hour). |
scope | string | The OAuth scopes granted, separated by commas. |
Listen to webhook events
Stripe sends a crypto.onramp_session.updated webhook every time the status of an onramp session changes after creation. Stripe doesn’t send an event when a new session is created. Configure webhooks in the Dashboard.
The SDK’s performCheckout callback covers the payment step synchronously on the client. Use the webhook on your server to track asynchronous fulfillment. In particular, the transition from fulfillment_processing to fulfillment_complete, which occurs after checkout resolves as the crypto delivery is confirmed on-chain.
The session status field progresses through these states: initialized > requires_payment > fulfillment_processing > fulfillment_complete. The session moves to rejected if the session is blocked.
The webhook payload uses the CryptoOnrampSession resource:
{
"id": "evt_123",
"object": "event",
"data": {
"object": {
"id": "cos_0MYvv9589O8KAxCGPm84FhVR",
"object": "crypto.onramp_session",
"client_secret": "cos_0MYvv9589O8KAxCGPm84FhVR_secret_IGBYKVlTlnJL8UGxji48pKxBO00deNcBuVc",
"created": 1675794575,
"livemode": false,
"status": "fulfillment_complete",
"transaction_details": {
"destination_currency": "eth",
"destination_amount": null,
"destination_network": "ethereum",
"fees": null,
"lock_wallet_address": false,
"source_currency": "usd",
"source_amount": null,
"destination_currencies": ["eth"],
"destination_networks": ["ethereum"],
"transaction_id": null,
"wallet_address": null,
"wallet_addresses": {
"bitcoin": null,
"ethereum": "0xB00F0759DbeeF5E543Cc3E3B07A6442F5f3928a2",
"polygon": null,
"solana": null,
"stellar": null,
"destination_tags": null
}
}
}
}
}
Handle identity challenges
Risk rules can require a customer to complete an identity challenge even when their transaction amount is within the limit for their current verification tier. In this case, the Create a CryptoOnrampSession API returns an HTTP 400 error with the crypto_onramp_missing_document_verification code.
Retrieve the CryptoCustomer and check their current verification tier. If the customer is only verified at L0, first prompt them to complete the KYC collection step by submitting their date of birth and Social Security number.
After the customer reaches L1, prompt them to complete the identity verification step, where they provide a photo ID and selfie. After they complete the flow, retrieve the CryptoCustomer again and check the L2 verification status. When the status is verified, retry creating the CryptoOnrampSession.
Completing the identity challenge also verifies the customer at L2, so their future transactions use the L2 transaction limits.
