Unhosted wallet verification

Use wallet ownership determination to comply with FATF requirements.

The Sumsub Travel Rule solution allows unhosted wallet controllers to securely prove wallet ownership using a cryptographic signature.

What is an unhosted wallet?

An unhosted wallet—also known as self-hosted or non-custodial—refers to a type of digital wallet that is hosted and controlled by the user, as opposed to being hosted by any exchanges, markets, or other VASPs, which means that:

  • The applicant's cryptocurrency balances are off any third parties.
  • The applicant did not pass any KYC or customer due diligence processes.

Such wallets offer their owners direct control over their private keys and, consequently, the security and management of their digital assets.

For example, a MetaMask wallet is considered unhosted, and a Centralized Crypto Exchange (CEX) account represents a hosted wallet, as you rely on a third party (custodian) to control your funds.

Why do you need Unhosted Wallet Verification?

Countries that follow the Travel Rule requirements may oblige VASPs to verify the ownership of self-hosted wallets before transacting with them.

Such verification includes, but is not limited to, the following:

  • Collecting relevant Travel Rule information related to Unhosted Wallets from their customers.
  • Introducing additional mitigation measures, such as verifying the identity of the Unhosted Wallet owner or performing enhanced due diligence.
  • Limiting or restricting transactions with unhosted wallets.
  • Using blockchain analytics services to mitigate some of the ML/TF risks of unhosted wallets.

Apart from compliance with regulations such as the Travel Rule, unhosted wallets verification is crucial for businesses for the following reasons:

  • Preventing money laundering and terrorist financing.
  • Mitigating the risks associated with anonymous transactions.
  • Establishing trust with their customers and reducing the potential for fraud.
  • Enhancing security and accountability in the cryptocurrency ecosystem.

How Unhosted Wallet Verification works?

Let’s say Alice, based in Switzerland, wants to withdraw some funds from her VASP and initiates a corresponding transaction. As the VASP is also based in Switzerland, it has to follow local regulatory requirements and verify who controls the wallet before allowing the transaction:

  1. The VASP gets notified about the transaction and sends Alice a message asking her to confirm wallet control. This message contains a link and a private encrypted verification key.
  2. Alice confirms wallet ownership by following the link in the message and signing in to her wallet with a secure key that is available only to her.
  3. After that, the wallet is verified, the transaction is sent for further processing, and the wallet is stored in the system so that Alice won’t have to verify it again.

Sumsub applies two methods for Unhosted Wallet Verification:

  • Signature proof (Sumsub WebSDK) → works if you have a cold wallet on Metamask, Ledger, or WalletConnect.
  • Quick proof (21 Analytics interface) → works for any wallet that one can sign a message with.

How to enable Unhosted Wallet Verification?

If you have never used Sumsub, visit our website and click Get started to begin your journey or contact our sales department.

If you are already a Sumsub customer, in the Dashboard, open the Rules Library and enable the following rules:

Verify unhosted wallets

The following diagram shows a typical wallet verification flow that is:

  • Seamless — all done via a link.
  • Integrated with the most popular wallets — Metamask, Ledger, Trezor, and any WalletConnect solution.

Step 1: Create transaction

Transactions are sent with the following conditions:

  • Transactions have no attribution, i.e. we did not manage to identify whom the key/wallet belongs to.
  • Unhosted Wallet Verification is enabled as described here.

Submit a Travel Rule transaction using any of the following methods:

