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

# Start a Wallet-Ownership Proof API - Travel Rule Widget

> Start a wallet-ownership proof from a widget session token: message signing, Satoshi test, screenshot or self-declaration. Picking a method again resumes it.

Start Widget Proof API Prompt
Copy prompt to clipboard

<pre>
  \`Start a wallet-ownership proof for a widget session. This is the call the hosted widget makes when the end user picks a proof method; use it only if I build my own widget UI.

  Endpoint:
  POST [https://verification.didit.me/v3/travel-rule/widget/\{token}/proofs/](https://verification.didit.me/v3/travel-rule/widget/\{token}/proofs/)

  Authentication:
  None - the widget token in the URL path is the credential. Treat the token as a secret.

  Body:

  * method - one of the context's allowed\_methods: MESSAGE\_SIGNING, SATOSHI\_TEST, SCREENSHOT or SELF\_DECLARATION.
  * wallet\_address, chain - optional, default to the session's.

  Behavior:

  * 201 with a new PENDING proof. MESSAGE\_SIGNING returns the challenge to sign; SATOSHI\_TEST returns deposit\_address, expected\_amount and expires\_at; SELF\_DECLARATION is verified at once.
  * 200 with the proof already open when the same method is picked again (same challenge or amount): resume it, do not treat it as an error.
  * Picking another method closes the unfinished proof as METHOD\_SWITCHED, except a submitted screenshot or a test deposit still being checked.
  * After a REJECTED proof, picking a method starts a new one (201). A session takes a limited number of rejected proofs: the context reports max\_attempts, attempts\_used and attempts\_remaining.
  * Message signing works on EVM chains (Ethereum, BNB Chain, Polygon, Arbitrum, Base, Optimism, Avalanche, Linea and others), Bitcoin (BIP-137 and BIP-322 simple), Solana and Tron.

  Failure modes (errors carry a code to translate):

  * 400 - code CHAIN\_UNSUPPORTED when the method is not available on the chain; also an invalid body or a wallet that does not match the transfer.
  * 403 - SELF\_DECLARATION not allowed for the application.
  * 409 - code OWNERSHIP\_CHANGED: mint a new session for the current holder.
  * 409 - no attempts left (no code; the body carries attempts\_remaining 0): mint a new session.
  * 404 - unknown token. 410 - the session expired.

  Full guide: /transaction-monitoring/wallet-ownership-widget#what-it-proves.\`
</pre>

Paste into


## OpenAPI

````yaml POST /v3/travel-rule/widget/{token}/proofs/
openapi: 3.0.0
info:
  version: 3.0.0
  title: Didit Verification API
  description: Identity verification API. Authenticate with x-api-key header.
servers:
  - url: https://verification.didit.me
security: []
tags: []
paths:
  /v3/travel-rule/widget/{token}/proofs/:
    post:
      tags:
        - Travel Rule
      summary: Start a wallet-ownership proof
      description: >-
        Public endpoint keyed by the widget session token, no API key required:
        the hosted widget calls it when the end user picks a proof method. A
        proof exists, and counts in your proof funnel, only from this call.
        Picking the same method again returns the proof already open (200, same
        challenge or deposit amount) instead of starting a second one; picking
        another method closes the unfinished one as METHOD_SWITCHED, except a
        submitted screenshot or a test deposit still being checked.
        MESSAGE_SIGNING returns the challenge to sign; SATOSHI_TEST the deposit
        address, exact amount and window; SELF_DECLARATION is verified at once
        (status VERIFIED). Signature formats verified per chain family: EVM
        chains take a personal_sign (EIP-191) signature, hex encoded; Bitcoin
        takes a BIP-137 signmessage signature (P2PKH, P2SH-P2WPKH and P2WPKH
        addresses) or a BIP-322 simple signature (P2WPKH and P2TR addresses),
        base64 encoded; Solana takes an Ed25519 signature over the challenge,
        base58 encoded; Tron takes a signMessageV2 (TIP-191) signature, hex
        encoded. A BIP-322 full or proof-of-funds signature, or a multisig
        address, fails with SIGNATURE_UNSUPPORTED_SCHEME.
      operationId: createTravelRuleWidgetProof
      parameters:
        - name: token
          in: path
          required: true
          description: Opaque session token returned when the widget session was minted.
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/TravelRuleWidgetProofCreate'
      responses:
        '200':
          description: The proof already open for this method, resumed.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TravelRuleWidgetProofSummary'
        '201':
          description: A new PENDING proof (VERIFIED for SELF_DECLARATION).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TravelRuleWidgetProofSummary'
        '400':
          description: >-
            Invalid body, a wallet that does not match the transfer, SCREENSHOT
            not allowed, or code CHAIN_UNSUPPORTED when the method is not
            available on the chain.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TravelRuleWidgetProofError'
        '403':
          description: SELF_DECLARATION is not allowed for this application.
        '404':
          description: Unknown token.
        '409':
          description: >-
            code OWNERSHIP_CHANGED: the wallet's holder changed or the wallet
            was deleted after the session was minted; mint a new session for the
            current holder. Also returned for SATOSHI_TEST when the session has
            no satoshi_deposit_address, and when the session has no attempts
            left (no code; the body carries max_attempts, attempts_used and
            attempts_remaining 0): mint a new session.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/TravelRuleWidgetProofError'
        '410':
          description: The session expired.
      security:
        - {}
      x-codeSamples:
        - lang: curl
          label: curl
          source: >-
            curl -X POST
            https://verification.didit.me/v3/travel-rule/widget/{token}/proofs/
            \
              -H 'Content-Type: application/json' \
              -d '{"method": "MESSAGE_SIGNING"}'
components:
  schemas:
    TravelRuleWidgetProofCreate:
      type: object
      required:
        - method
      properties:
        method:
          type: string
          enum:
            - MESSAGE_SIGNING
            - SATOSHI_TEST
            - SCREENSHOT
            - SELF_DECLARATION
          description: >-
            The proof method the end user picked. It must be one of the
            context's allowed_methods.
        wallet_address:
          type: string
          maxLength: 128
          description: >-
            Defaults to the session's wallet_address. Must match the transfer's
            wallet when the session is linked to a transfer.
        chain:
          type: string
          maxLength: 32
          description: Defaults to the session's chain.
    TravelRuleWidgetProofSummary:
      type: object
      properties:
        proof_id:
          type: string
          format: uuid
        method:
          type: string
          enum:
            - MESSAGE_SIGNING
            - SATOSHI_TEST
            - SCREENSHOT
            - SELF_DECLARATION
        status:
          type: string
          enum:
            - PENDING
            - VERIFIED
            - REJECTED
            - EXPIRED
          description: >-
            PENDING: the end user picked this method and the proof is not
            decided yet. A failed signature or a test deposit that is not
            readable or confirmed yet keeps the proof PENDING with a
            failure_reason, so the end user can try again; a submitted
            SATOSHI_TEST deposit is rechecked automatically until its window
            closes, and a submitted SCREENSHOT waits for your review. VERIFIED:
            ownership is proven. REJECTED: the test deposit is genuinely wrong
            (another sender, not the expected amount to the deposit address,
            older than the challenge, or a transaction that already proved
            another wallet) or your reviewer rejected the screenshot;
            failure_reason says which. A rejected proof is final and uses one of
            the session's attempts. EXPIRED: the proof can no longer be decided
            (its window closed, the end user switched method, the widget session
            lapsed, or the wallet's holder changed); failure_reason says which.
        wallet_address:
          type: string
        chain:
          type: string
          description: >-
            Canonical chain identifier of the address book entry the proof is
            for, e.g. ETH, BTC, SOL, TRX.
        deposit_address:
          type: string
          nullable: true
          description: >-
            Deposit address the customer must send the Satoshi test amount to.
            Present only on a SATOSHI_TEST proof.
        expected_amount:
          type: string
          nullable: true
          description: >-
            Exact on-chain amount, in the deposit's native currency, the Satoshi
            test deposit must match. Present only on a SATOSHI_TEST proof.
        expires_at:
          type: string
          format: date-time
          nullable: true
          description: >-
            Deadline for this proof's own Satoshi test window. Present only on a
            SATOSHI_TEST proof; the widget session's own expires_at is extended
            to cover it when SATOSHI_TEST is offered.
        challenge:
          type: string
          nullable: true
          description: >-
            Didit-issued message the customer signs with their wallet. Present
            only on a MESSAGE_SIGNING proof.
        rejection_reason:
          type: string
          description: >-
            Why the proof was rejected, as an English sentence for the end user.
            Present only when `status` is REJECTED; empty when your reviewer
            rejected a screenshot (your note stays with you).
        created_at:
          type: string
          format: date-time
        attempts:
          type: integer
          description: >-
            How many times evidence was submitted for this proof (signatures,
            transaction ids, uploads).
        failure_reason:
          allOf:
            - $ref: '#/components/schemas/TravelRuleWalletProofFailureReason'
          description: >-
            Why the last attempt fell short, or why the proof closed. Empty when
            nothing failed.
    TravelRuleWidgetProofError:
      type: object
      properties:
        detail:
          type: string
          description: Human-readable message in English.
        code:
          allOf:
            - $ref: '#/components/schemas/TravelRuleWalletProofFailureReason'
          description: >-
            Machine-readable reason, so a client can show its own translated
            message.
        status:
          type: string
          enum:
            - PENDING
            - VERIFIED
            - REJECTED
            - EXPIRED
          description: >-
            Present when the proof is closed: REJECTED by the deposit just
            submitted (400), or REJECTED or EXPIRED when evidence is sent to a
            proof that is already closed (409).
        proof_id:
          type: string
          format: uuid
          description: 'Present with status: the closed proof.'
        method:
          type: string
          enum:
            - MESSAGE_SIGNING
            - SATOSHI_TEST
            - SCREENSHOT
            - SELF_DECLARATION
          description: 'Present with status: the closed proof''s method.'
        max_attempts:
          type: integer
          description: 'Present with status: rejected wallet proofs this session allows.'
        attempts_used:
          type: integer
          description: 'Present with status: rejected proofs so far.'
        attempts_remaining:
          type: integer
          description: >-
            Present with status: when 0, starting another proof answers 409 and
            you mint a new widget session.
    TravelRuleWalletProofFailureReason:
      type: string
      enum:
        - ''
        - SIGNATURE_INVALID
        - SIGNATURE_UNSUPPORTED_SCHEME
        - DEPOSIT_NOT_FOUND
        - DEPOSIT_UNCONFIRMED
        - DEPOSIT_LOOKUP_FAILED
        - DEPOSIT_MISMATCH
        - DEPOSIT_STALE
        - TXID_REUSED
        - CHAIN_UNSUPPORTED
        - WINDOW_EXPIRED
        - OWNERSHIP_CHANGED
        - METHOD_SWITCHED
        - REVIEW_REJECTED
        - ABANDONED
      description: >-
        Why a wallet-ownership proof, or its last attempt, fell short of
        VERIFIED. Empty when nothing failed. A PENDING proof can carry one: the
        last attempt failed and the end user can try again. DEPOSIT_MISMATCH,
        DEPOSIT_STALE, TXID_REUSED and REVIEW_REJECTED close the proof as
        REJECTED. SIGNATURE_INVALID: the signature does not match the wallet and
        challenge. SIGNATURE_UNSUPPORTED_SCHEME: the wallet signed in a format
        Didit does not verify. DEPOSIT_NOT_FOUND, DEPOSIT_UNCONFIRMED,
        DEPOSIT_LOOKUP_FAILED: the test deposit is not readable or not confirmed
        yet; Didit keeps checking it until the test window closes.
        DEPOSIT_MISMATCH: the transaction does not send the expected amount from
        the wallet to the deposit address. DEPOSIT_STALE: the transaction
        predates the challenge. TXID_REUSED: the transaction already proved
        another wallet. CHAIN_UNSUPPORTED: the method is not available on this
        chain. WINDOW_EXPIRED: the test window closed. OWNERSHIP_CHANGED: the
        wallet's holder changed, or the wallet was deleted from your address
        book, after the widget session was minted. METHOD_SWITCHED: the end user
        picked another method before finishing this one. REVIEW_REJECTED: your
        reviewer rejected the screenshot. ABANDONED: the widget session lapsed
        before the end user finished.

````

This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.