Skip to content

Transaction monitoring

Routes screens on-chain activity at two points: ingress (deposits into the system) and egress (withdrawals to external addresses). Screening prevents flagged funds from entering the credit ledger or flowing through trades.

Egress screening

Egress addresses are screened when they are registered as destinations.

Screening at registration

When a destination is created via POST /accounts/{account_id}/destinations, Routes runs the address against sanctions lists and blockchain analytics providers before accepting it. If the address is flagged, the registration is rejected and no destination_id is created.

Every destination_id referenced in a trade, zap, or cancel has passed screening at the time it was registered.

Delta screening

Registered destinations are re-screened on a recurring schedule to catch addresses flagged after initial registration. If a destination is flagged during a delta screen:

  • The destination's suspended_at_ms is set and suspension_reason is populated
  • New trades, zaps, and cancels referencing that destination_id are rejected with 403 destination_suspended
  • In-flight settlements targeting the destination proceed — they were committed before the flag
  • A destination.suspended webhook fires with the destination_id, suspended_at_ms, and reason

If a destination later clears screening, Routes emits destination.reactivated and clears suspended_at_ms and suspension_reason.

The integrator should treat destinations with non-null suspended_at_ms as unavailable and route future requests to an active destination.

Regulated destinations

When the destination is a deposit address at a regulated institution (e.g. a centralized exchange), that institution runs equivalent screening on its own ingress. Integrators operating as regulated entities may request that egress screening be marked as screened_by: integrator for these destinations, reducing duplicate screening cost. Contact your Routes account manager to configure this.

Ingress screening

Routes screens the source address of every inbound deposit. Screening and chain confirmation run in parallel — a credit is only created when both have resolved.

Deposit verification flow

When the indexer detects an inbound transaction, it publishes a deposit.pending event. Up to three processes then run concurrently:

  1. Chain confirmation — the indexer tracks on-chain confirmations until the chain-specific finality threshold is met
  2. Transaction monitoring — the transaction hash is submitted to the screening provider asynchronously; the screening result arrives on a separate event stream
  3. Partner screening (opt-in) — if enabled, Routes fires a deposit.partner_screening webhook so the integrator can run their own compliance checks (see Partner screening)

A credit is created only when all active checks have resolved without a flag: chain finality reached, Routes screening passed, and (if enabled) the partner screening window closed without a flag. The deposit.verified event is the output of this join.

POST /deposit-addresses  → deposit_address_id + address
     │
     │  (end user sends funds on-chain)
     │
deposit.pending          → transaction detected
     │
     ├─ chain confirmation                (indexer)
     ├─ source address screening          (Routes)
     ├─ partner screening (if enabled)    (integrator, 10 s window)
     │
     │  (all must resolve without a flag)
     │
     ├─ all passed       → deposit.verified → credit.created
     │
     └─ any flagged      → deposit.flagged  → no credit created

If all checks pass, deposit.verified fires and a credit is created. Only verified deposits produce credits.

If any check flags the deposit — whether Routes screening or partner screening — deposit.flagged fires. No credit is created. The funds remain on-chain at the deposit address but do not enter the Routes system.

Effect on zaps

Zaps created without a credit_id enter awaiting_deposit and return a deposit address. When funds arrive at that address, the deposit goes through the same parallel verification flow described above.

  • If the deposit is verified and the deposited asset matches the zap's requested source asset, a credit is created and the zap transitions to executing
  • If the deposit is verified but the deposited asset does not match the zap's requested source asset, the zap fails with failure_code: from_asset_mismatch. The deposit is still credited normally for the deposited asset.
  • If the deposited amount differs from the requested amount, Routes executes on the observed deposited amount. The zap may fail with existing execution/fill failure codes if the observed size is not executable.
  • If the deposit is flagged, no credit is created and the zap fails with failure_code: deposit_flagged
  • If the zap is configured with expires_at_ms and expiry is reached before execution begins, the zap fails with failure_code: zap_expired

Zaps created with a credit_id are unaffected — the credit's source deposit was already verified.

