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
| Counterparty | Transaction amount | Signature / counterparty user info | Code | Result |
|---|---|---|---|---|
| VASP | Any | Counterparty user info on file | — | Proceeds. TFR sent to receiving VASP. |
| VASP | Any | No counterparty user info | 42008 | Create counterparty user info; retry. |
| OWNED | Under 1000 EUR | n/a | — | Proceeds. |
| OWNED | ≥ 1000 EUR | Wallet ownership verified: Yes | — | Proceeds. |
| OWNED | ≥ 1000 EUR | Wallet ownership verified: No | 42002 | Verify wallet ownership and retry. |
| THIRD_PARTY | Under 1000 EUR | n/a | — | Proceeds. |
| THIRD_PARTY | ≥ 1000 EUR | n/a | 42004 | Blocked. |
Deposit outcomes
| Counterparty for source | Transaction amount | Signature / counterparty user info | Result |
|---|---|---|---|
| VASP | Any | Counterparty user info required per transaction | Held. VASP_COUNTERPARTY_USER_INFO_REQUIRED webhook. Create counterparty user info and link it to the transaction. |
| OWNED | Under 1000 EUR | n/a | Credited. |
| OWNED | ≥ 1000 EUR | Wallet ownership verified: Yes | Credited. |
| OWNED | ≥ 1000 EUR | Wallet ownership verified: No | Held (WALLET_VERIFICATION_REQUIRED). Verify wallet ownership via POST /counterparty/self-hosted/verify-wallet or the widget; credit releases automatically. |
| THIRD_PARTY | Under 1000 EUR | n/a | Credited. |
| THIRD_PARTY | ≥ 1000 EUR | n/a | Held / 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
paymentHashrather 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
counterpartyVaspIdonPOST /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
42008Travel 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_REQUIREDwebhook (or the held deposit is matched automatically if the originating VASP's TFR message arrives first). - Resolve by either:
POST /counterparty/vasp/counterparty-user-infoto create the originator record, thenPOST /counterparty/vasp/counterparty-user-info/linkwith the held deposit'stxHash(orpaymentHashfor Lightning), or- Start a widget session scoped to the held deposit by passing
txHashonPOST /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
| Direction | VASP counterparty user info needed | When |
|---|---|---|
| Withdraw | Once per counterparty | Create before first send. |
| Deposit | Once per transaction | Create + 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
| Flag | Rail | Direction |
|---|---|---|
onChainDepositsFromOwnWalletsOnly | On-chain | deposits |
lightningDepositsFromOwnWalletsOnly | Lightning | deposits |
onChainWithdrawalsToOwnWalletsOnly | On-chain | withdrawals |
lightningWithdrawalsToOwnWalletsOnly | Lightning | withdrawals |
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 type | Below 1,000 EUR | At or above 1,000 EUR |
|---|---|---|
| OWNED (verified) | Proceeds | Proceeds |
| OWNED (unverified) | Proceeds | Skipped until ownership is verified |
| Third-party | Proceeds | Always skipped |
| VASP | Proceeds if beneficiary user info is provided / Same as Withdrawal flow | Proceeds 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
paymentHash or invoiceEverywhere 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 Lightninginvoiceand Striga decodes it server-side to its payment hash. The counterparty is stored and echoed back by the resultingpaymentHash.- Valid on Bitcoin only: declare with
currency: BTCandnetwork: BTC. - On every declaration and link endpoint, provide exactly one of
counterpartyAddress(on-chain),paymentHash(Lightning), orinvoice(Lightning bolt11). They are mutually exclusive — sending more than one, or none, fails with42005. - Counterparty responses echo back only the field that applies: a Lightning counterparty has
paymentHashset andcounterpartyAddressomitted; an on-chain counterparty hascounterpartyAddressset andpaymentHashomitted.
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)
| Counterparty | Transaction amount | Counterparty user info / declaration | Code | Result |
|---|---|---|---|---|
| Undeclared | Any | — | 42001 | Declare a counterparty (paymentHash or invoice), then retry. |
| VASP | Any | Counterparty user info on file | — | Proceeds. TFR sent to receiving VASP. |
| VASP | Any | No counterparty user info | 42008 | Create counterparty user info; retry. |
| OWNED / THIRD_PARTY | Under 1000 EUR | Declared | — | Proceeds. |
| OWNED / THIRD_PARTY | ≥ 1000 EUR | Declared | 42004 | Blocked. |
Deposit (Lightning payin)
| Counterparty | Transaction amount | Result |
|---|---|---|
| Undeclared | Any | Held. COUNTERPARTY_DECLARATION_REQUIRED webhook. |
| VASP | Any | Held. VASP_COUNTERPARTY_USER_INFO_REQUIRED webhook. Create counterparty user info and link it to the transaction by paymentHash. |
| OWNED / THIRD_PARTY | Under 1000 EUR | Credited. |
| OWNED / THIRD_PARTY | ≥ 1000 EUR | Held / 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.
currency | network |
|---|---|
BTC | BTC |
ETH | ETH |
USDC | ETH |
BNB | BSC |
POL | POLYGON |
MATIC_POLYGON | POLYGON |
USDC_POLYGON | POLYGON |
SOL | SOL |
USDC_SOL | SOL |
Lightning counterparties use the BTC / BTC pair and supply paymentHash (or invoice) instead of counterpartyAddress.
Error codes
| Code | Meaning |
|---|---|
42001 | Declare a counterparty for this address, then retry. |
42002 | OWNED address exceeded the EUR threshold. Verify wallet ownership via POST /counterparty/self-hosted/verify-wallet. |
42003 | Travel Rule message rejected by the beneficiary VASP. |
42004 | THIRD_PARTY address (or any Lightning transfer) exceeded the EUR threshold. Blocked. |
42005 | Travel 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. |
42006 | Counterparty not found for the given identifier. |
42007 | Travel Rule application configuration not found. |
42008 | Travel rule VASP counterparty user info required for this transaction. |