curl -X POST \ 'https://api.sumsub.com/resources/applicants/66e056c7776e5a46bf63da0a/kyt/txns/-/data'\
-H 'Accept: application/json'\
-H 'Content-Type: application/json'\
-d '{
      "txnId": "z527pptec8492lrq9pa4b",
      "type": "travelRule",
      "applicant": {
        "address": {
          "country": "CHE"
        },
        "institutionInfo": {
          "internalId": "AliceVaspId"
        },
        "paymentMethod": {
          "type": "crypto",
          "accountId": "bc1qwxdppz8623cewq46tg4wnrp9nu7jj4dx3nr70n",
          "issuingCountry": "CHE"
        },
        "device": {
          "ipInfo": {
            "ip": "130.60.28.120"
          }
        },
        "type": "individual",
        "fullName": "Alice Doe"
      },
      "counterparty": {
        "paymentMethod": {
          "type": "crypto",
          "accountId": "bc1qtspgw38syqvyullsac4lnymm6qj38s6d35pchn"
        },
        "fullName": "Joe Doe",
        "externalUserId": "JoeDoeID",
        "type": "individual"
      },
      "info": {
        "direction": "out",
        "amount": 0.02,
        "currencyType": "crypto",
        "currencyCode": "BTC"
      },
      "txnDate": "2024-08-24 23:37:02+0000"
    }'
curl -X POST \ 'https://api.sumsub.com/resources/applicants/-/kyt/txns/-/data?levelName=basic-kyc-level'\
-H 'Accept: application/json'\
-H 'Content-Type: application/json'\
-d '{
      "txnId": "z527pktec34922lrq9pa4b",
      "type": "travelRule",
      "applicant": {
        "address": {
          "country": "CHE"
        },
        "institutionInfo": {
          "internalId": "AliceVaspId"
        },
        "paymentMethod": {
          "type": "crypto",
          "accountId": "bc1qwxdzpz8653cewq46tg4wnrp9nu7jj4dx3nr70n",
          "issuingCountry": "CHE"
        },
        "device": {
          "ipInfo": {
            "ip": "130.60.28.120"
          }
        },
        "type": "individual",
        "fullName": "Alice Doe",
        "externalUserId": "AliceId"
      },
      "counterparty": {
        "paymentMethod": {
          "type": "crypto",
          "accountId": "bc1qtspgw38syqvynllsac4lnypm6qj38s6d35pchn"
        },
        "fullName": "Joe Doe",
        "externalUserId": "JoeDoeID",
        "type": "individual"
      },
      "info": {
        "direction": "out",
        "amount": 0.02,
        "currencyType": "crypto",
        "currencyCode": "BTC"
      },
      "txnDate": "2024-08-24 23:37:02+0000"
    }'
curl -X POST \ 'https://api.sumsub.com/resources/kyt/misc/txns/import' \
-H 'Content-Type: application/x-ndjson' \ 
-d $'{
        "applicantId": "636cee6b17d6c000144673b",
        "data": {
          "txnId": "631f268442d8290071e1eee8_newTxn",
          "type": "travelRule",
          "applicant": {
            "externalUserId": "AliceDoeId",
            "address": {
              "country": "CHE"
            },
            "device": {
              "ipInfo": {
                "ip": "130.60.28.120"
              }
            },
            "institutionInfo": {
              "internalId": "AliceVaspId"
            },
            "paymentMethod": {
              "type": "crypto",
              "accountId": "bc1qwxdzpv8623cewq46tg4wnrp9nu7jj4dx3nr70n",
              "issuingCountry": "CHE"
            }
          },
          "counterparty": {
            "paymentMethod": {
              "type": "crypto",
              "accountId": "bc1qtspgw38syqvyullsac8lnypm6qj38s6d35pchn"
            },
            "fullName": "Joe Doe",
            "externalUserId": "JoeDoeID",
            "type": "individual"
          },
          "info": {
            "direction": "out",
            "amount": 0.02,
            "currencyType": "crypto",
            "currencyCode": "BTC"
          }
        }
      }\n{
        "applicantId": "66e066c3756e5a46bf63da0a",
        "data": {
          "txnId": "631f268442h8290001e1eee9_newTxn",
          "type": "travelRule",
          "applicant": {
            "externalUserId": "JoeDoeId",
            "address": {
              "country": "CHE"
            },
            "device": {
              "ipInfo": {
                "ip": "130.60.38.120"
              }
            },
            "institutionInfo": {
              "internalId": "JoeDoeId"
            },
            "paymentMethod": {
              "type": "crypto",
              "accountId": "0x4d1f36b2vc2d7b42f71f0b381b6d9d4ce1282721"
            }
          },
          "counterparty": {
            "paymentMethod": {
              "type": "crypto",
              "accountId": "0xf9b12293340a1153601bc8d8a858f0a24a54e655"
            },
            "fullName": "James Doe",
            "externalUserId": "JamesDoeID",
            "type": "individual"
          },
          "info": {
            "direction": "out",
            "amount": 0.01,
            "currencyType": "crypto",
            "currencyCode": "ETH"
          }
        }
      }'

