> ## Documentation Index
> Fetch the complete documentation index at: https://crossmint-devin-1787949784-wallet-docs-two-concept-model.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Onramp to Non-Crossmint Wallets

> Enable onramp orders to wallets not powered by Crossmint

This guide walks you through how to enable onramp orders to external wallets.

<Note>
  **Internal vs. external wallets:** When you create a Crossmint wallet for a user, set the [`owner`](/wallets/guides/create-wallet) to that user's locator so the wallet is recognized as theirs — onramps to those wallets do not require ownership verification. A wallet created without an `owner`, or any wallet not created through Crossmint, is treated as **external**: you must link it to the user and verify ownership, as shown below.
</Note>

## When Is Proof of Ownership Required?

Proof of ownership confirms that an onramp user controls the wallet receiving their assets. It helps Crossmint meet anti-money laundering (AML) and counter-terrorism financing (CTF) requirements by linking the user's verified identity to the recipient wallet.

Proof of ownership is triggered based on transaction volume thresholds:

| Condition                                                         | Verification Required? |
| :---------------------------------------------------------------- | :--------------------- |
| Transaction below **\$1,000** AND 30-day volume below **\$1,000** | No                     |
| Transaction above **\$1,000** OR 30-day volume above **\$1,000**  | Yes                    |

<Note>
  These thresholds apply to users verified outside the United States who onramp to external wallets.
</Note>

To link the user's identity to an external wallet, Crossmint asks the user to sign a challenge with the recipient wallet and submit the signature in the `proof` field. This flow works the same across chains and for all wallet types, including externally owned accounts (EOAs) and smart contract wallets.

## Integration Steps

