Wallet Verification Quickstart

Create a wallet verification, send the link to your user, and read the signed attestation.

This guide takes you from an API key to a verified wallet in five steps.

EnvironmentAPI base URL
Productionhttps://api.wallet-verification.walletconnect.com
Staginghttps://staging.api.wallet-verification.walletconnect.com

Use staging for integration testing only; attestations created there are not evidence for compliance. There is no test mode in production.

Get access and an API key

In the WalletConnect Pay merchant dashboard, open the Wallet Verification tab on the API keys page and request access. Once access is enabled, create an API key. The full key is shown only once, so store it in a secret manager. See Dashboard & API keys for details.

Send the key as a bearer token on every request:

Environment
export WV_API_KEY="wv_your-api-key"
export WV_BASE="https://api.wallet-verification.walletconnect.com"

A missing, unknown, or revoked key, or an account without access, returns 401 unauthorized.

Create a verification

Create a verification
curl -X POST "$WV_BASE/verifications" \
  -H "Authorization: Bearer $WV_API_KEY" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "reference_id": "customer-4821-payout-wallet",
    "purpose": "payout",
    "chain_id": "eip155:1",
    "return_url": "https://app.example.com/wallets"
  }'
FieldRequiredDescription
reference_idYesYour own identifier, 1–200 printable ASCII characters with no spaces. Not included in the signed message.
purposeYeswithdrawal, payout, onboarding, or re_verification. Sets the wording of the signed message.
chain_idNoCAIP-2 chain ID in the eip155 or solana namespace. If omitted, the user chooses Ethereum-compatible or Solana when connecting.
expected_addressNoPins the wallet. The page and the API refuse any other address. Returns 400 invalid_address if it does not fit the pinned chain.
validity_secondsNoHow long the link stays valid. 60 to 2,592,000 (30 days). Defaults to 604,800 (7 days).
metadataNoObject up to 4 KiB, echoed back on the verification. Never signed. Do not put personal data here.
return_urlNoHTTPS URL the page sends the user back to. Up to 2,048 characters, no credentials, and its origin must be registered for your account. See Return to your app.

The request body can be up to 16 KiB. Larger bodies return 413 request_error.

A successful request returns 201 with the verification object plus a url:

201 Created
{
  "id": "ver_3f9a1c2b7d8e4f6a9b0c1d2e3f4a5b6c",
  "state": "pending",
  "reference_id": "customer-4821-payout-wallet",
  "purpose": "payout",
  "chain_id": "eip155:1",
  "expected_address": null,
  "expires_at": "2026-10-15T10:00:00Z",
  "return_url": "https://app.example.com/wallets",
  "created_at": "2026-10-08T10:00:00Z",
  "metadata": {},
  "failure_reason": null,
  "attestation": null,
  "url": "https://wallet-verification.walletconnect.com/s/your-session-token"
}

The url is returned only in this response. Store it with the verification id.

Give the user the url however fits your product: send it as a link, redirect the browser to it, or embed it in an iframe. The verification page walks the user through connecting and signing.

Poll for the result

Poll the verification until its state is terminal: verified, failed, expired, or cancelled.

Read a verification
curl "$WV_BASE/verifications/ver_3f9a1c2b7d8e4f6a9b0c1d2e3f4a5b6c" \
  -H "Authorization: Bearer $WV_API_KEY"

Poll every few seconds while the user is on the page, then back off. If the user returns through your return_url or the page posts a message to your parent window, poll once more to confirm the result server-side.

To find verifications later, list them with GET /verifications. Results are newest first and support limit (1–100, default 20), an opaque cursor, and the filters state, reference_id, from (inclusive), and to (exclusive). The response is { "data": [...], "next_cursor": "..." }, and next_cursor is null on the last page.

Read the result

When state is verified, the attestation field holds the address, the exact signed message, the signature, and how it was checked. See Results & attestations for every field and how to re-check the signature yourself.

Idempotency

Send an Idempotency-Key header (1–255 characters) on create so you can retry safely. Keys are scoped to your account and kept for 24 hours.

  • Retrying with the same key and the same body returns 200 with the same verification and no url. Keep the url from the first 201.
  • Reusing a key with a different body returns 409 idempotency_key_mismatch.

One live session per reference

Each reference_id can have only one pending verification at a time. Creating another while one is pending returns 409 reference_in_progress, and the link is not re-issued:

409 Conflict
{
  "status": "error",
  "error": {
    "code": "reference_in_progress",
    "message": "A verification for this reference is already in progress.",
    "details": {
      "verification_id": "ver_3f9a1c2b7d8e4f6a9b0c1d2e3f4a5b6c",
      "state": "pending",
      "expires_at": "2026-10-15T10:00:00Z"
    }
  }
}

Once the earlier verification reaches a terminal state, create a new one with the same reference_id. Use the re_verification purpose so the signed message says why.

Cancel a verification

Cancel a pending verification to stop its link from working:

Cancel a verification
curl -X POST "$WV_BASE/verifications/ver_3f9a1c2b7d8e4f6a9b0c1d2e3f4a5b6c/cancel" \
  -H "Authorization: Bearer $WV_API_KEY"

The state moves to cancelled. Cancelling a verification in any other state returns 409 not_cancellable.

Error handling

Errors use one envelope. Switch on code. The message text may change.

Error envelope
{
  "status": "error",
  "error": {
    "code": "invalid_body",
    "message": "Human-readable description.",
    "details": {}
  }
}
CodeHTTPMeaning
invalid_body400The request body failed validation.
invalid_address400expected_address is not valid for the pinned chain.
invalid_return_url400return_url is malformed or its origin is not registered for your account.
invalid_cursor400The cursor is invalid or was reused with different filters.
request_error400, 413The request is malformed, or the body is larger than 16 KiB.
unauthorized401The API key is missing, unknown, or revoked, or the account has no access.
not_found404No verification with this ID exists for your account.
not_cancellable409Only pending verifications can be cancelled.
not_verified409The signed message is only available once the verification is verified.
reference_in_progress409A pending verification already exists for this reference_id.
idempotency_key_mismatch409The idempotency key was used with a different body.
internal_error500Something went wrong on the server. Retry with the same idempotency key.

Frequently asked questions

On this page