> ## Documentation Index
> Fetch the complete documentation index at: https://docs.crossmint.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Proof of Ownership

> Understand when and how wallet ownership verification works for onramp transactions

When onramping to non-Crossmint wallets, the onramp may require the user to prove they own the recipient wallet by signing a message. This is a legal requirement under anti-money laundering (AML) and counter-terrorism financing (CTF) regulations, which mandate verification of the beneficiary wallet owner's identity above certain transaction thresholds. This page explains when proof of ownership is required and how it works.

## When Is Proof of Ownership Required?

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>
  Proof of ownership only applies to **non-Crossmint wallets**. Crossmint-managed wallets do not require ownership verification.
</Note>

## How It Works

<Steps>
  <Step title="Link External Wallet">
    Before creating an onramp order to an external wallet, link the wallet to a Crossmint user using the Link External Wallet API:

    ```bash theme={null}
    curl --request PUT \
        --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
        --header 'X-API-KEY: YOUR_API_KEY' \
        --header 'Content-Type: application/json' \
        --data '{
            "chain": "base-sepolia"
        }'
    ```

    The API responds with a verification challenge following the <a href="https://chainagnostic.org/CAIPs/caip-122" target="_blank">CAIP-122</a> standard:

    ```json theme={null}
    {
        "address": "0x1234...5678",
        "chain": "base-sepolia",
        "type": "external-wallet",
        "ownership": {
            "verified": false,
            "verificationChallenge": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
        }
    }
    ```
  </Step>

  <Step title="Create Order">
    Create the onramp order as usual. If verification is required, the order response will have status `requires-recipient-verification`:

    ```json theme={null}
    {
        "order": {
            "payment": {
                "status": "requires-recipient-verification",
                "preparation": {
                    "message": "crossmint.com wants you to sign in with your blockchain account:\n0x1234...5678\n\n..."
                }
            }
        }
    }
    ```
  </Step>

  <Step title="Sign the Message">
    Have the user sign the `preparation.message` using their wallet's private key:

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

      const wallet = new Wallet("YOUR_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 YOUR_SECRET_KEY_BYTES = new Uint8Array(64); // Replace with your actual secret key bytes
      const keypair = nacl.sign.keyPair.fromSecretKey(YOUR_SECRET_KEY_BYTES);

      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">
    Submit the signature as proof of ownership using the same Link External Wallet API:

    ```bash theme={null}
    curl --request PUT \
        --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
        --header 'X-API-KEY: YOUR_API_KEY' \
        --header 'Content-Type: application/json' \
        --data '{
            "chain": "base-sepolia",
            "proof": "YOUR_SIGNATURE"
        }'
    ```

    Once verified, the response confirms ownership:

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

  <Step title="Complete Order">
    After ownership is verified, fetch the order using the [Get Order API](/api-reference/headless/get-order). The status will advance to:

    * **`requires-kyc`** — the user has not completed KYC yet.
    * **`awaiting-payment`** — the user can proceed to payment.
  </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. This avoids requiring verification during the checkout flow.

```bash theme={null}
curl --request PUT \
    --url https://staging.crossmint.com/api/2025-06-09/users/YOUR_USER_LOCATOR/linked-wallets/YOUR_WALLET_ADDRESS \
    --header 'X-API-KEY: YOUR_API_KEY' \
    --header 'Content-Type: application/json' \
    --data '{
        "chain": "base-sepolia",
        "proof": "YOUR_SIGNATURE"
    }'
```

## Related Resources

* [Onramp to Non-Crossmint Wallets](/onramp/guides/onramp-to-external-wallets) — full guide for non-Crossmint wallet onramp integration
* [React Quickstart (External Wallets tab)](/onramp/quickstarts/react) — quickstart with external wallet support
