Travel Rule (TFR) for crypto addresses

Comply with EU TFR requirements

The EU Transfer of Funds Regulation (TFR) requires VASPs to exchange originator and beneficiary information for crypto transfers. Striga is the VASP of record for transfers on your accounts and coordinates the required TFR messaging with the counterparty VASP.

For each external address an end user transacts with, we are legally obliged to collect information on the address, who owns it, what chain it lives on, and (above the EUR threshold) whether the user controls it. You have two ways to provide this information.

Integration paths

Counterparty API. Pre-declare every external address from your backend before the user transacts with it. Server-to-server, HMAC-authenticated. Use this whenever you can. You must prompt the end user to provide declaration details in your application UI. Submit only values the user has entered or confirmed. Do not populate yourself. Reown SDK is recommended for signature verification. Counterparties are identified by an on-chain counterpartyAddress or, for Lightning, by paymentHash — or a bolt11 invoice, which Striga decodes to its payment hash (see Lightning). Addresses are matched case-insensitively — declare in any casing.

Widget. Create a session, open the Striga-hosted widget in the user's browser, and let it collect the declaration. The widget renders inside an iframe, popup, or mobile WebView, so its behaviour can vary with the embedding environment. Use this only when you cannot host the declaration UX yourself.

The rest of this guide focuses on the API path. Widget specifics are at the end.

Decision flow

Deposit

Withrawal



Once the destination is declared ,Striga matches the originating address against the user's declared counterparties.

Withdrawal outcomes

CounterpartyTransaction amountSignature / counterparty user infoCodeResult
VASPAnyCounterparty user info on fileProceeds. TFR sent to receiving VASP.
VASPAnyNo counterparty user info42008Create counterparty user info; retry.
OWNEDUnder 1000 EURn/aProceeds.
OWNED≥ 1000 EURWallet ownership verified: YesProceeds.
OWNED≥ 1000 EURWallet ownership verified: No42002Verify wallet ownership and retry.
THIRD_PARTYUnder 1000 EURn/aProceeds.
THIRD_PARTY≥ 1000 EURn/a42004Blocked.

Deposit outcomes

Counterparty for sourceTransaction amountSignature / counterparty user infoResult
VASPAnyCounterparty user info required per transactionHeld. VASP_COUNTERPARTY_USER_INFO_REQUIRED webhook. Create counterparty user info and link it to the transaction.
OWNEDUnder 1000 EURn/aCredited.
OWNED≥ 1000 EURWallet ownership verified: YesCredited.
OWNED≥ 1000 EURWallet ownership verified: NoHeld (WALLET_VERIFICATION_REQUIRED). Verify wallet ownership via POST /counterparty/self-hosted/verify-wallet or the widget; credit releases automatically.
THIRD_PARTYUnder 1000 EURn/aCredited.
THIRD_PARTY≥ 1000 EURn/aHeld / blocked (BLOCKED_DUE_TO_TRAVEL_RULE).

When the TFR message reaches us before the on-chain confirmation, the deposit credits as soon as the chain confirms.

Lightning: for Lightning counterparties the source/destination is identified by paymentHash rather than an on-chain address — the outcome rules above still apply. See Lightning.

Special case: VASP counterparties

VASPs typically issue one deposit address per user , but on the chain withdrawals that address can be reused across many sends. Because of that asymmetry, Striga handles VASP counterparties differently from self-hosted OWNED / THIRD_PARTY:

Withdrawals to a VASP

  • Declare the address once with the VASP's counterpartyVaspId on POST /counterparty/vasp.
  • Create the per-transaction VASP counterparty user info record separately via POST /counterparty/vasp/counterparty-user-info; it identifies the beneficiary for withdrawals to that address.
  • If the VASP counterparty has no counterparty user info record when you try to withdraw, the call returns 42008 Travel rule VASP counterparty user info required. Create a record and retry.