When recovery is enabled and a failed zap leaves an open credit, the failed zap payload includes recovery_id + credit_id.

Partner screening

Partners can opt into running their own transaction screening on inbound deposits alongside Routes' built-in screening. When enabled, Routes fires a deposit.partner_screening webhook when a deposit is detected, giving the partner a time-limited window to flag the transaction using their preferred compliance tool suite.

Partner screening runs in parallel with Routes screening and chain confirmation. It does not add latency to the deposit flow under normal conditions — on most chains, chain confirmation takes longer than the partner screening window.

How it works

  1. A deposit is detected and deposit.pending fires
  2. Routes delivers a deposit.partner_screening event to the partner's webhook with the transaction hash, source address, and deposit details
  3. The partner has 10 seconds (from created_at_ms) to evaluate the transaction and, if needed, call POST /v1/deposits/{deposit_address_id}/flag to flag it
  4. If the partner does not respond within the window, Routes treats this as no objection and the deposit proceeds through normal verification
  5. If the partner flags the deposit, it is treated as deposit.flagged with flag_reason: partner_flagged

The screening window is 10 seconds by default. Contact your Routes account manager to configure a different window per partner (bounded range).

deposit.partner_screening

Fires when a deposit is detected on an account whose integrator has opted into partner screening. This is the partner's signal to run their own compliance checks.

{
  "event_id": "evt_01J...",
  "type": "deposit.partner_screening",
  "created_at_ms": 1730000006500,
  "account_id": "acct_01J...",
  "data": {
    "deposit_address_id": "depaddr_01J...",
    "tx_hash": "abc123...",
    "source_address": "bc1q...",
    "asset_key": "native.btc",
    "detected_atoms": "100000",
    "screening_deadline_ms": 1730000016500
  }
}
Field Type Description
deposit_address_id string The deposit address that received funds
tx_hash string On-chain transaction hash
source_address string Address that sent the funds
asset_key string Asset deposited
detected_atoms string Amount detected on-chain
screening_deadline_ms integer Unix ms deadline — flag must arrive before this time

POST /v1/deposits/{deposit_address_id}/flag

Call this endpoint within the screening window to flag a deposit. The deposit will be treated as deposit.flagged and no credit will be created.

Request body:

{
  "tx_hash": "abc123...",
  "flag_category": "ofac_match",
  "flag_message": "Matched internal blocklist entry"
}
Field Type Required Description
tx_hash string Yes Must match the tx_hash from the deposit.partner_screening event
flag_category string Yes Partner's reason category (free-form, e.g. ofac_match, internal_blocklist, high_risk)
flag_message string Yes Human-readable explanation for the flag

Responses:

Status Description
200 OK Flag accepted. The deposit will produce a deposit.flagged event with flag_reason: partner_flagged.
404 Not Found No pending deposit with that deposit_address_id and tx_hash combination.
409 Conflict Deposit has already been verified or flagged — the screening window has closed.
422 Unprocessable Entity Missing or invalid tx_hash, flag_category, or flag_message.

When a partner flags a deposit, the resulting deposit.flagged event includes additional fields:

{
  "event_id": "evt_01J...",
  "type": "deposit.flagged",
  "created_at_ms": 1730000008000,
  "account_id": "acct_01J...",
  "data": {
    "deposit_address_id": "depaddr_01J...",
    "asset_key": "native.btc",
    "detected_atoms": "100000",
    "source_address": "bc1q...",
    "flag_reason": "partner_flagged",
    "flagged_by": "partner",
    "tx_hash": "abc123...",
    "partner_flag_category": "ofac_match",
    "partner_flag_message": "Matched internal blocklist entry"
  }
}
Field Type Description
flagged_by string routes or partner — indicates who initiated the flag. Present on all deposit.flagged events.
partner_flag_category string The partner's reason category. Present only when flagged_by: partner.
partner_flag_message string The partner's human-readable reason. Present only when flagged_by: partner.

The same flagged deposit recovery path applies to partner-flagged deposits — key export is the only supported recovery mechanism.

