# KYC integration best practices

Reference integration flows and implementation patterns for the KYC tier system.

This page provides reference integration flows and best practices for implementing the KYC tier system. Before using these flows, read the [KYC integration guide](https://docs.stripe.com/crypto/onramp/kyc-integration-guide.md) to understand the tier structure, API changes, and error codes.

For a working reference implementation, see the [crypto-embedded-components-onramp](https://github.com/stripe-samples/crypto-embedded-components-onramp) sample app.

## Screen overview

A typical onramp integration has the following screens:

| Screen | Purpose | Client SDK calls | Server calls |
| --- | --- | --- | --- |
| Login | Authenticate the customer and register or retrieve their crypto customer record. | [registerLinkUser](https://docs.stripe.com/crypto/onramp/embedded-components.md) | [GET /v1/crypto/customers/:id](https://docs.stripe.com/api/crypto/customers/retrieve.md) |
| KYC | Collect and submit identity information for the required tier. | - L0/L1: [attachKYCInfo](https://docs.stripe.com/crypto/onramp/embedded-components.md)
- L2: [verifyIdentity](https://docs.stripe.com/crypto/onramp/embedded-components.md) | [GET /v1/crypto/customers/:id](https://docs.stripe.com/api/crypto/customers/retrieve.md) |
| Wallet | Collect or select the customer’s destination wallet address. | Not applicable | [GET /v1/crypto/customers/:id/crypto_consumer_wallets](https://docs.stripe.com/api/crypto/consumer_wallets/list.md) |
| Payment | Collect the customer’s payment method. | [createCryptoPaymentToken](https://docs.stripe.com/crypto/onramp/embedded-components.md) | Not applicable |
| Buy | Display transaction limits, confirm amount, and execute purchase. | Not applicable | - [GET /v1/crypto/onramp_transaction_limits](https://docs.stripe.com/api/crypto/onramp_transaction_limits/retrieve.md)
- [GET /v1/crypto/onramp_quotes](https://docs.stripe.com/api/crypto/onramp_quotes/retrieve.md)
- [POST /v1/crypto/onramp_sessions](https://docs.stripe.com/api/crypto/onramp_sessions/create.md) |

An onramp integration reuses the KYC screen across tiers and renders different fields depending on what’s needed:

- **L0**: Name and address fields (`attachKYCInfo` with `firstName`, `lastName`, `address`)
- **Step-up to L1**: SSN and date of birth fields (`attachKYCInfo` with `dateOfBirth`, `idNumber`, `idType`)
- **L0 + L1 combined**: All fields collected in a single KYC screen visit
- **L2**: The Stripe-hosted identity document verification flow (`verifyIdentity`), available after L1 is verified

The reference app implements all screens in a single [WizardView](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx) component:

- [Login](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx#L302)
- [KYC](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx#L354)
- [Wallet](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx#L481)
- [Payment](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx#L596)
- [Buy](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/WizardView.tsx#L653)

The KYC step renders different fields and submits information using the onramp SDK based on the KYC level.

## Use Platform Pay with L0 KYC 

With the iOS, Android, and React Native SDKs, you can present Apple Pay or Google Pay before Link authentication. The wallet can provide the name, email, phone, and address needed for registration and L0 KYC, reducing the information the customer needs to enter in your app. This flow collects a payment method first. Payment token creation, verification, and checkout still happen afterward.

Follow the platform-specific setup and collection examples in the integration guide for [iOS](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md?platform=ios#platform-pay-before-authentication), [Android](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md?platform=android#platform-pay-before-authentication), or [React Native](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md?platform=react-native#platform-pay-before-authentication). Use this sequence:

1. **Collect Platform Pay**: Configure the SDK with a `countryHint` based on the customer’s country, when known. Request the wallet’s name, postal address, email, and phone. Keep the returned `kycInfo` for registration and KYC submission. If the customer cancels or collection fails, stop and let them retry.
2. **Complete missing details**: Let the customer review and correct the wallet information in your app. Collect any missing name, address, email, or phone required for registration or KYC. The SDK’s `phone` is in E.164 format when available. If only `rawPhone` is available, use it to prefill your phone form and obtain a valid E.164 number before registration.
3. **Register or authenticate**: Use the email to check for an existing Link account. For a new customer, follow the [registration terms requirements](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md?platform=react-native#link-registration-disclosure) and call `registerLinkUser` with the email, phone, country, and name. For an existing account, complete `authorize`. Handle an account-already-exists registration error by switching to the existing-account flow.
4. **Obtain backend access**: Before retrieving the customer or creating an onramp session, your back end needs an OAuth access token with `kyc.status:read` and `crypto:ramp`. For a newly registered customer, [create a LinkAuthIntent](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#create-a-linkauthintent), complete the SDK’s `authorize` flow, and [retrieve access tokens](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#retrieve-access-tokens) after consent succeeds. Keep the tokens on your back end and pass the access token in `Stripe-OAuth-Token` on subsequent onramp API requests.
5. **Submit L0 KYC**: Retrieve the crypto customer and check whether KYC is needed. If needed, call `attachKYCInfo` (`attachKycInfo` on Android and React Native) with `firstName`, `lastName`, and `address`. Email and phone are registration details and aren’t part of the KYC submission payload. Don’t include date of birth or an ID number when requesting only L0 verification.
6. **Wait for verification**: Poll the customer until the relevant tier has `verification_status: "verified"` or `"rejected"`. Wallet contact details aren’t proof of verification. If L0 isn’t available or is rejected, follow the required KYC flow. If L0 succeeds, apply its transaction limits and the [step-up detection strategy](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#step-up-detection).
7. **Complete the purchase**: Register or select the destination crypto wallet, get a quote, and call `createCryptoPaymentToken`. If token creation reports that the payment method’s account or KYC region changed, collect Platform Pay again and retry token creation with the same authenticated SDK instance. After verification completes, create the onramp session, confirm the purchase with the customer, and complete checkout as described in the integration guide.

`registerLinkUser` itself doesn’t present a Stripe screen. Registration establishes the SDK session but doesn’t return an OAuth access token for your back end. The `authorize` flow presents any required Link verification or consent before you can obtain that token. Additional KYC, identity challenges, or payment authentication can still require customer interaction.

Submitting date of birth and an ID number starts L1 verification. After that submission, the customer must pass L1 to transact, even if L0 previously succeeded. See [verification failures](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#verification-failures) before designing a step-up flow. For customers in the EU, also follow the [EU KYC requirements](https://docs.stripe.com/crypto/onramp/eu-kyc-integration-guide.md).

## Step-up detection strategy 

Completing a higher KYC tier is required for higher limits, but doesn’t guarantee a particular allowance or access to the low-risk range.

`GET /v1/crypto/onramp_transaction_limits` is the authoritative source for a customer’s current remaining limits. We recommend checking limits proactively so you can explain any constraints before the customer encounters an error:

1. On the buy screen, call `GET /v1/crypto/onramp_transaction_limits` before the customer confirms their purchase.
2. Compare the customer’s desired amount against the returned limits for the selected currency, payment method, and settlement speed.
3. If the amount exceeds the limit, prompt for an available KYC step-up before attempting to create a session. If no further step-up is available, ask the customer to choose a smaller amount.
4. After each successful KYC step-up, call `GET /v1/crypto/onramp_transaction_limits` again and repeat steps 2–3. Don’t assume that verification permits the desired amount.

If you can’t implement proactive step-up detection, use session-creation errors to guide the next step. In either approach, check limits before purchase confirmation, refresh them after successful verification, and handle session-creation errors.

If the amount exceeds the customer’s overall permissible limit, session creation can return a `400` error with `crypto_onramp_limit_exceeded` instead of a missing-KYC error. Ask the customer to choose a smaller amount or wait for the limit to reset, as directed by the error message. Don’t treat this error as a request for more KYC.

KYC-related errors indicate that completing the specified verification flow might allow the customer to proceed. Retrieve the customer to determine the next step, then refresh limits after successful verification as described in the proactive flow:

- `crypto_onramp_missing_minimum_identity_verification`: The customer hasn’t submitted minimum identity information yet. Route to the KYC screen for L0.
- `crypto_onramp_missing_identity_verification`: L1 verification is required. Route to the KYC screen for L1.
- `crypto_onramp_missing_document_verification`: L2 verification is required. Route to the KYC screen for L2.

> `crypto_onramp_missing_document_verification` can also mean that risk rules require the customer to complete an [identity challenge](https://docs.stripe.com/crypto/onramp/embedded-components-integration-guide.md#handle-identity-challenges), even when the transaction amount is within the limit for their current tier.

## Polling verification status 

After submitting KYC information, poll `GET /v1/crypto/customers/:id` until `verification_status` is `verified` or `rejected` for the relevant tier. L0 and L1 verification typically complete within a few seconds. L2 verification (document review) can take longer — adjust your polling interval and provide appropriate loading indicators for each tier.

For a reference implementation, see the [refreshKycLevel](https://github.com/stripe-samples/crypto-embedded-components-onramp/blob/main/react-web/src/ExampleApp.tsx#L116) function in the sample app.

## Integration scenarios

The following scenarios cover the common KYC verification paths:

- [L0 verification succeeds](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l0-verification-succeeds): Customer passes L0 and completes a purchase within L0 limits
- [L0 succeeds, step-up to L1](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l0-step-up-l1): Customer passes L0 but needs L1 to complete a higher-value purchase
- [L0 succeeds, step-up to L2](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l0-step-up-l2): Customer steps up from L0 to L1, then from L1 to L2
- [L1 verification succeeds](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l1-verification-succeeds): Customer provides L0 and L1 information upfront
- [L1 succeeds, step-up to L2](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l1-step-up-l2): Customer is L1-verified but needs L2 to complete a higher-value purchase
- [L2 verification succeeds](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l2-verification-succeeds): Customer completes all three tiers upfront
- [Handling verification failures](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#verification-failures)
  - [L0 failure](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l0-failure)
  - [L1 failure](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l1-failure)
  - [L2 failure](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#l2-failure)

### L0 verification succeeds 

When a verification succeeds, the customer provides basic identity information, passes L0 verification, and completes a purchase within L0 limits.

L0 verification succeeds: Login, KYC (L0), Wallet, Payment, and Buy screens in sequence, ending in a successful purchase. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0)]
[KYC (L0)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] --> [Success]
```

#### Recommended operations 

1. **Login**: Register or retrieve the crypto customer. Check `kyc_tiers` to determine if KYC is already complete.
2. **KYC (L0)**: Call `attachKYCInfo` with `firstName`, `lastName`, and `address`. Poll `GET /v1/crypto/customers/:id` until the L0 tier `verification_status` is `verified` or `rejected`.
3. **Wallet**: Create or select a destination wallet.
4. **Payment**: Call `createCryptoPaymentToken` to collect payment method.
5. **Buy**: Call `GET /v1/crypto/onramp_transaction_limits` to display the customer’s available limits. Get a quote and create a session.

### L0 succeeds, step-up to L1 

In this flow, the customer passes L0 verification but attempts a purchase that exceeds L0 limits. The integration routes the customer back to the KYC screen, which collects SSN and date of birth for L1 verification.

L0 succeeds, step-up to L1: after the Buy screen detects the amount exceeds L0 limits, the user goes to the KYC screen for L1 verification before retrying the purchase. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0)]
[KYC (L0)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] -- exceeds L0 limit --> [KYC (L1)]
[KYC (L1)] -- verified --> [Buy (retry)]
[Buy (retry)] --> [Success]
```

#### Recommended operations 

1. **Detect step-up to L1**: On the Buy screen, call `GET /v1/crypto/onramp_transaction_limits`. If the customer’s desired amount exceeds their L0 limit, or if creating a session returns `crypto_onramp_missing_identity_verification`, route the customer back to the KYC screen for L1.
2. **KYC (L1)**: Call `attachKYCInfo` with `dateOfBirth`, `idNumber`, and `idType`. Poll until L1 `verification_status` is `verified` or `rejected`.
3. **Return to Buy**: After L1 verification succeeds, refresh limits and recheck the amount using the [step-up detection strategy](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#step-up-detection).

### L0 succeeds, step-up to L2 

The customer passes L0 verification, exceeds L0 limits (triggering a step-up to L1), then exceeds L1 limits (triggering a step-up to L2). Each step-up returns to the same KYC screen, which renders different fields for each tier.

L0 succeeds, step-up to L2: the user exceeds L0 limits triggering a step-up to L1, then exceeds L1 limits triggering a step-up to L2, before completing the purchase. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0)]
[KYC (L0)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] -- exceeds L0 limit --> [KYC (L1)]
[KYC (L1)] -- verified --> [Buy]
[Buy] -- exceeds L1 limit --> [KYC (L2)]
[KYC (L2)] -- verified --> [Buy (retry)]
[Buy (retry)] --> [Success]
```

#### Recommended operations 

1. **Detect step-up to L1**: On the Buy screen, call `GET /v1/crypto/onramp_transaction_limits`. If the amount exceeds the L0 tier limit, or if creating a session returns `crypto_onramp_missing_identity_verification`, route the customer to the KYC screen for L1.
2. **KYC (L1)**: Call `attachKYCInfo` with `dateOfBirth`, `idNumber`, and `idType`. Poll until L1 `verification_status` is `verified`.
3. **Return to Buy**: After L1 verification succeeds, return the customer to the Buy screen. Call `GET /v1/crypto/onramp_transaction_limits` again to check the customer’s L1 tier limits.
4. **Detect step-up to L2**: If the amount exceeds the L1 tier limit, or if creating a session returns `crypto_onramp_missing_document_verification`, route the customer to the KYC screen for L2.
5. **KYC (L2)**: Call `verifyIdentity` to present the Stripe-hosted identity verification flow. Poll `GET /v1/crypto/customers/:id` until L2 `verification_status` is `verified` or `rejected`.
6. **Return to Buy**: After L2 verification succeeds, refresh limits and recheck the amount using the [step-up detection strategy](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#step-up-detection).

### L1 verification succeeds 

The customer provides L0 and L1 information upfront and passes both tiers. This approach collects more information initially but avoids step-up interruptions for moderate purchase amounts.

L1 verification succeeds: Login, KYC (L0 + L1), Wallet, Payment, and Buy screens in sequence, ending in a successful purchase at L1 limits. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0 + L1)]
[KYC (L0 + L1)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] --> [Success]
```

#### Recommended operations 

1. **KYC (L0 + L1)**: Call `attachKYCInfo` with all L0 and L1 fields in a single request: `firstName`, `lastName`, `address`, `dateOfBirth`, `idNumber`, `idType`. Poll until both L0 and L1 `verification_status` values are `verified`.
2. **Buy**: Fetch the customer’s current limits and proceed only if the amount is within the returned limit.

### L1 succeeds, step-up to L2 

The customer is L1-verified but attempts a purchase that exceeds L1 limits. The KYC screen shows the Stripe-hosted identity document verification flow.

L1 succeeds, step-up to L2: after the Buy screen detects the amount exceeds L1 limits, the user goes to the KYC screen for L2 document verification before retrying the purchase. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0 + L1)]
[KYC (L0 + L1)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] -- exceeds L1 limit --> [KYC (L2)]
[KYC (L2)] -- verified --> [Buy (retry)]
[Buy (retry)] --> [Success]
```

#### Recommended operations 

1. **Detect step-up to L2**: If the session creation returns `crypto_onramp_missing_document_verification`, or limits indicate the amount requires L2, route the customer to the KYC screen for L2.
2. **KYC (L2)**: Call `verifyIdentity`. Poll until L2 is `verified` or `rejected`.
3. **Return to Buy**: After verification succeeds, refresh limits and recheck the amount using the [step-up detection strategy](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#step-up-detection).

### L2 verification succeeds 

The customer completes all three tiers upfront in a single KYC screen visit and purchases within their returned limits without further KYC tier step-ups. On the KYC screen, L1 form fields are collected and verified first, then `verifyIdentity` is called for L2 within the same screen.

L2 verification succeeds: Login, KYC (L0 + L1 + L2), Wallet, Payment, and Buy screens in sequence, ending in a successful purchase within the customer's returned limits. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[Login] --> [KYC (L0 + L1 + L2)]
[KYC (L0 + L1 + L2)] --> [Wallet]
[Wallet] --> [Payment]
[Payment] --> [Buy]
[Buy] --> [Success]
```

#### Recommended operations 

1. **KYC (L0 + L1)**: Call `attachKYCInfo` with all L0 and L1 fields: `firstName`, `lastName`, `address`, `dateOfBirth`, `idNumber`, `idType`. Poll `GET /v1/crypto/customers/:id` until L1 `verification_status` is `verified`.
2. **KYC (L2)**: After L1 is verified, call `verifyIdentity` to present the Stripe-hosted identity document verification flow within the same KYC screen. Poll until L2 `verification_status` is `verified`.
3. **Buy**: Fetch limits and recheck the amount using the [step-up detection strategy](https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices.md#step-up-detection) before getting a quote and creating a session.

### Handling verification failures 

When verification fails at any tier, the `verification_status` is `rejected` and `verification_errors` contains details about the failure. Handle these cases based on the tier.

#### Transaction eligibility after a step-up failure 

If a step-up fails, transaction eligibility depends on which tier failed.

- **L2 failure with L1 previously verified**: The customer can continue to transact within their L1 tier limits. Your integration should fall back to the Buy screen with L1 limits displayed.
- **L1 failure with L0 previously verified**: The customer can’t fall back to transacting within L0 limits. After L1 verification information has been submitted, the customer must pass L1 verification before they can transact again. Your integration should prompt the customer to correct and resubmit their L1 information.

In general, submitting information for a higher tier is a one-way transition for L0 and L1. Design your step-up flows to clearly communicate to customers that providing L1 information (date of birth and SSN) replaces their L0 verification status and requires successful L1 verification to continue using the onramp.

#### L0 failure 

If L0 verification fails, the customer can still proceed by completing L1 verification. L1 supersedes L0.

L0 failure: when L0 verification is rejected, the user can still pass by completing L1 verification. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[KYC (L0)] -- rejected --> [KYC (L1)]
[KYC (L1)] -- verified --> [Continue]
```

Prompt the customer to provide additional information (date of birth and SSN) to verify at L1.

#### L1 failure 

If L1 verification fails, check the `verification_errors` field for details. Common causes include mismatched identity information. The customer can retry by calling `attachKYCInfo` again with corrected information.

L1 failure: when L1 verification is rejected, the user can correct and resubmit their information. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[KYC (L1)] -- rejected --> [Show error]
[Show error] --> [KYC (L1 retry)]
[KYC (L1 retry)] -- verified --> [Continue]
```

Display a user-friendly error message based on `verification_errors` and allow the customer to correct and resubmit.

#### L2 failure 

If L2 verification fails, the customer can retry the identity document upload. Call `verifyIdentity` again to present a new verification session.

L2 failure: when L2 verification is rejected, the user can retry the identity document upload. (See full diagram at https://docs.stripe.com/crypto/onramp/kyc-integration-best-practices)

```text
[KYC (L2)] -- rejected --> [Show error]
[Show error] --> [KYC (L2 retry)]
[KYC (L2 retry)] -- verified --> [Continue]
```

Common L2 failures include blurry photos, expired documents, or selfie mismatches. Prompt the customer to retry with clearer images.