<Steps>
  <Step title="Link External Wallet to Crossmint User">
    Before creating an onramp order to an external wallet, you must link the wallet to a Crossmint user (Crossmint creates the user automatically if the locator does not exist yet), using the Link External Wallet API:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url https://staging.crossmint.com/api/2025-06-09/users/{userLocator}/linked-wallets/{address} \
          --header 'X-API-KEY: <x-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "chain": "base-sepolia",
              "proof": "<signature>"
          }'
      ```

      ```js Node.js theme={null}
      const options = {
          method: 'PUT',
          headers: {
              'X-API-KEY': '<x-api-key>',
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              chain: "base-sepolia",
              proof: "<signature>"
          })
      };

      const userLocator = "email:user@example.com";
      const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/linked-wallets/${walletAddress}`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```
    </CodeGroup>

    The `proof` field is optional:

    * If you omit it, you are simply linking the external wallet to the user without proving ownership yet.
    * If you pass it, you prove ownership of the wallet in the same request.

    When linking without proof, the API responds with a verification challenge:

    ```json theme={null}
    {
        "address": "0x1234567890abcdef1234567890abcdef12345678",
        "chain": "base-sepolia",
        "type": "external-wallet",
        "ownership": {
            "verified": false,
            "verificationChallenge": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\nI am signing this message to prove ownership of my wallet address 0x1234...5678 for Crossmint verification.\n\nURI: https://..."
        }
    }
    ```

    The `verificationChallenge` is a message in the <a href="https://chainagnostic.org/CAIPs/caip-122" target="_blank">CAIP-122</a> format for the recipient wallet to sign. CAIP-122 defines only the message format; all wallets, including externally owned accounts (EOAs) and smart contract wallets, use the same verification flow.
  </Step>

  <Step title="Create Onramp Order">
    Create an onramp order using the [Create Order API](/api-reference/headless/create-order) with the linked external wallet address as the recipient.

    <Note>
      Depending on the transaction value and the user's past activity, the order may require wallet ownership verification (steps 3 and 4). If verification is not required, the order proceeds directly to the KYC/payment phase and you can skip to step 5.
    </Note>

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request POST \
          --url https://staging.crossmint.com/api/2022-06-09/orders \
          --header 'X-API-KEY: <x-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "recipient": {
                  "walletAddress": "0x1234567890abcdef1234567890abcdef12345678"
              },
              "payment": {
                  "method": "card",
                  "receiptEmail": "user@example.com"
              },
              "lineItems": [
                  {
                      "tokenLocator": "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
                      "executionParameters": {
                          "mode": "exact-in",
                          "amount": "10"
                      }
                  }
              ]
          }'
      ```

      ```js Node.js theme={null}
      const options = {
          method: 'POST',
          headers: {
              'X-API-KEY': '<x-api-key>',
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              recipient: {
                  walletAddress: "0x1234567890abcdef1234567890abcdef12345678"
              },
              payment: {
                  method: "card",
                  receiptEmail: "user@example.com"
              },
              lineItems: [
                  {
                      tokenLocator: "base-sepolia:0x036CbD53842c5426634e7929541eC2318f3dCF7e",
                      executionParameters: {
                          mode: "exact-in",
                          amount: "10"
                      }
                  }
              ]
          })
      };

      fetch('https://staging.crossmint.com/api/2022-06-09/orders', options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```
    </CodeGroup>

    The order may or may not require wallet ownership verification:

    * **Below threshold**: If the transaction value is below **\$1,000** **and** the user's past 30-day onramp transaction volume is below **\$1,000**, ownership verification is not required. The order proceeds directly to the KYC/payment phase.
    * **Above threshold**: If either threshold is surpassed, the order status will be `requires-recipient-verification` and the user must sign a message to prove wallet ownership (steps 3 and 4).

    When ownership verification is required, the order response will look like this:

    ```json theme={null}
    {
        "clientSecret": "...",
        "order": {
            "orderId": "987e81ab-8c8f-464e-95e9-11ceda80d559",
            "phase": "payment",
            "lineItems": [...],
            "quote": {...},
            "payment": {
                "status": "requires-recipient-verification",
                "method": "card",
                "currency": "usd",
                "preparation": {
                    "message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\nI am signing this message to prove ownership of my wallet address 0x1234...5678 for Crossmint verification.\n\nURI: https://...\nVersion: 1\nNonce: ...\nIssued At: ...\nExpiration Time: ...\nRequest ID: ...\nChain ID: base-sepolia"
                }
            }
        }
    }
    ```
  </Step>

  <Step title="Sign Message (conditional)">
    <Info>
      This step is only required if the order status is `requires-recipient-verification`.
    </Info>

    Have the user sign the `preparation.message` with the recipient wallet. All wallets, including EOAs and smart contract wallets, use the same verification flow.

    <CodeGroup>
      ```js EVM (ethers.js) theme={null}
      import { Wallet } from "ethers";

      const PRIVATE_KEY = "YOUR_WALLET_PRIVATE_KEY"; // Replace with your wallet's private key
      const wallet = new Wallet(PRIVATE_KEY);
      const message = order.payment.preparation.message;
      const signature = await wallet.signMessage(message);
      ```

      ```js Solana (tweetnacl) theme={null}
      import nacl from "tweetnacl";
      import { decodeUTF8 } from "tweetnacl-util";
      import { encode as base64Encode } from "@stablelib/base64";

      const message = order.payment.preparation.message;
      const messageBytes = decodeUTF8(message);
      const signatureBytes = nacl.sign.detached(messageBytes, keypair.secretKey);
      const signature = base64Encode(signatureBytes);
      ```
    </CodeGroup>
  </Step>

  <Step title="Submit Signature (conditional)">
    <Info>
      This step is only required if the order status is `requires-recipient-verification`.
    </Info>

    Use the same Link External Wallet API to submit the signature as proof of ownership. Pass the signature in the `proof` field:

    <CodeGroup>
      ```bash cURL theme={null}
      curl --request PUT \
          --url https://staging.crossmint.com/api/2025-06-09/users/{userLocator}/linked-wallets/{address} \
          --header 'X-API-KEY: <x-api-key>' \
          --header 'Content-Type: application/json' \
          --data '{
              "chain": "base-sepolia",
              "proof": "<signature>"
          }'
      ```

      ```js Node.js theme={null}
      const options = {
          method: 'PUT',
          headers: {
              'X-API-KEY': '<x-api-key>',
              'Content-Type': 'application/json'
          },
          body: JSON.stringify({
              chain: "base-sepolia",
              proof: "<signature>"
          })
      };

      const userLocator = "email:user@example.com";
      const walletAddress = "0x1234567890abcdef1234567890abcdef12345678";

      fetch(`https://staging.crossmint.com/api/2025-06-09/users/${userLocator}/linked-wallets/${walletAddress}`, options)
          .then(response => response.json())
          .then(response => console.log(response))
          .catch(err => console.error(err));
      ```
    </CodeGroup>

    Once ownership is verified, the response will confirm verification:

    ```json theme={null}
    {
        "address": "0x1234567890abcdef1234567890abcdef12345678",
        "chain": "base-sepolia",
        "type": "external-wallet",
        "ownership": {
            "verified": true
        }
    }
    ```
  </Step>

  <Step title="Complete Order">
    Once ownership is verified (or if verification was not required), fetch the order using the [Get Order API](/api-reference/headless/get-order). The order status should now have proceeded to the next phase:

    * **`requires-kyc`**: The user hasn't completed KYC yet and will be guided through the KYC flow first.
    * **`awaiting-payment`**: The user has completed KYC and can proceed to complete payment.

    The user will be guided through Crossmint's embedded components to complete their order, handling payment and any remaining compliance steps automatically.
  </Step>
</Steps>

## Pre-Verifying Ownership

You can verify wallet ownership at the time of linking (before any order is created) by including the `proof` field in the initial Link External Wallet call, as shown in step 1. This avoids requiring verification during the checkout flow.

## Related Resources

* [React Quickstart (External Wallets tab)](/onramp/quickstarts/react) — quickstart with external wallet support
* [Import User KYC Data](/onramp/guides/import-user-kyc-data) — pre-register KYC data so users skip in-checkout verification
