Wallet screening API: how to screen a crypto address before you pay
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.
- 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 site | Mode | Budget | Purpose |
|---|---|---|---|
| Onboarding — customer supplies an address | Synchronous | 2–5 s acceptable | Establish an initial risk band; a slow response here is invisible to the user |
| Pre-transfer — before signing | Synchronous, blocking | 200–500 ms | Prevent the payment. This is the control that matters |
| Ongoing — scheduled and event-driven | Asynchronous | Minutes | Catch 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.
- Sanctions determination — fail closed. Sanctions breach is strict liability. "Our vendor was down" is not a defence. Queue the transfer, alert an operator, and hold.
- Enrichment and scoring — fail open, degraded. Proceed on your last known band, mark the transaction as screened-degraded, and re-screen when the provider returns.
- Always alert. A silent fail-open path is how a firm discovers, during an examination, that screening was effectively off for eleven days.
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.
| Field | TTL | Why |
|---|---|---|
| Service attribution (this cluster is exchange X) | 6–24 h | Stable; expensive to recompute |
| Score and band | 5–15 min | Moves with new flow into the address |
| Sanctions determination | 0–60 s | List updates invalidate it instantly |
| Full trace / hop graph | Do not cache for decisions | Recompute; 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
- Pre-transfer call is synchronous and blocks signing.
- Sanctions fails closed; enrichment fails open and alerts.
- Cache keyed on
(chain, address), TTL by field volatility. - Idempotency key per screening event; retries capped inside the latency budget.
- Full response persisted with a trace ID, immutable, retained for the AML period.
- Re-screening job running on schedule plus list-update triggers.
- Degraded-mode duration monitored and paged.
- Policy thresholds versioned in code review, not edited in a console.
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.