# Integrate the Embedded Components onramp
Step-by-step integration guide for the Embedded Components onramp.
This guide provides step-by-step instructions for you to build your integration. Use this when you need full control, 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.
## Before you begin
- The Embedded Components onramp is only available in the US (excluding Hawaii).
- The Embedded Components API is in private preview. 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)
- After you get access to the Embedded Components onramp, obtain your secret and [publishable API keys](https://docs.stripe.com/keys.md#obtain-api-keys) from the [API keys page](https://dashboard.stripe.com/apikeys).
- Work with your Stripe account executive or solutions architect to complete the onboarding process before starting your integration. This includes, but is not limited to:
1. Confirm your account is enrolled in the required feature gates for the Embedded Components onramp APIs and Link OAuth APIs.
1. Confirm your app is registered as a trusted application. This is required before you can use the SDK, including for simulator testing.
1. Enable Link as a payment method in your [Dashboard](https://dashboard.stripe.com/settings/payment_methods).
1. Obtain your OAuth client ID and client secret, which are provisioned by Stripe and required for the [authentication flow](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#authentication).
## Mobile SDK configuration
### Step 1: Install the Stripe React Native SDK
Follow the installation instructions in the [README](https://github.com/stripe/stripe-react-native?tab=readme-ov-file) to install the Stripe React Native SDK.
### 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
```
### 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.
This component is essential for initializing 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 any onramp APIs can be called successfully, you’ll 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. `configure` takes an instance of `Onramp.Configuration` 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 user must have a [Link](https://link.com) account to use the onramp APIs. Use `hasLinkAccount` to determine if the user’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-3-authorize).
- If they don’t, use [Register a new Link user](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-2-register-a-new-link-user-if-needed), then proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-3-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 ;
}
```
### Step 2: Register a new Link user (if needed) (Client-side)
If the user doesn’t have a Link account, use `registerLinkUser` to create one with user information collected from your UI. Upon successful account creation, proceed to [Authorize](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-3-authorize). See the [RegisterLinkUserResult](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.RegisterLinkUserResult.html) for the return type and the [OnrampError](https://stripe.dev/stripe-react-native/api-reference/enums/OnrampError.html) for the error type.
```jsx
const userInfo = {
email: 'user@example.com',
phone: '+12125551234',
country: 'US',
fullName: 'John Smith',
};
const registerResult = await registerLinkUser(userInfo);
if (registerResult.error) return;
if (registerResult.customerId) {
// Proceed to authorization.
}
```
### Step 3: Authorize (Client-side) (Server-side)
The primary method of authentication is via two-factor authorization.
#### Create a LinkAuthIntent
A `LinkAuthIntent` tracks scopes of the OAuth requests and the status of user consent. Your backend 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, get `authIntentId`, and send it to the client.
To obtain your `OAUTH_CLIENT_ID`, reach out to your Stripe account executive or solutions architect. The credential is provisioned by Stripe as part of your onboarding.
OAuth scopes used when creating a `LinkAuthIntent`:
| Scope (string) | Description |
| ----------------- | ---------------------------------------------------------------------- |
| `kyc.status:read` | Read the user’s KYC verification status. |
| `crypto:ramp` | Add crypto wallets to deposit from the user’s account on their behalf. |
#### Client-side
```jsx
// createAuthIntent is a client-side function you must implement.
// Call your back end to create a LinkAuthIntent using the API.
const authIntentResponse = await createAuthIntent(
email,
// This is your OAUTH_CLIENT_ID, which identifies your application in the Link OAuth flow.
authToken,
'kyc.status:read,crypto:ramp'
);
const authIntentId = authIntentResponse.data.authIntentId;
```
#### Server-side
```shell
curl -X POST https://login.link.com/v1/link_auth_intent \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-d email=user@example.com \
-d oauth_client_id=$OAUTH_CLIENT_ID \
-d oauth_scopes=kyc.status:read,crypto:ramp
# Response
{
"id": "lai_xxxx",
"expires_at": 1756238966
}
```
#### User consents
The client calls `authorize` with the `authIntentId` to complete consent. The `authorize` SDK looks up and verifies the user’s Link session, shows them what your app is requesting (the OAuth scopes) on a consent screen or inline on the OTP screen, and collects their approval.
The SDK then sends that consent to Stripe so your backend can exchange the intent for an access token and finish the flow. The result includes a `customerId` that must be used for all subsequent onramp API calls. See the [AuthorizeResult](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.AuthorizeResult.html) for the return type and the [OnrampError](https://stripe.dev/stripe-react-native/api-reference/enums/OnrampError.html) for the error type.
```jsx
const result = await authorize(authIntentId);
if (result?.error) {
// Error occurred. Show result.error.message and stop.
} else if (result?.status === 'Consented' && result.customerId) {
// User consented. Call your backend to exchange for access token, then proceed to identity flow.
} else if (result?.status === 'Denied') {
// User denied. Explain they need to consent to continue, or let them try again.
} else {
// User canceled. Dismiss and let them try again.
}
```
#### Request access tokens
If the result is `Consented`, your backend 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).
```shell
# Request
curl -X POST https://login.link.com/v1/link_auth_intent/{authIntentId}/tokens \
-H "Authorization: Bearer $STRIPE_SECRET_KEY"
# Response
{
"access_token": "liwltoken_xxx",
"token_type": "Bearer",
"expires_in": 3600,
"refresh": {
"refresh_token": "liwlrefresh_xxx",
"expires_in": 7776000
}
}
```
## 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 backend calls the [Retrieve a CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#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](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#step-2-collect-kyc-if-needed).
```shell
curl https://api.stripe.com/v1/crypto/customers/{customerId} \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
### View example
```javascript
async function getCryptoCustomer(req, res) {
const { id } = req.params;
const oauthToken = req.headers['stripe-oauth-token'];
const response = await fetch(
`https://api.stripe.com/v1/crypto/customers/${id}`,
{
headers: {
Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
'Stripe-OAuth-Token': oauthToken ?? '',
},
}
);
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 `attachKycInfo` to collect and submit user KYC data. Present your own interface to the user to collect this KYC information. See the [KycInfo](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.KycInfo.html) for the return type and the [OnrampError](https://stripe.dev/stripe-react-native/api-reference/enums/OnrampError.html) for the error type.
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function AttachKYCComponent() {
const { attachKycInfo } = useOnramp();
const handleAttachKycInfo = async () => {
const kycInfo = {
firstName: "FirstName",
lastName: "LastName",
idNumber: '000000000',
dateOfBirth: { day: 1, month: 1, year: 1990 },
address: {
line1: '123 Main St',
line2: 'Apt 4B',
city: 'San Francisco',
state: 'CA',
postalCode: '94111',
country: 'US',
},
};
const result = await attachKycInfo(kycInfo);
if (result?.error) {
// KYC failed to attach. Show result.error.message and let the user fix the data or retry.
} else {
// KYC attached. Proceed to identity verification (if needed) or payment flow.
}
};
return ;
}
```
### Step 3: Verify KYC (if needed) (Client-side)
When a user already has KYC information, use `presentKycInfoVerification` so the SDK can present a screen with the user’s existing KYC information for verification. Currently, we only support users updating their address. See the [VerifyKycResult](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.VerifyKycResult.html) for the return type and the [OnrampError](https://stripe.dev/stripe-react-native/api-reference/enums/OnrampError.html) for the error type.
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function VerifyKYCComponent() {
const { presentKycInfoVerification } = useOnramp();
const handlePresentKycVerification = async (updatedAddress: Address | null) => {
const result = await presentKycInfoVerification(updatedAddress);
if (result?.error?.code === 'Canceled') {
// User canceled. Dismiss and let them try again.
} else if (result?.error) {
// Verification failed. Show result.error.message and let the user retry.
} else if (result?.status === 'Confirmed') {
// KYC verified. Proceed to identity verification (if needed) or payment flow.
} else if (result?.status === 'UpdateAddress') {
// User needs to update address. Show your address form, then call again with updatedAddress.
} else {
// User canceled. Dismiss and let them try again.
}
};
return ;
}
```
### Step 4: Verify identity (if needed) (Client-side)
Some users must verify their identity before continuing with checkout. When required, use the `verifyIdentity` 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 backend can call the [Retrieve a CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-a-cryptocustomer) API and inspect the verifications array to see the results.
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function VerifyIdentityComponent() {
const { verifyIdentity } = useOnramp();
const handleVerifyIdentity = async () => {
const result = await verifyIdentity();
if (result?.error?.code === 'Canceled') {
// User canceled. Dismiss and let them try again.
} else if (result?.error) {
// Verification failed. Show result.error.message and let the user retry.
} else {
// Identity verified. Proceed to payment flow (register wallet, collect payment method).
}
};
return ;
}
```
## Payment
### Step 1: Register a crypto wallet (Client-side) (Server-side)
A [ConsumerWallet](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#consumerwallet) must be registered before you can create a [PaymentToken](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#paymenttoken). This validates that the address is valid for the given network. Your backend can call the [List ConsumerWallets](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#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. Replace the address and network with user-provided values. A previously registered wallet can be reused in future sessions.
#### List ConsumerWallets
```shell
curl "https://api.stripe.com/v1/crypto/customers/{customerId}/crypto_consumer_wallets" \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
#### Register a wallet
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function RegisterWalletComponent() {
const { registerWalletAddress } = useOnramp();
const handleRegisterWallet = async () => {
const result = await registerWalletAddress("0x000…", Onramp.CryptoNetwork.bitcoin);
if (result?.error) {
// Registration failed. Show result.error.message and let the user retry with a different address.
} else {
// Wallet registered. Proceed to collect payment method.
}
};
return ;
}
```
### Step 2: Collect a payment method (Client-side) (Server-side)
A payment method must first be collected before a transaction can occur. Your backend can call the [List PaymentTokens](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#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`.
Card, Bank Account, Apple Pay, and Google Pay are supported. For Card and Bank Account, `collectPaymentMethod` presents Stripe’s wallet user interface, which lists existing stored payment methods, allows the user to add new ones, and select one. Upon successful payment method selection, it returns an instance of [CollectPaymentMethodResult](https://stripe.dev/stripe-react-native/api-reference/types/Onramp.CollectPaymentMethodResult.html), which includes a `displayData` property (icon, label, sublabel) that you can use in your UI to show the selected payment method.
#### List PaymentTokens
```shell
curl https://api.stripe.com/v1/crypto/customers/{customerId}/payment_tokens \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
#### Collect a payment method
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function CollectPaymentMethodComponent() {
const { collectPaymentMethod } = useOnramp();
const handleCollectPaymentMethod = async () => {
const result = await collectPaymentMethod('Card');
if (result?.error) {
// Collection failed. Show result.error.message and let the user retry.
} else if (result?.displayData) {
// Payment method selected. Use result.displayData in your UI, then call createCryptoPaymentToken.
} else {
// User canceled. Dismiss and let them try again.
}
};
return ;
}
```
#### Collect Apple Pay
To collect Apple Pay, first check [isPlatformPaySupported](https://stripe.dev/stripe-react-native/api-reference/functions/isPlatformPaySupported.html) in [useStripe()](https://stripe.dev/stripe-react-native/api-reference/functions/useStripe.html). See [Apple Pay on React Native](https://docs.stripe.com/apple-pay.md?platform=react-native#check-if-apple-pay-supported). If the user chooses Apple Pay, pass an instance of [PlatformPay.PaymentMethodParams](https://stripe.dev/stripe-react-native/api-reference/types/PlatformPay.PaymentMethodParams.html) into `collectPaymentMethod`.
#### Collect Google Pay
To collect Google Pay, first check [isPlatformPaySupported](https://stripe.dev/stripe-react-native/api-reference/functions/isPlatformPaySupported.html) in [useStripe()](https://stripe.dev/stripe-react-native/api-reference/functions/useStripe.html). See [Google Pay on React Native](https://docs.stripe.com/google-pay.md?platform=react-native#react-native-create-enable-google-pay). If the user chooses Google Pay, pass an instance of [PlatformPay.PaymentMethodParams](https://stripe.dev/stripe-react-native/api-reference/types/PlatformPay.PaymentMethodParams.html) into `collectPaymentMethod`.
### Step 3: Create a payment token (Client-side)
Create a [PaymentToken](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#paymenttoken) by calling `createCryptoPaymentToken`. Use the returned token when creating the [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md).
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function CreatePaymentTokenComponent() {
const { createCryptoPaymentToken } = useOnramp();
const handleCreateCryptoPaymentToken = async () => {
const result = await createCryptoPaymentToken();
if (result?.error) {
// Token creation failed. Show result.error.message and let the user retry.
} else {
// Token created. Pass result.cryptoPaymentToken to createOnrampSession.
}
};
return ;
}
```
### Step 4: 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 backend 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 Stripe React Native SDK doesn’t provide APIs for creating a crypto onramp session. It happens on your backend. The example below shows how a client application might call your backend. Adapt it to your use case.
When the user is paying with ACH, you can optionally pass `settlement_speed` on create session (private feature; contact us if you need this).
#### Client-side
```jsx
function CreateOnrampSessionComponent() {
const handleCreateOnrampSession = async () => {
// createOnrampSession is a client-side function you must implement.
// Call your back end to create a CryptoOnrampSession using the API.
const result = await createOnrampSession(
"headless",
cryptoCustomerId,
cryptoPaymentToken,
100.0,
"usd",
"usdc",
walletAddress,
Onramp.CryptoNetwork.bitcoin
);
if (result.success) {
const sessionId = result.data.id;
// Call performCheckout with sessionId.
} else {
// Creation failed. Show error and let the user retry.
}
};
return ;
}
```
#### Server-side
```shell
curl -X POST https://api.stripe.com/v1/crypto/onramp_sessions \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN" \
-d "ui_mode=headless" \
-d "crypto_customer_id=crc_xxx" \
-d "payment_token=cpt_xxx" \
-d "source_amount=100" \
-d "source_currency=usd" \
-d "destination_currency=usdc" \
-d "destination_network=base" \
-d "wallet_addresses[base]=0x1234567890abcdef1234567890abcdef12345678"
```
### Step 5: Perform checkout (Client-side) (Server-side)
Call `performCheckout` to run the checkout flow for a crypto onramp session. It presents a UI for any required actions such as 3DS.
You must implement the client-side callback `fetchClientSecretFromBackend`, 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/crypto/onramp/embedded-components-integration-guide.md#checkout) with the session ID. The response includes the `client_secret`, which your callback can then return to the SDK.
For ACH, the API may indicate that `mandate_data` is missing. Collect acceptance and send it on a later checkout call if required.
#### Client-side
```jsx
import { useOnramp } from '@stripe/stripe-react-native';
function CheckoutComponent() {
const { performCheckout } = useOnramp();
const handleCheckout = async () => {
const result = await performCheckout(sessionId, async () => {
// fetchClientSecretFromBackend is a client-side function you must implement.
// It calls your back end, which calls the onramp session checkout endpoint with the session ID
// and returns the client_secret from the response.
// Return the client secret on success, or throw an Error on failure.
const clientSecret = await fetchClientSecretFromBackend(sessionId);
return clientSecret;
});
if (result.error?.code === 'Canceled') {
// User canceled. Dismiss and let them try again.
} else if (result.error) {
// Checkout failed. Show result.error.message and let the user retry.
} else {
// Purchase complete. Show success and that crypto was sent to their wallet.
}
};
return ;
}
```
#### Server-side
```shell
curl -X POST https://api.stripe.com/v1/crypto/onramp_sessions/{sessionId}/checkout \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
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` | User must complete a payment step (for example, 3D Secure). | Run the SDK’s 3DS handling. After the user completes the step, call checkout again. |
| `missing_kyc` | KYC verification is required. | Have the user complete KYC in the SDK (for example, `attachKycInfo`). Then call checkout again. |
| `missing_document_verification` | Identity document verification is required. | Have the user complete verification in the SDK (for example, `verifyIdentity`). Then call checkout again. |
| `charged_with_expired_quote` | Quote expired. | Call [Refresh a Quote](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#refresh-a-quote) 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. |
## Main objects
### CryptoCustomer
A Crypto Customer represents a Link user in the context of your platform’s crypto onramp. It ties together user’s KYC and verification state, their consumer wallets, and their payment tokens. You use `customerId` in all onramp API calls to check what’s missing (for example, KYC or identity verification), to list wallets and payment tokens, and to create crypto onramp sessions.
| Attribute | Type | Description |
| ----------------- | --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Unique identifier for the object (for example, `crc_xxx`). |
| `provided_fields` | array of strings | The set of KYC fields. See [KycField](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#kycfield). |
| `verifications` | array of Verification | List of verifications and their outcome. See [Verification](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#verification). |
#### KycField
| Value | Description |
| --------------------- | ----------------------------------------------------------------- |
| `first_name` | Customer’s first name. |
| `last_name` | Customer’s last name. |
| `id_type` | Type of ID document (for example, passport or driver’s license). |
| `id_number` | ID document number. |
| `dob` | Date of birth. |
| `address_line_1` | Address line 1. |
| `address_line_2` | Address line 2. |
| `address_city` | City. |
| `address_state` | State or region. |
| `address_postal_code` | Postal or ZIP code. |
| `address_country` | Country of address. |
| `birth_country` | Country of birth. |
| `birth_city` | City of birth. |
| `nationalities` | Nationality or nationalities. |
| `id_document` | Identity document (for example, image or verification reference). |
| `selfie` | Selfie image for verification. |
#### Verification
| Attribute | Type | Description |
| --------- | ----------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| `name` | string (enum) | [VerificationType](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#verificationtype) |
| `status` | string (enum) | [VerificationStatus](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#verificationstatus) |
| `errors` | array of strings (enum) | [VerificationError](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#verificationerror) |
#### VerificationType
| Value | Description |
| ---------------------- | ------------------------------------------------------------------------------------------- |
| `phone_verified` | Verficiation that the customer has submitted KYC L0 information. |
| `kyc_verified` | Verification that the customer has submitted required KYC L1 information. |
| `id_document_verified` | Verification that the customer has submitted and verified their identity documents (KYC 2). |
#### VerificationStatus
| Value | Description |
| ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `not_started` | Customer has not provided the info. |
| `pending` | Verification has been submitted and is processing. |
| `rejected` | Verification failed. See [VerificationError](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#verificationerror) for details. |
| `verified` | Verification complete. The customer may continue. |
#### VerificationError
| Value | Description |
| ------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `id_document_verification_failed` | Identity document verification didn’t succeed (for example, document was rejected or couldn’t be verified). |
| `user_has_reached_max_verification_attempt` | The user has reached the maximum number of verification attempts and must wait or contact support before trying again. |
### CryptoOnrampSession
See [Stripe API docs](https://docs.stripe.com/api/crypto/onramp_sessions/object.md) for the object’s attributes.
### PaymentToken
A Payment Token is a read-only representation of a payment method that belongs to a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer) and applies only to crypto onramp.
| Attribute | Type | Description |
| ----------------- | ----------------- | --------------------------------------------------------------------------------------------------------------------- |
| `id` | string | Unique identifier for the payment token (for example, `cpt_xxx`). |
| `type` | string (enum) | [PaymentMethodType](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#paymentmethodtype) |
| `card` | object (optional) | [Card](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#card) |
| `us_bank_account` | object (optional) | [UsBankAccount](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#usbankaccount) |
#### PaymentMethodType
| Value | Description |
| ----------------- | ------------------------------- |
| `card` | Card payment method. |
| `us_bank_account` | US bank account payment method. |
#### Card
| Attribute | Type | Description |
| ----------- | ------------------ | ----------------------------------------------------------------------------------------------- |
| `brand` | string (optional) | Card brand (for example, amex, mastercard, visa). |
| `last4` | string (optional) | Last four digits of the card. |
| `exp_month` | integer (optional) | Two-digit expiration month (1–12). |
| `exp_year` | integer (optional) | Four-digit expiration year. |
| `funding` | string | Card funding type (for example, credit, debit, prepaid, unknown). |
| `wallet` | object (optional) | [Wallet](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#wallet) |
#### Wallet
| Attribute | Type | Description |
| --------- | ------------- | ------------------------------------------------------------------------------------------------------- |
| `type` | string (enum) | [WalletType](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#wallettype) |
#### WalletType
| Value | Description |
| ------------ | ------------------ |
| `apple_pay` | Apple Pay wallet. |
| `google_pay` | Google Pay wallet. |
#### UsBankAccount
| Attribute | Type | Description |
| -------------- | ----------------------- | --------------------------------------------------------------------------------------------------------- |
| `account_type` | string (enum, optional) | [AccountType](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#accounttype) |
| `last4` | string (optional) | Last four digits of the bank account number. |
| `bank_name` | string (optional) | Name of the bank. |
#### AccountType
| Value | Description |
| ---------- | ----------------- |
| `checking` | Checking account. |
| `savings` | Savings account. |
### ConsumerWallet
A Consumer Wallet is a cryptocurrency wallet address linked to a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer). It’s the destination where purchased crypto is sent. The user registers wallet addresses during account setup.
| Attribute | Type | Description |
| ---------------- | ------------- | ------------------------------------------------------------------------------------------------- |
| `id` | string | Unique identifier for the consumer wallet. |
| `livemode` | boolean | Whether this wallet is in live mode (true) or test mode (false). |
| `network` | string (enum) | [Network](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#network) |
| `wallet_address` | string | The blockchain address (for example, the destination address for crypto delivery). |
#### Network
| Value | Description |
| ------------ | ------------------- |
| `bitcoin` | Bitcoin network. |
| `ethereum` | Ethereum network. |
| `solana` | Solana network. |
| `polygon` | Polygon network. |
| `base` | Base network. |
| `avalanche` | Avalanche network. |
| `stellar` | Stellar network. |
| `aptos` | Aptos network. |
| `optimism` | Optimism network. |
| `worldchain` | Worldchain network. |
| `xrpl` | XRP Ledger network. |
## API specs
### Retrieve a CryptoCustomer
Retrieves the details of a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer). Use the response to check the customer’s KYC and verification status so you can prompt the user to complete setup (for example, KYC or identity verification) before creating an onramp session.
```shell
curl https://api.stripe.com/v1/crypto/customers/{customerId} \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
```json
// Response
{
"id": "crc_XXXX",
"object": "object.public_crypto_customer",
"provided_fields": ["first_name", "last_name"],
"verifications": [
{
"name": "phone_verified",
"status": "verified",
"errors": []
},
{
"name": "kyc_verified",
"status": "verified",
"errors": []
},
{
"name": "id_document_verified",
"status": "rejected",
"errors": ["id_document_verification_failed"]
}
]
}
```
**Parameters**
| Parameter | Type | Description |
| --------- | ----------------- | ------------------------------------------------ |
| `id` | string (required) | The crypto customer ID (for example, `crc_xxx`). |
**Returns**
Returns a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer) object.
**Errors**
| HTTP status | Cause |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400 | Missing OAuth token. The request didn’t include the `Stripe-OAuth-Token` header. |
| 403 | Invalid or insufficient OAuth token. The token is missing, invalid, expired, has wrong scopes (for example, missing kyc.status:read), or wrong livemode. |
| 403 | Customer belongs to another user. The CryptoCustomer exists but is tied to a different Link consumer than the one identified by the OAuth token. |
| 404 | Customer not found. The :id isn’t a valid crypto customer ID. |
| 404 | Headless onramp not enabled. The request is from a business that doesn’t have the headless onramp gate/feature enabled. |
### List ConsumerWallets
Returns a list of [ConsumerWallet](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#consumerwallet) for a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer). Use this to see which wallet addresses are on file and whether the user needs to register a new one. Each wallet has an id, network, and address.
```shell
curl "https://api.stripe.com/v1/crypto/customers/{customerId}/crypto_consumer_wallets" \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
```json
// Response
{
"data": [
{
"id": "ccw_xxx",
"object": "crypto.consumer_wallet",
"livemode": false,
"network": "ethereum",
"wallet_address": "0x1234567890abcdef1234567890abcdef12345678"
}
],
"has_more": false
}
```
**Parameters**
| Parameter | Type | Description |
| ---------------- | ------------------ | --------------------------------------------------------------------- |
| `id` | string (required) | The crypto customer id (for example, `crc_xxx`). |
| `limit` | integer (optional) | Maximum number of wallets to return. Default is 10; maximum is 20. |
| `starting_after` | string (optional) | A wallet id. Return wallets after this id (for forward pagination). |
| `ending_before` | string (optional) | A wallet id. Return wallets before this id (for backward pagination). |
**Returns**
| Field | Type | Description |
| ---------- | ------- | ----------------------------------------------- |
| `data` | array | List of ConsumerWallet objects. |
| `has_more` | boolean | Whether there are more wallets after this page. |
| `url` | string | The URL of this list. |
**Errors**
| HTTP status | Cause |
| ----------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| 400 | Missing required param. |
| 403 | Invalid request: invalid or expired OAuth token, wrong scopes, livemode mismatch, or crypto customer belongs to a different consumer. |
| 404 | No such cryptocustomer: customer id invalid or not found. |
| 404 | Headless onramp not enabled for this business. |
### List PaymentTokens
Returns a list of [PaymentToken](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#paymenttoken) for a [CryptoCustomer](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#cryptocustomer). Use this to see which saved payment methods the user has before asking them to add one.
```shell
curl https://api.stripe.com/v1/crypto/customers/{customerId}/payment_tokens \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
```json
// Response
{
"url": "/v1/crypto/customers/crc_xxx/payment_tokens",
"data": [
{
"id": "cpt_xxx",
"object": "crypto.payment_token",
"type": "card",
"card": {
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2028,
"funding": "credit"
}
}
],
"has_more": false
}
```
**Parameters**
| Parameter | Type | Description |
| ---------------- | ------------------ | ----------------------------------------------------------------------------------------------------------- |
| `id` | string (required) | The crypto customer id (for example, `crc_xxx`). Must belong to the consumer identified by the OAuth token. |
| `limit` | integer (optional) | Maximum number of payment tokens to return. Default is 10. |
| `starting_after` | string (optional) | A payment token id. Return tokens after this id (for forward pagination). |
| `ending_before` | string (optional) | A payment token id. Return tokens before this id (for backward pagination). |
**Returns**
| Field | Type | Description |
| ---------- | ------- | ------------------------------------------------------ |
| `data` | array | List of PaymentToken objects. |
| `has_more` | boolean | Whether there are more payment tokens after this page. |
| `url` | string | The URL of this list. |
**Errors**
| HTTP status | Cause |
| ----------- | -------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400 | Missing required param: HTTP_HEADER[Stripe-OAuth-Token]. Send the Stripe-OAuth-Token header with a valid OAuth access token. |
| 403 | Invalid request: invalid or expired OAuth token, missing crypto:ramp scope, livemode mismatch, or crypto customer belongs to a different consumer. |
| 404 | No such crypto customer: customer id invalid or not found. |
| 404 | Headless onramp not enabled for this business. |
### Checkout
Completes a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md) by confirming the payment and executing the quote so crypto is delivered to the customer’s wallet. Call this after creating an onramp session. If the response indicates more steps (for example, 3DS), have the user complete them in the SDK and call checkout again.
```shell
curl -X POST https://api.stripe.com/v1/crypto/onramp_sessions/{sessionId}/checkout \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
```json
// Response
{
"id": "cos_xxx",
"object": "crypto.onramp_session",
"status": "fulfillment_complete",
"transaction_details": {
"wallet_address": "0xx",
"source_amount": "100.00",
"source_currency": "usd",
"destination_amount": "0.05",
"destination_currency": "eth",
"destination_network": "ethereum",
"quote_expiration": 1756238966,
"transaction_id": "0xx"
}
}
```
**Parameters**
| Parameter | Type | Description |
| -------------- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `id` | string (required) | The onramp session id (for example, `cos_xxx`) to checkout. The session must have been created with `ui_mode=headless` and be in a state that allows checkout. |
| `mandate_data` | object (optional) | Details for creating a mandate when required (for example, for certain payment method types or regions). Include when the API or payment method requires it. |
**Returns**
Returns the updated [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md) object.
**Errors**
| HTTP status | Cause |
| ----------- | ------------------------------------------------------------------------------------------------------------------- |
| 400 | Invalid request: missing or invalid parameters, unsupported currency or network, or invalid or missing OAuth token. |
| 400 | MissingKYC: user has not completed KYC. |
| 400 | InvalidWallet: wallet not attached to this user. |
| 400 | InvalidPaymentMethod: payment method not attached to this user. |
| 400 | Unauthorized: OAuth token invalid or missing. |
| 404 | Session not found or not owned by this business. |
### Refresh a Quote
Refreshes the quote for a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md). Use this when checkout returns `charged_with_expired_quote` so you can get a new quote and call checkout again on the same session instead of creating a new one.
```shell
curl -X POST https://api.stripe.com/v1/crypto/onramp_sessions/{sessionId}/quote \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-H "Stripe-OAuth-Token: $ACCESS_TOKEN"
```
```json
// Response
{
"id": "cos_XXXX",
"object": "crypto.onramp_session",
"status": "requires_payment",
"transaction_details": {
"wallet_address": "0x1234567890abcdef1234567890abcdef12345678",
"source_amount": "100.00",
"source_currency": "usd",
"destination_amount": "0.05",
"destination_currency": "eth",
"destination_network": "ethereum",
"quote_expiration": 1756238966
}
}
```
**Parameters**
| Parameter | Type | Description |
| --------- | ----------------- | -------------------------------------------------------------------------------- |
| `id` | string (required) | The crypto onramp session ID (for example, `cos_xxx`). Provided in the URL path. |
**Returns**
Returns a [CryptoOnrampSession](https://docs.stripe.com/api/crypto/onramp_sessions/object.md) object with an updated quote.
**Errors**
| HTTP status | Cause |
| ----------- | ----------------------------------------------------------------------------------------------------------------------- |
| 403 | Quote locked. The session quote is in a locked state. Create a new session and call checkout on it instead. |
| 403 | Internal error. The session has no consumer account, or the liquidity provider account couldn’t be found. |
| 404 | Session not found. The id isn’t a valid crypto onramp session ID or the session doesn’t exist. |
| 404 | Headless onramp not enabled. The request is from a business that doesn’t have the headless onramp gate/feature enabled. |
| 499 | Partner exchange error. The quote provider returned an error or timeout. |
### Create a LinkAuthIntent
Creates a `LinkAuthIntent` to start a [Log in with Link](https://link.com/) flow. Send the OAuth client id and scopes you need. The API returns an intent id and expiration.
This API is still in private preview. [Sign up](https://docs.stripe.com/crypto/onramp.md#sign-up) to get access to additional API info.
```shell
curl -X POST https://login.link.com/v1/link_auth_intent \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-d email=user@example.com \
-d oauth_client_id=$OAUTH_CLIENT_ID \
-d oauth_scopes=kyc.status:read,crypto:ramp
```
```json
// Response
{
"id": "lai_xxxx",
"expires_at": 1756238966
}
```
**Parameters**
| Parameter | Type | Description |
| ----------------------- | ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| `email` | string (required) | The user’s email for looking up an existing Link consumer. Provide either `email` or `hashed_email`, not both. |
| `hashed_email` | string (required*) | A SHA256 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 | CreateLinkAuthIntent not enabled for business or invalid or missing API key. |
| 404 | OAuth client not found for authIntentId or no active Link consumer for the provided email. |
| 409 | Link consumer previously revoked connection with this partner. |
### Retrieve Access Tokens
Exchanges a consented `LinkAuthIntent` for an OAuth access token. Call this after the user has completed authorization. Use the access token (for example, in the `Stripe-OAuth-Token` header) on subsequent onramp API requests for that user.
This API is still in private preview. [Sign up](https://docs.stripe.com/crypto/onramp.md#sign-up) to get access to additional API info.
```shell
curl -X POST https://login.link.com/v1/link_auth_intent/{authIntentId}/tokens \
-H "Authorization: Bearer $STRIPE_SECRET_KEY"
```
```json
// Response
{
"access_token": "liwltoken_xxx",
"expires_in": 3600,
"token_type": "Bearer",
"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. |
| `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](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#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](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#refresh-an-access-token). |
| `refresh.expires_in` | integer | Seconds until the refresh token expires. |
**Errors**
| HTTP status | Cause |
| ----------- | ------------------------------------------------------------------------------ |
| 403 | Feature not available. |
| 403 | `LinkAuthIntent` has not been consented. |
| 403 | Invalid or missing API key. |
| 404 | `LinkAuthIntent` not found (invalid id or 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](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens) to obtain a new access token without requiring the user to re-authorize.
To obtain your `OAUTH_CLIENT_SECRET`, reach out to your Stripe account executive or solutions architect. The credential is provisioned by Stripe as part of your onboarding.
This API is still in private preview. [Sign up](https://docs.stripe.com/crypto/onramp.md#sign-up) to get access to additional API info.
```shell
curl -X POST https://login.link.com/auth/token \
-H "Authorization: Bearer $STRIPE_SECRET_KEY" \
-d "grant_type=refresh_token" \
-d "refresh_token=$REFRESH_TOKEN" \
-d "client_id=$OAUTH_CLIENT_ID" \
-d "client_secret=$OAUTH_CLIENT_SECRET"
```
```json
// Response
{
"token_type": "Bearer",
"access_token": "liwltoken_xxx",
"expires_in": 3600,
"refresh": {
"refresh_token": "liwlrefresh_xxx",
"expires_in": 7776000
},
"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 Retrieve Access Tokens. |
| `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 |
| ----------------------- | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------- |
| `token_type` | string | Token type. Always `Bearer`. |
| `access_token` | string | OAuth access token. Expires in 1 hour. Send it on subsequent API requests (for example, in the `Stripe-OAuth-Token` header). |
| `expires_in` | integer | TTL in seconds (3600, that is, 1 hour). |
| `refresh` | object (optional) | Present when a new refresh token was issued. A new refresh token is returned each time you use the old one; store it for future refresh requests. |
| `refresh.refresh_token` | string | OAuth refresh token. Store it securely for obtaining new access tokens when the current one expires. |
| `refresh.expires_in` | integer | Seconds until the refresh token expires. |
| `scope` | string | The OAuth scopes granted. |
## React Native SDK
| SDK method | Presents UI? | What the user sees |
| ------------------------------------------- | ------------ | ---------------------------------------------------------- |
| `authorize(authIntentId)` | Yes | Link consent screen (or consent inline on OTP screen). |
| `attachKycInfo` | Optional | You can collect KYC in your own UI and pass data in. |
| `presentKycInfoVerification` | Yes | Stripe screen showing existing KYC for the user to verify. |
| `verifyIdentity` | Yes | Stripe-hosted flow (document + selfie). |
| `collectPaymentMethod` (Card / BankAccount) | Yes | Stripe wallet UI: list saved methods, add new, choose one. |
| `performCheckout` | Maybe | Only when needed (for example, 3DS). |
| `registerWalletAddress` | No | No UI. You pass address and network. |
## Troubleshooting
### Unrecognized request URL
If your API calls return a `404` with an `invalid_request_error` and the message `Unrecognized request URL`, your account might not be enrolled in the private preview or might be missing one or more required feature gates.
Contact your Stripe account executive or solutions architect to confirm that your account has access to all required feature gates for the Embedded Components onramp.