Skip to main content
The wallet-ownership widget is a hosted page that proves one of your customers controls a specific crypto wallet address. Under the EU TFR, a VASP must verify ownership of a self-hosted (unhosted) wallet before releasing funds to or from it above the sunrise threshold - the widget is how Didit collects that proof without you building signing flows yourself. A verified proof marks the matching wallet address book entry as is_ownership_verified: true, which lets a linked Travel Rule transfer advance out of UNCONFIRMED_OWNERSHIP.

When Didit mints it for you

You rarely need to mint a session yourself. When auto_wallet_verification is on in your Travel Rule settings (the default), Didit auto-mints a widget session at transaction creation whenever a transfer needs end-user proof of wallet control:
  • The transfer is in UNCONFIRMED_OWNERSHIP - the counterparty wallet was declared self-hosted at submission, or an inbound transfer hit an unverified address-book entry.
  • The transfer is outbound and in COUNTERPARTY_VASP_NOT_FOUND or ON_HOLD - no counterparty VASP could be reached, so ownership proof is the remaining path to completion.
The auto-minted session is returned in the transaction’s action_required block (type: "wallet_ownership", with the url to open), and the SDKs launch it automatically on client-side submission. Mint manually with POST /v3/travel-rule/widget-session/ when you want to pre-verify a wallet before any transfer exists, or when you have auto_wallet_verification turned off.

What it proves

The widget offers up to four proof methods. Which ones appear depends on how you minted the session and your Travel Rule settings:

Satoshi test verify contract

Verifying a Satoshi test deposit is asynchronous, because the transaction has to be seen on-chain and then reach enough confirmations. Submitting the deposit’s txid returns 200 with status: "PENDING" the moment the deposit is seen on-chain but before it has enough confirmations - the txid is retained, not discarded. It returns 200 with status: "VERIFIED" once the deposit confirms. It returns 400 only for a genuinely wrong deposit: absent, sent from an unexpected address, or the wrong amount. It returns 410 if the test window expires before a valid deposit is seen. A background job automatically rechecks pending deposits and completes the proof the moment the transaction confirms, so the customer is never stranded after sending funds - they do not need to resubmit the txid. When SATOSHI_TEST is offered, the widget session’s own expires_at is extended to cover the Satoshi test window, giving the deposit time to confirm before the session itself expires.

Flow

1

Mint a widget session

Call POST /v3/travel-rule/widget-session/ with the wallet you need verified. Optionally link it to a transfer so the transfer advances automatically once ownership is proven.
2

Open the returned URL

Send your customer to the url from the response. They complete a proof there - no Didit account or API key needed; the token in the URL is the authentication.
3

Detect completion

When a proof is verified, the widget marks the address-book entry verified and, if the session is linked to a transfer, advances it. Watch for travel_rule.status.updated (linked transfers) or poll the widget context.

Mint a widget session

Always open the url returned in the response verbatim - do not construct it yourself.

Open the returned URL

The widget is web-only in this release. Open the url in the system browser or a custom tab - never inside a Didit mobile SDK webview.Wallet proofs rely on deep links that hand off to the customer’s wallet app (WalletConnect, Phantom, a Bitcoin wallet) and back. Those deep links cannot escape an in-app webview, so a widget opened inside the Didit Web/Flutter/React Native/iOS/Android SDK webview will dead-end when the wallet tries to return.On mobile, launch the url with the OS browser (SFSafariViewController / Chrome Custom Tabs, or the default browser) instead of rendering it in a webview. On web, a normal tab or popup is fine.

Detect completion

The widget exposes a public read endpoint keyed by the token - poll it to see progress, or rely on webhooks when the session is linked to a transfer:
  • completed_at becomes non-null once a proof is verified.
  • Each entry in proofs carries the fields its method needs: a SATOSHI_TEST proof adds deposit_address, expected_amount, and its own expires_at; a MESSAGE_SIGNING proof adds challenge, the string the customer signs.
  • A SATOSHI_TEST proof can sit at status: "PENDING" while its deposit is seen on-chain but unconfirmed, with no action needed from you or the customer - see Satoshi test verify contract above.
  • When the session was minted with a transaction_id, verifying the proof advances the linked transfer (out of UNCONFIRMED_OWNERSHIP, or resolving an inbound TRP transfer), and a travel_rule.status.updated webhook fires. Prefer the webhook over polling.
  • The context endpoint returns 404 for an unknown token and 410 once the session has expired without completing.
There are no one-shot dependencies here: the widget and your backend both converge on the same durable state (the verified address-book entry and the transfer status), so a dropped webhook or a closed tab is always recoverable by re-reading the transfer or the widget context.

Localization and branding

The widget renders in 54 languages, following the language path of the link (or the browser default). When whitelabel is enabled for your application, the widget inherits your verification-flow branding automatically: logo, brand colors, button and panel styling, custom font, favicon, and your app’s public name in the title. No separate configuration - the same customization you set up for KYC sessions applies here. A user who returns to an already-completed link sees the completion screen again; if you passed a callback_url, the widget returns them there. Expired links show a branded expiry screen prompting them to request a new link (mint a new session or use action: "resend" flows as appropriate).

Next steps

Travel Rule

The full exchange lifecycle, rails, settings, and receiving flows.

Wallet address book

Register the wallets you control so inbound transfers resolve against them.

Webhooks

Subscribe to travel_rule.status.updated to react when a proof advances a transfer.

Native SDKs

Open the widget URL with the system browser, not the SDK webview.