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

# Wallet Ownership Widget

> Hosted widget that proves a customer controls a crypto wallet via message signing, Satoshi test, screenshot, or self-declaration - for the Travel Rule.

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](/transaction-monitoring/travel-rule#wallet-address-book) as `is_ownership_verified: true`, which lets a linked [Travel Rule transfer](/transaction-monitoring/travel-rule) 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](/transaction-monitoring/travel-rule#setup) (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](/transaction-monitoring/travel-rule#declared-self-hosted-wallets) 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](/transaction-monitoring/sdk-transaction-submission#required-user-actions).
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](/transaction-monitoring/travel-rule#setup):

| Method             | How it works                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    | When it's offered                                              |
| ------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------------------- |
| `MESSAGE_SIGNING`  | The customer signs a Didit-issued challenge string with their wallet. Supports EVM, Solana, Bitcoin, and Tron wallets; Didit verifies the signature against the address, and signing with a different account than the one being verified is rejected with a clear switch-accounts prompt. Installed browser wallets are discovered through the EIP-6963 and Wallet Standard protocols, so every extension appears as its own signing option, and mobile wallets connect through WalletConnect. | Always, for supported chain families.                          |
| `SATOSHI_TEST`     | The customer sends a tiny on-chain amount from the wallet to a deposit address you control; Didit's watcher confirms the sender, amount tolerance, and confirmation depth, then automatically completes the proof once the deposit confirms - see [Satoshi test verify contract](#satoshi-test-verify-contract) below.                                                                                                                                                                          | Only when you pass a `satoshi_deposit_address` at mint time.   |
| `SCREENSHOT`       | The customer uploads a screenshot of the wallet showing the address (JPEG/PNG/WebP, up to 10 MB). Unlike the other methods it never auto-verifies: your compliance team reviews it and confirms from the transaction's **Travel Rule** tab in the console, or via [`POST .../travel-rule/ownership/`](/management-api/travel-rule/confirm-ownership).                                                                                                                                           | Only when `allow_screenshot_proof` is `true` in your settings. |
| `SELF_DECLARATION` | The customer attests ownership without cryptographic proof - a last resort where the jurisdiction tolerates it.                                                                                                                                                                                                                                                                                                                                                                                 | Only when `allow_self_declaration` is `true` in your 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

<Steps>
  <Step title="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.
  </Step>

  <Step title="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.
  </Step>

  <Step title="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.
  </Step>
</Steps>

### Mint a widget session

```bash theme={null}
curl -X POST https://verification.didit.me/v3/travel-rule/widget-session/ \
  -H "x-api-key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "wallet_address": "0xBeneficiaryWallet01",
    "chain": "ethereum",
    "holder_name": "Ana Diaz",
    "vendor_data": "user-042",
    "transaction_id": "22222222-3333-4444-5555-666666666666",
    "satoshi_deposit_address": "",
    "callback_url": "https://yourapp.example/wallet-verified",
    "expires_in_minutes": 60
  }'
```

| Field                     | Type          | Description                                                                                                                        |
| ------------------------- | ------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `wallet_address`          | string        | The wallet address to verify.                                                                                                      |
| `chain`                   | string        | Chain identifier for the address (for example `ethereum`, `bitcoin`, `solana`).                                                    |
| `holder_name`             | string        | Name of the wallet holder, used for the beneficiary name match.                                                                    |
| `vendor_data`             | string        | Your internal identifier for the holder, carried onto the created address-book entry.                                              |
| `transaction_id`          | string (uuid) | Optional. Didit transaction UUID of a Travel Rule transfer to advance when the proof verifies. Omit to verify a wallet standalone. |
| `satoshi_deposit_address` | string        | Optional. A deposit address you control; supplying it enables the `SATOSHI_TEST` method.                                           |
| `callback_url`            | string        | Optional. Where the widget returns the customer after completion.                                                                  |
| `expires_in_minutes`      | integer       | Optional. Lifetime of the minted link; automatically extended to cover the Satoshi test window when `SATOSHI_TEST` is offered.     |

```json theme={null}
{
  "widget_session_id": "77777777-8888-9999-aaaa-bbbbbbbbbbbb",
  "token": "3s9x...urlsafe",
  "url": "https://verify.didit.me/wallet-ownership/3s9x...urlsafe",
  "expires_at": "2026-07-06T13:00:00Z"
}
```

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

### Open the returned URL

<Warning>
  **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.
</Warning>

### 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:

```bash theme={null}
curl https://verification.didit.me/v3/travel-rule/widget/{token}/
```

```json theme={null}
{
  "vasp_name": "Origin CASP SL",
  "wallet_address": "0xBeneficiaryWallet01",
  "chain": "ethereum",
  "holder_name": "Ana Diaz",
  "allowed_methods": ["MESSAGE_SIGNING", "SATOSHI_TEST", "SCREENSHOT"],
  "transfer_status": "UNCONFIRMED_OWNERSHIP",
  "callback_url": "https://yourapp.example/wallet-verified",
  "expires_at": "2026-07-06T14:00:00Z",
  "completed_at": null,
  "proofs": [
    {
      "proof_id": "88888888-9999-aaaa-bbbb-cccccccccccc",
      "method": "SATOSHI_TEST",
      "status": "PENDING",
      "wallet_address": "0xBeneficiaryWallet01",
      "chain": "ethereum",
      "deposit_address": "0xYourDepositWallet",
      "expected_amount": "0.00015",
      "expires_at": "2026-07-06T14:00:00Z",
      "created_at": "2026-07-06T13:05:00Z"
    }
  ]
}
```

* `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](#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`](/integration/webhooks#event-types) 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.

<Note>
  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.
</Note>

## 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

<CardGroup cols={2}>
  <Card title="Travel Rule" icon="route" href="/transaction-monitoring/travel-rule">
    The full exchange lifecycle, rails, settings, and receiving flows.
  </Card>

  <Card title="Wallet address book" icon="address-book" href="/transaction-monitoring/travel-rule#wallet-address-book">
    Register the wallets you control so inbound transfers resolve against them.
  </Card>

  <Card title="Webhooks" icon="bell" href="/integration/webhooks">
    Subscribe to travel\_rule.status.updated to react when a proof advances a transfer.
  </Card>

  <Card title="Native SDKs" icon="mobile" href="/integration/native-sdks/overview">
    Open the widget URL with the system browser, not the SDK webview.
  </Card>
</CardGroup>