Opting in

Partner screening is configured per integrator. Contact your Routes account manager to enable it. Once enabled, deposit.partner_screening events are delivered through your existing webhook subscription — no additional endpoint configuration is required.

Partners that do not opt in are unaffected — the deposit flow works exactly as before with Routes-only screening.

Public recovery destination screening

When a user claims funds via POST /public/recover-funds, the submitted destination is screened before payout using the same sanctions and risk controls applied to egress flows.

  • If screening passes, Routes creates a cancel-style transfer for the full open credit amount.
  • If screening fails, recovery claim is rejected with destination_screening_failed and the credit remains open.

deposit.verified

Fires when a deposit's source address passes screening and the deposit reaches the chain-specific finality threshold. A corresponding credit.created event follows.

{
  "event_id": "evt_01J...",
  "type": "deposit.verified",
  "created_at_ms": 1730000006500,
  "account_id": "acct_01J...",
  "data": {
    "deposit_address_id": "depaddr_01J...",
    "credit_id": "cred_01J...",
    "asset_key": "native.btc",
    "credited_atoms": "100000",
    "source_address": "bc1q..."
  }
}

deposit.flagged

Fires when a deposit fails screening — either Routes screening (source address flagged) or partner screening (integrator called the flag endpoint). The flagged_by field distinguishes the source.

{
  "event_id": "evt_01J...",
  "type": "deposit.flagged",
  "created_at_ms": 1730000006500,
  "account_id": "acct_01J...",
  "data": {
    "deposit_address_id": "depaddr_01J...",
    "asset_key": "native.btc",
    "detected_atoms": "100000",
    "source_address": "bc1q...",
    "flag_reason": "sanctions_match",
    "tx_hash": "abc123..."
  }
}
Field Type Description
deposit_address_id string The deposit address that received funds
asset_key string Asset deposited
detected_atoms string Amount detected on-chain
source_address string Address that sent the funds
flag_reason string Screening result: sanctions_match, high_risk_score, mixer_association, partner_flagged
flagged_by string Who initiated the flag: routes or partner
tx_hash string On-chain transaction hash
partner_flag_category string Partner's reason category. Present only when flagged_by: partner.
partner_flag_message string Partner's human-readable reason. Present only when flagged_by: partner.

Flagged deposit recovery

When a deposit is flagged, the funds exist on-chain at the account's deposit address but no credit is created. The account cannot spend, trade, or cancel those funds through Routes.

Routes does not permit cancels (withdrawals) against flagged deposits. Flagged funds do not enter the credit ledger and cannot be routed to a different address through the system.

The only supported recovery path is key export — the end user exports their deposit address private keys and manages the flagged funds independently, outside of Routes.

Integrator responsibilities

When you receive a deposit.flagged event:

  1. Notify the end user that their deposit was flagged and no credit was created
  2. Direct the user to initiate key export via POST /public/initiate-export — the user completes this flow independently using their email for OTP verification
  3. Once exported, the account is permanently locked. Require new account provisioning for future Routes usage.

The integrator does not need to handle key material. The user generates their own key pair and receives their encrypted export bundle directly. See Key export for the full user-facing flow.

Key export is irreversible

Once export is initiated, the account is permanently locked. The end user receives their keys but the account can no longer trade on Routes. A new account is required to continue using Routes.

Responsibility model

Party Responsibility
Routes Screen deposit sources at confirmation. Screen destinations at registration. Delta screening of registered destinations. Only credit verified deposits.
Integrator KYC/identity verification of end users. Communicating screening outcomes to users. Initiating key export when users need to recover flagged deposits. May opt into screened_by: integrator for regulated destinations. May opt into partner screening to run independent deposit screening alongside Routes.
Solver / MM No screening obligation imposed by Routes. Solvers fill credits that have been screened at ingress, and settlement destinations have been screened at registration. Solvers and MMs are free to perform their own additional screening — they should treat Routes screening as an input, and apply whatever additional diligence is relevant for their jurisdiction and control obligations.