← All posts Engineering

Wallet screening API: how to screen a crypto address before you pay

27 July 20268 min readBy FIN-TECHAI Research
EngineeringSentinel

Integrating a wallet screening API is a two-day job. Integrating it so that it actually blocks a bad payment, survives a provider outage, and produces a record you can defend two years later takes rather longer. This is the part the docs skip.

Key takeaways
  • Three call sites, not one: onboarding, pre-transfer, and scheduled re-screening.
  • The pre-transfer call must be synchronous and blocking. Anything else is monitoring, not screening.
  • Fail closed on sanctions, fail open on enrichment — and alert loudly on the degraded path.
  • Persist the whole response, not the decision. The response is your evidence.

Where the wallet screening call belongs in a payment flow

Most teams wire screening into one place and assume they are covered. There are three distinct call sites, and they have different latency and consistency requirements.

Call siteModeBudgetPurpose
Onboarding — customer supplies an addressSynchronous2–5 s acceptableEstablish an initial risk band; a slow response here is invisible to the user
Pre-transfer — before signingSynchronous, blocking200–500 msPrevent the payment. This is the control that matters
Ongoing — scheduled and event-drivenAsynchronousMinutesCatch risk that appeared after onboarding

The pre-transfer call is the one people get wrong, usually by making it asynchronous to protect their p99. A screening call that returns after the transaction is broadcast has not screened anything — it has produced an incident report. If the latency budget is the constraint, solve it with a warmed cache of enrichment data and a fast sanctions path, not by moving the call off the critical path.

What a useful screening API response contains

A single risk number is not enough to act on, and not enough to defend. A response you can build policy on looks closer to this:

{
  "address": "0x…",
  "chain": "ethereum",
  "screened_at": "2026-07-27T09:14:22Z",
  "sanctions": {
    "match": false,
    "lists_checked": ["ofac_sdn","eu_csl","uk_hmt","un_consolidated"],
    "list_versions": {"ofac_sdn":"2026-07-26","eu_csl":"2026-07-25"}
  },
  "score": 34,
  "band": "medium",
  "confidence": 0.82,
  "exposure": [
    {"category":"mixer","hops":2,"share":0.11,"source":"cluster_attribution","confidence":0.74},
    {"category":"exchange","entity":"…","hops":1,"share":0.61,"confidence":0.95}
  ],
  "policy": {"decision":"review","rule":"mixer_indirect_gt_10pct"},
  "trace_id": "scr_01J…"
}

Four things in there matter more than the score. List versions, so you can prove what was live when you screened. Hop distance on each exposure, because direct and three-hop exposure are different risks and collapsing them into one number destroys that. Confidence per finding, so policy can treat a 0.95 attribution differently from a 0.4 one. And a trace ID that ties the response to your own audit record.

We go into why the score itself is the least interesting field in what a wallet risk score actually measures.

The fail-open decision

Your provider will have an outage. The design question is what your payment flow does during it, and the answer is not uniform across the response.

Set an explicit degraded-mode budget too. If you have been fail-open on enrichment for more than an hour, that should page someone rather than accumulate quietly.

Caching rules

Caching is where correctness quietly erodes. The rule is to cache by volatility, not by convenience.

FieldTTLWhy
Service attribution (this cluster is exchange X)6–24 hStable; expensive to recompute
Score and band5–15 minMoves with new flow into the address
Sanctions determination0–60 sList updates invalidate it instantly
Full trace / hop graphDo not cache for decisionsRecompute; stale traces mislead investigators

Cache keys must include the chain. The same hex string is a valid address on many EVM networks and its risk profile differs on each — a cache keyed on the address alone will happily serve an Ethereum verdict for a Polygon transfer.

Idempotency and retries

Screening calls are read-only, which tempts teams into aggressive retries. Two constraints matter. First, send an idempotency key per screening event so a retry does not produce two audit records that later look like two separate decisions. Second, cap retries hard on the blocking path — three attempts with jittered backoff inside your 500 ms budget, then fall to the degraded policy. A retry storm against a struggling provider extends the outage for everyone, including you.

What to log, and for how long

Persist the entire response body, not your interpretation of it. When an examiner asks why a payment cleared in July 2026, "score was 34, we allow under 40" is a policy statement; the stored response with its list versions and confidence values is evidence. Retain in line with your AML record-keeping period — five years after the relationship ends in most jurisdictions, and matching what our privacy policy commits to.

Log the response, not the decision. The decision is reconstructable from the response; the reverse is not true.

A minimal integration checklist

Frequently asked questions

Where should a wallet screening API call sit in a payment flow?

Three places: at onboarding when a customer supplies an address, synchronously before every outbound transfer is signed, and asynchronously on a re-screening schedule. The pre-transfer call is the one that prevents loss.

Should screening fail open or fail closed?

Fail closed for sanctions determinations and fail open for enrichment, with the degraded state logged and alerted. Failing open on sanctions turns an availability incident into a compliance breach.

Can screening results be cached?

Cache enrichment such as service attribution for hours. Never cache a sanctions determination beyond a minute, because list updates invalidate it and you must be able to prove which version applied.

What latency should I budget for the blocking call?

200–500 ms end to end. If a provider cannot hold that at your p99, the fix is a warmed enrichment cache and a fast sanctions path, not moving the call off the critical path.