Skip to main content
This guide walks you through setting up and resolving a declared- or verified-level Travel Rule verification for a saved crypto address — see Address Book overview for how verification levels work.

Prerequisites

  • The user has completed onboarding and has the required capabilities enabled.
  • The user already has a crypto external account — see Create external account.
  • For the verified level, the user also needs the crypto-deposits capability.

Declared level

Set up the verification

Call Set up crypto external account verification with level: "declared".
A 201 response means a new verification was initiated. Its requestForInformation.proofOptions.selfDeclaration.schema describes exactly which fields to collect from the user.
Calling this endpoint again is safe: a new request for information is initiated (201) only when there is no declared verification yet or the last one failed. While it’s pending, or once it’s completed, the same verification is returned unchanged (200) — it’s never redone or regressed.
If a stronger, verified-level verification is already completed for this address, this returns 409 operation_not_allowed with details.reasons including stronger-verification-already-completed — there’s nothing to declare, a cryptographic proof already covers it.

Resolve the Travel Rule RFI

For a crypto-address-travel-rule RFI, selfDeclaration is the only mechanism listed under proofOptions — see Address Book overview for why.

Self-declaration

Present the fields from proofOptions.selfDeclaration.schema to the user, then submit their answers with Update request for information.
When isVASP is true, also include a vaspDid identifying the VASP that owns the address. If it can’t be resolved by the Travel Rule provider, the request returns 404 entity_not_found (details.entity: "vasp") — ask the user to double-check the VASP they selected.

Confirm the verification

Call Get external account verification to confirm the verification is completed.
The address now carries a completed declared verification, reflected in the account’s compact verifications array on Get external account.

Testing

Verify the following:
  1. Set up crypto external account verification returns 201 with a pending verification and a crypto-address-travel-rule RFI.
  2. Update request for information with a valid self-declaration proof returns 200 with status: "ok".
  3. Get external account verification now returns status: "completed".
  4. Calling Set up crypto external account verification again returns 200 with the same, unchanged verification.

Verified level

After setup, the requestForInformation.proofOptions offers two independent proof options for a verified-level verification — the user (or your backend) resolves whichever is more convenient: signing a message, or sending an exact on-chain amount. Either one resolves the same verification.

Set up the verification

Call Set up crypto external account verification with level: "verified".
A 201 response means a new verification was initiated, returning both proof options.
Calling this endpoint again is safe: a new request for information is initiated (201) only when there is no verified verification yet or the last one failed (e.g. it expired). While it’s pending, or once it’s completed, the same verification is returned unchanged (200) — it’s never redone or regressed.
Setting up and getting a verified verification both return 409 user_capability_failure with details.capability.code: "crypto-deposits" when the user lacks that capability. The declared level isn’t affected.
To receive the Satoshi test, Uphold uses the user’s default account for the external account’s asset, creating one if there is none, and creates a crypto deposit address for the network if needed. A new account or deposit address may therefore appear in the user’s accounts after the first verified set up.

Submit a proof

Have the user sign proofOptions.messageSigning.message verbatim with their wallet, producing a proof that validates against the returned proofOptions.messageSigning.schema. Then submit it with Update request for information.
The proof’s type is the signing scheme used by the wallet — one of the values in proofOptions.messageSigning.schema.properties.proof.properties.type.enum (e.g. bip-137, eip-191).
If the signature doesn’t validate, this returns 409 proof_invalid. A proof that doesn’t match proofOptions.messageSigning.schema (for example a type outside the allowed values or a missing field) is rejected earlier with 400 request_invalid, and an expired request for information returns 409 request_for_information_expired.

Confirm the verification

Call Get external account verification to confirm the verification is completed. The normalized public proof shape is the same regardless of which option was used to resolve it.
The address now carries a completed verified verification, reflected in the account’s compact verifications array on Get external account.

Testing

Verify the following:
  1. Set up crypto external account verification with level: "verified" returns 201 with a pending verification and a crypto-address-verification RFI exposing both messageSigning and satoshiTest proof options.
  2. Update request for information with a valid message signing proof returns 200 with status: "ok", and with an invalid signature returns 409 proof_invalid.
  3. Sending the exact Satoshi test amount makes Get request for information return status: "ok", without needing to update the RFI directly.
  4. Get external account verification now returns status: "completed" with the normalized proof.
  5. Calling Set up crypto external account verification again while pending or once completed returns 200 with the same, unchanged verification.
  6. Without the crypto-deposits capability, set up and get for verified return 409 user_capability_failure.