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_msis set andsuspension_reasonis populated - New trades, zaps, and cancels referencing that
destination_idare rejected with403 destination_suspended - In-flight settlements targeting the destination proceed — they were committed before the flag
- A
destination.suspendedwebhook fires with thedestination_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:
- Chain confirmation — the indexer tracks on-chain confirmations until the chain-specific finality threshold is met
- Transaction monitoring — the transaction hash is submitted to the screening provider asynchronously; the screening result arrives on a separate event stream
- Partner screening (opt-in) — if enabled, Routes fires a
deposit.partner_screeningwebhook 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_msand expiry is reached before execution begins, the zap fails withfailure_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¶
- A deposit is detected and
deposit.pendingfires - Routes delivers a
deposit.partner_screeningevent to the partner's webhook with the transaction hash, source address, and deposit details - The partner has 10 seconds (from
created_at_ms) to evaluate the transaction and, if needed, callPOST /v1/deposits/{deposit_address_id}/flagto flag it - If the partner does not respond within the window, Routes treats this as no objection and the deposit proceeds through normal verification
- If the partner flags the deposit, it is treated as
deposit.flaggedwithflag_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_failedand 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:
- Notify the end user that their deposit was flagged and no credit was created
- Direct the user to initiate key export via
POST /public/initiate-export— the user completes this flow independently using their email for OTP verification - 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. |