Skip to main content

How it works

Travel Rule outcomes in sandbox are driven by two layers:
  1. Wallet screening (synchronous, at create time): resolves a destination to one of four categories — VASP-attributed, self-hosted, elevated risk, or sanctions match. Magic-suffix encoded.
  2. Counterparty webhook (asynchronous): the VASP counterparty’s terminal decision — acknowledged, approved, rejected, declined. The sandbox automatically delivers the suffix-encoded outcome 10 seconds after the Travel Rule transfer is created, unless you call the simulate endpoint first to override it.

Wallet screening scenario catalog

Address format. EVM addresses must be all-lowercase OR a correctly EIP-55 checksummed mixed-case form. The mnemonic suffixes called out in this page are uppercase for readability; the wire-format addresses you send to the API are all-lowercase.

Counterparty-outcome scenario catalog (VASP-attributed only)

These suffixes route through the Travel Rule flow. Unless you call the simulate endpoint first, the sandbox automatically delivers the suffix-encoded counterparty outcome 10 seconds after the Travel Rule transfer is created. Calling payouts/:id/simulate/counterparty-webhook before that 10-second window elapses overrides the automatic outcome. A call that arrives after the automatic outcome has already been recorded does not change it. The Travel Rule transfer is created after compliance and document review clear, just before the customer is asked to sign, so it exists throughout the signing window up to broadcast. A rejected / declined outcome received before signing begins terminates the payout immediately, before broadcast. One received while the payout is still collecting signatures is held; the sandbox checks it again once the signatures reach quorum, and if the rejection is still on record it terminates the payout there, before broadcast. Once the payout has moved past that point toward broadcast, a rejection has no effect.

Pre- vs post-broadcast

Pre-broadcast: counterparty REJECTED / DECLINED cancels and terminalizes the transaction. Post-broadcast: audit-only; the payout completes. A confirmed chain transfer cannot be unwound (FATF Rec. 16).

How to guarantee a pre-broadcast terminal rejection

A counterparty rejection arrives pre-broadcast as long as the payout has not yet broadcast on-chain. Travel Rule runs before signing, so the Travel Rule transfer exists throughout the signing window and right up to broadcast — a reject stays pre-broadcast until the payout broadcasts, after which it becomes audit-only. Two reliable approaches:
  • Manual counterparty call: call payouts/:id/simulate/counterparty-webhook { outcome: "rejected", reason: "..." } once the Travel Rule transfer exists — before the customer signs, or once the signatures reach quorum. A call made while the payout is still collecting signatures is held; the sandbox checks it again when quorum is reached. In both cases the transaction terminates with failureCode: travel_rule_rejected, as long as it has not already broadcast.
  • Unconditional pre-broadcast suffix: use destination suffix BAD7E517. This is unconditionally pre-broadcast; no manual call is needed.
For both flows, the polled GET /v2/transactions/:id and the transaction.failed webhook carry the same failureMessage: the standard text for travel_rule_rejected, as in production. The optional reason does not appear in it. See the failureMessage symmetry contract.

Override the auto-pilot

To inject a counterparty webhook synchronously (before the 10s timer), call:
The synchronous call overrides the pending automatic outcome and records the counterparty outcome on the payout’s Travel Rule transfer. For rejected / declined: see the pre- vs post-broadcast carve-out above — post-broadcast, the call is audit-only and the payout completes. For acknowledged / approved: always informational; the payout proceeds independently of counterparty resolution. A later call that would regress an already-recorded outcome is ignored.
reason stays internal. The reason you supply is recorded for Conduit operators. The polled GET /v2/transactions/:id and the transaction.failed webhook carry the standard failureMessage for travel_rule_rejected, as in production. Correlate the simulator call with the resulting webhook by transactionId.

Example: VASP destination + REJECTED counterparty

amount is a canonical decimal string with exactly asset.precision digits after . (USDC has 6 decimals → "10.000000" = 10 USDC). Lifecycle:
  1. Wallet screening resolves VASP-attributed; the synthetic Travel Rule transfer row is written and the auto-pilot job is queued — all before the customer is asked to sign.
  2. The payout reports requiresUserSignature: true and waits while the customer’s authorized signers approve it. The Travel Rule row already exists, so a counterparty rejection can land during this window.
  3. The synthetic counterparty webhook fires with outcome: rejected. Broadcast has not yet happened; the payout terminates with transaction.failed carrying failureCode: travel_rule_rejected. The funds reserved for the payout are returned; Conduit also notifies the counterparty VASP of the cancellation, on a best-effort basis.

Errors

See also