Deposits from a VASP

  • Even with the sending address already declared as a VASP counterparty, every incoming deposit is held. VASPs reuse the same on-chain deposit address for many of their users, so the originator PII must be identified per transaction — the counterparty alone is not enough.
  • You receive a VASP_COUNTERPARTY_USER_INFO_REQUIRED webhook (or the held deposit is matched automatically if the originating VASP's TFR message arrives first).
  • Resolve by either:
    • POST /counterparty/vasp/counterparty-user-info to create the originator record, then POST /counterparty/vasp/counterparty-user-info/link with the held deposit's txHash (or paymentHash for Lightning), or
    • Start a widget session scoped to the held deposit by passing txHash on POST /widget/sessions.
  • The deposit then goes through compliance review before credit. If the originating VASP's TFR message arrives at any time, the hold releases automatically.

Cheat sheet

DirectionVASP counterparty user info neededWhen
WithdrawOnce per counterpartyCreate before first send.
DepositOnce per transactionCreate + link per held txHash (or widget).

Special case: Skipping Per-Address Declarations

For integrators who can attest that a user transacts exclusively with their own wallets, this feature eliminates per-payment counterparty declarations — Striga automatically treats each deposit or withdrawal as from OWNED wallets, with no address-level declaration required.

Flags

FlagRailDirection
onChainDepositsFromOwnWalletsOnlyOn-chaindeposits
lightningDepositsFromOwnWalletsOnlyLightningdeposits
onChainWithdrawalsToOwnWalletsOnlyOn-chainwithdrawals
lightningWithdrawalsToOwnWalletsOnlyLightningwithdrawals

Each flag is independent. Omitting a flag from a request leaves its current value unchanged.

Wallet-ownership verification (at or above the 1 000 EUR threshold) is still triggered as normal for OWNED counterparties — the flags only remove the obligation to declare an address before each transaction. The existing evaluation path and thresholds are unchanged.

Setting Travel Rule Options

POST /api/v1/travel-rule/travel-rule-options
Authorization: <HMAC>

{
  "userId": "<uuid>",
  "onChainDepositsFromOwnWalletsOnly": true,
  "lightningDepositsFromOwnWalletsOnly": true,
  "onChainWithdrawalsToOwnWalletsOnly": true,
  "lightningWithdrawalsToOwnWalletsOnly": true
}

Webhooks

See Travel Rule Webhooks for full payload examples, field descriptions, and Lightning variants for each event type.

Widget

If you choose to delegate the declaration UX to Striga, create a session from your backend and render the returned URL in the user's browser. Follow integration guide.

POST /api/v1/travel-rule/widget/sessions
Authorization: <HMAC>

{
  "userId": "<uuid>",
  "currency": "ETH",
  "network": "ETH",
  "counterpartyAddress": "0xabc..."
}

Response:

{
  "sessionId": "widget_session_<...>",
  "token": "<jwt>",
  "widgetUrl": "https://<widget-host>/<sessionId>?token=<token>",
  "expiresAt": "<ISO date>"
}

Open widgetUrl in an iframe, popup, or WebView. The widget walks the user through the declaration and any signature or liveness verification, then closes. Track completion through the webhooks above. Optionally scope the session to a specific held deposit by passing txHash.



For standing orders

Standing Orders to External Wallets

A counterparty declaration is required before you can create a standing order to an external address. Use the Counterparty API or widget to declare the destination.

At each execution, the withdrawal amount is checked against Travel Rule. If the amount is below 1,000 EUR equivalent, the withdrawal proceeds. If it meets or exceeds 1,000 EUR equivalent, the outcome depends on the counterparty type:

Destination typeBelow 1,000 EURAt or above 1,000 EUR
OWNED (verified)ProceedsProceeds
OWNED (unverified)ProceedsSkipped until ownership is verified
Third-partyProceedsAlways skipped
VASPProceeds if beneficiary user info is provided / Same as Withdrawal flowProceeds if beneficiary user info is provided / Same as Withdrawal flow

When an execution is skipped, the standing order stays active — it will continue on its next scheduled execution. A STANDING_ORDER_FAILED webhook is sent with failureType: BLOCKED_BY_TRAVEL_RULE so you can notify the user.

Swap Only

Swap-only standing orders (SWAP_ONLY) do not make an external payout, so no counterparty declaration is required for the outgoing side. Incoming crypto deposits still need to satisfy Travel Rule before they can be swapped.

Lightning

Lightning counterparties follow the same Travel Rule model as on-chain BTC, with one structural difference: a Lightning payment has no reusable on-chain destination address, so the counterparty is identified by the payment hash of the invoice rather than a wallet address.

Outcome rules follow counterparty type and transaction amount — the same model as the withdrawal and deposit tables for on-chain transfers. Lightning-specific rules are documented below.

Identifying a Lightning counterparty: paymentHash or invoice

Everywhere the counterparty API and webhooks accept or return counterpartyAddress for on-chain transfers, Lightning uses paymentHash instead:

  • **paymentHash** — the 64-character hex payment hash (the 32-byte Lightning payment hash, hex-encoded) of the invoice. Lowercased on storage; submit in any casing.
  • **invoice** — alternatively, submit the bolt11 Lightning invoice and Striga decodes it server-side to its payment hash. The counterparty is stored and echoed back by the resulting paymentHash.
  • Valid on Bitcoin only: declare with currency: BTC and network: BTC.
  • On every declaration and link endpoint, provide exactly one of counterpartyAddress (on-chain), paymentHash (Lightning), or invoice (Lightning bolt11). They are mutually exclusive — sending more than one, or none, fails with 42005.
  • Counterparty responses echo back only the field that applies: a Lightning counterparty has paymentHash set and counterpartyAddress omitted; an on-chain counterparty has counterpartyAddress set and paymentHash omitted.

Declaring a Lightning counterparty

Use the same declaration endpoints as on-chain — POST /counterparty/self-hosted (for OWNED / THIRD_PARTY) and POST /counterparty/vasp — passing paymentHash (or invoice) in place of counterpartyAddress:

POST /api/v1/travel-rule/counterparty/self-hosted
Authorization: <HMAC>

{
  "userId": "<uuid>",
  "invoice": "...",
  "currency": "BTC",
  "network": "BTC",
  "subType": "THIRD_PARTY",
  "entityType": "NATURAL",
  "firstName": "Jane",
  "lastName": "Doe",
  "country": "DE"
}

Response (201) — note paymentHash is returned and counterpartyAddress is absent:

{
  "counterpartyId": "9808388e-bd29-4110-b5d3-b183b59ca49e",
  "userId": "<uuid>",
  "identityType": "CONSUMER",
  "paymentHash": "8ac740817d5f7794f550764945c3ac35f830364ea11e8427e36cf0319d0e3f71",
  "blockchain": "Btc",
  "type": "SELF_HOSTED",
  "subType": "THIRD_PARTY",
  "disabled": false,
  "declaredAt": "2026-05-15T12:00:00.000Z",
  "createdAt": "2026-05-15T12:00:00.000Z",
  "updatedAt": "2026-05-15T12:00:00.000Z"
}

A VASP Lightning counterparty is declared the same way on POST /counterparty/vasp, passing paymentHash (or invoice) plus counterpartyVaspId.

No wallet-ownership proof for Lightning

Lightning counterparties cannot be wallet-signed — a paymentHash / invoice is not a signable address. Wallet verification (POST /counterparty/self-hosted/verify-wallet, Sumsub liveness) and 42002 do not apply. Self-hosted Lightning at or above 1000 EUR is blocked.

Linking VASP counterparty user info to a Lightning transaction

When per-transaction originator counterparty user info is required for a Lightning deposit from a VASP, link the counterparty user info record by paymentHash (or invoice) instead of txHash:

POST /api/v1/travel-rule/counterparty/vasp/counterparty-user-info/link
Authorization: <HMAC>

{
  "userId": "<uuid>",
  "vaspCounterpartyUserInfoId": "<uuid>",
  "paymentHash": "8ac740817d5f7794f550764945c3ac35f830364ea11e8427e36cf0319d0e3f71"
}

txHash, paymentHash, and invoice are mutually exclusive here too — provide exactly one.

Lightning in webhooks

Travel Rule webhooks for Lightning deposits carry **paymentHash** in place of the on-chain counterpartyAddress / txHash locators (txId and memo are unchanged). Match held Lightning deposits to your declaration by paymentHash.

Lightning outcomes

Lightning payins and payouts are evaluated by counterparty type and amount. The tables below are authoritative for Lightning.

Withdrawal (Lightning payout)

CounterpartyTransaction amountCounterparty user info / declarationCodeResult
UndeclaredAny42001Declare a counterparty (paymentHash or invoice), then retry.
VASPAnyCounterparty user info on fileProceeds. TFR sent to receiving VASP.
VASPAnyNo counterparty user info42008Create counterparty user info; retry.
OWNED / THIRD_PARTYUnder 1000 EURDeclaredProceeds.
OWNED / THIRD_PARTY≥ 1000 EURDeclared42004Blocked.

Deposit (Lightning payin)

CounterpartyTransaction amountResult
UndeclaredAnyHeld. COUNTERPARTY_DECLARATION_REQUIRED webhook.
VASPAnyHeld. VASP_COUNTERPARTY_USER_INFO_REQUIRED webhook. Create counterparty user info and link it to the transaction by paymentHash.
OWNED / THIRD_PARTYUnder 1000 EURCredited.
OWNED / THIRD_PARTY≥ 1000 EURHeld / blocked. BLOCKED_DUE_TO_TRAVEL_RULE.

Before initiating a Lightning withdrawal

Declare the destination (paymentHash or invoice) and provide VASP counterparty user info where required — for any amount — using the counterparty API or widget before the user initiates the withdrawal.

Currency and network

Both fields are required on the counterparty endpoints and must form a valid pair.

currencynetwork
BTCBTC
ETHETH
USDCETH
BNBBSC
POLPOLYGON
MATIC_POLYGONPOLYGON
USDC_POLYGONPOLYGON
SOLSOL
USDC_SOLSOL

Lightning counterparties use the BTC / BTC pair and supply paymentHash (or invoice) instead of counterpartyAddress.


Error codes

CodeMeaning
42001Declare a counterparty for this address, then retry.
42002OWNED address exceeded the EUR threshold. Verify wallet ownership via POST /counterparty/self-hosted/verify-wallet.
42003Travel Rule message rejected by the beneficiary VASP.
42004THIRD_PARTY address (or any Lightning transfer) exceeded the EUR threshold. Blocked.
42005Travel Rule evaluation failed, signature invalid, or declaration payload invalid (e.g. more than one of counterpartyAddress, paymentHash, invoice, or none provided). Check the response message.
42006Counterparty not found for the given identifier.
42007Travel Rule application configuration not found.
42008Travel rule VASP counterparty user info required for this transaction.