If the transaction is approved, you will get the applicantKytTxnApproved webhook. For more information about the webhooks, see this article.

{
  "applicantId": "634829375766b80001a40152",
  "applicantType": "individual",
  "correlationId": "f24f6616020245053139a6537303a251",
  "sandboxMode": false,
  "externalUserId": "AliceId",
  "type": "applicantKytTxnApproved",
  "reviewResult": {
    "reviewAnswer": "GREEN"
  },
  "reviewStatus": "completed",
  "createdAt": "2024-09-10 17:46:24+0000",
  "createdAtMs": "2024-09-10 17:46:24.183",
  "clientId": "sumsub_new_22",
  "kytTxnId": "64a7dc05fbf57c624afcb72d",
  "kytDataTxnId": "uauu08x44xexbohyh4lkp9",
  "kytTxnType": "travelRule"
}

If the transaction was put on hold, you will get the applicantKytOnHold webhook. At this stage, the applicant must confirm wallet ownership via a link.

{
  "applicantId": "66e085ef8c8b010a0d318e47",
  "applicantType": "individual",
  "correlationId": "e38c2eb49fde9291c6a028de2c760104",
  "sandboxMode": false,
  "externalUserId": "AliceId",
  "type": "applicantKytOnHold",
  "reviewStatus": "onHold",
  "createdAt": "2024-09-10 17:46:24+0000",
  "createdAtMs": "2024-09-10 17:46:24.183",
  "clientId": "sumsub_new_22",
  "kytTxnId": "66e075ef8c8b910a0d318e4a",
  "kytDataTxnId": "z527mptec34922lrq9pa4b",
  "kytTxnType": "travelRule"
}

Step 2: Retrieve verification link and send it to the applicant

When the applicantKytOnHold webhook is received, you must obtain the wallet confirmation link that you must send to the applicant. By following the link, the applicant is asked to sign/encrypt the Initial Message with his Private Key.

Depending on your choice, the applicant might either receive a link leading to Signature proof (Sumsub WebSDK) or Quick proof (21 Analytics interface). The link type is configured during the integration phase.

To get the link, use either of the following methods:

  • Pass the applicant ID to this API method and get the link from the paymentSource.ownershipChecks.quickproof object. Once you have the link, send it to the applicant so that they can confirm wallet ownership.
  • Use the WebSDK preview link that is provided in the Wallet ownership verification section of the transaction card.

🚧

Important

When the applicant issues multiple transactions to the same wallet, we send only one confirmation link via which the applicant confirms all of the transactions at once.

Step 3: Wait until the applicant confirms wallet ownership

Applicants encrypt the Initial Message manually or using the Quick proof mechanism. The UI on this step will be different for Signature proof and Quick proof.

Signature proof (Sumsub WebSDK) user flow

The Quick proof flow is as follows:

  1. Applicant reads the sensitive data disclaimer.
  2. Applicant selects a wallet type.
  3. Applicant is given a choice of confirming wallet ownership on the current device or on a mobile phone.
  4. Applicant verifies wallet ownership by signing the message.
  5. We verify the signed message.
  6. We display verification results.

Quick proof (21 Analytics) user flow

The Signature proof flow is as follows:

  1. Applicant selects a wallet type.
  2. Applicant reads the instructions on how to register Ledger Wallet.
  3. Applicant signs the message.
  4. Applicant sees verification results.

Step 4: We verify the results

At this step, we receive the Encrypted Message, decrypt it, and check with the Initial Message. If the messages are the same, the applicant has passed verification, in which case, we calculate the transaction risk score and send you the applicantKytTxnApproved webhook to validate successful wallet confirmation.