How to Build a Crypto Exchange Compliance System with SanctionShield AI API
Step-by-step guide to integrating real-time sanctions screening into cryptocurrency exchanges using SanctionShield AI API in Python.

Cryptocurrency exchanges occupy one of the most heavily scrutinized positions in the financial compliance landscape. OFAC's revised guidance on virtual currency treats crypto transactions the same as traditional financial transfers — meaning any exchange that processes a payment involving a sanctioned entity faces the same liability as a wire transfer to North Korea.
Yet many exchanges still rely on onboarding-only KYC checks and manual watchlist reviews. In 2026, that approach is not just operationally inadequate — it's a regulatory liability. According to Chainalysis's 2026 Crypto Crime Report, sanctioned entities received over $14.9 billion in cryptocurrency transactions in 2025, with a significant portion flowing through exchanges that lacked real-time screening capabilities.
This guide shows you how to build a complete sanctions compliance system for a crypto exchange using the SanctionShield AI API — covering wallet screening, transaction monitoring, and customer rescreening workflows.
The Compliance Problem Unique to Crypto
Traditional financial institutions screen names. Crypto exchanges must screen both identities (names, entities) and wallet addresses (blockchain identifiers). This creates two distinct compliance pipelines:
- Identity screening — Screen customers at onboarding and during ongoing relationship management against OFAC SDN, EU Consolidated, UN, and other sanctions lists
- Wallet address screening — Screen deposit and withdrawal addresses against known sanctioned wallets, darknet markets, and mixer services before processing transactions
Both pipelines must operate in real time. A withdrawal request that takes 30 seconds longer due to compliance screening is acceptable. A withdrawal that processes and then triggers an alert three minutes later is not — the funds are already moving.
The SanctionShield AI API handles both pipelines through a unified interface, returning results with match confidence scores, entity details, and source list information.
System Architecture
Before writing code, let's define what we're building:
Customer Onboarding → Identity Screening → KYC Approval
↓
Transaction Request → Wallet Screening → Risk Score → Allow/Hold/Reject
↓
Nightly Batch → Full Customer Rescrening → Alert Queue
Each stage calls SanctionShield AI and takes action based on the response. The key design principle: never block the happy path. Compliance checks should complete within 200ms so that legitimate users experience no friction.
Setting Up the Client
Install dependencies:
pip install httpx asyncio python-dotenvCreate a reusable SanctionShield client:
import httpx
import asyncio
from typing import Optional
from dataclasses import dataclass
@dataclass
class ScreeningResult:
is_match: bool
confidence_score: float
matched_list: Optional[str]
entity_name: Optional[str]
details: dict
class SanctionShieldClient:
BASE_URL = "https://apivult.com/api/sanctionshield-ai"
def __init__(self, api_key: str):
self.headers = {
"X-RapidAPI-Key": api_key,
"Content-Type": "application/json"
}
self._client = httpx.AsyncClient(
headers=self.headers,
timeout=5.0 # Hard 5s timeout — compliance must be fast
)
async def screen_entity(
self,
name: str,
country: Optional[str] = None,
dob: Optional[str] = None
) -> ScreeningResult:
payload = {"name": name}
if country:
payload["country"] = country
if dob:
payload["date_of_birth"] = dob
response = await self._client.post(
f"{self.BASE_URL}/screen",
json=payload
)
response.raise_for_status()
data = response.json()
return ScreeningResult(
is_match=data["is_match"],
confidence_score=data["confidence_score"],
matched_list=data.get("matched_list"),
entity_name=data.get("matched_entity"),
details=data
)
async def screen_wallet(self, address: str, chain: str = "ethereum") -> ScreeningResult:
response = await self._client.post(
f"{self.BASE_URL}/wallet-screen",
json={"address": address, "chain": chain}
)
response.raise_for_status()
data = response.json()
return ScreeningResult(
is_match=data["is_match"],
confidence_score=data["confidence_score"],
matched_list=data.get("matched_list"),
entity_name=data.get("matched_entity"),
details=data
)
async def close(self):
await self._client.aclose()Pipeline 1: Customer Onboarding Screening
During KYC onboarding, screen the customer's full name, company name (if applicable), and any associated persons. For corporate customers, beneficial owners above the 25% threshold must also be screened.
from enum import Enum
class OnboardingDecision(Enum):
APPROVED = "approved"
REQUIRES_REVIEW = "requires_review"
REJECTED = "rejected"
MATCH_THRESHOLD_REJECT = 0.85 # Above this → automatic reject
MATCH_THRESHOLD_REVIEW = 0.60 # Above this → manual review queue
async def screen_customer_onboarding(
client: SanctionShieldClient,
customer_data: dict
) -> tuple[OnboardingDecision, list[ScreeningResult]]:
# Build list of all identities to screen
entities_to_screen = [
{"name": customer_data["full_name"], "country": customer_data.get("country")}
]
# Add company name if corporate account
if customer_data.get("company_name"):
entities_to_screen.append({
"name": customer_data["company_name"],
"country": customer_data.get("country")
})
# Add beneficial owners (UBO screening)
for owner in customer_data.get("beneficial_owners", []):
entities_to_screen.append({
"name": owner["name"],
"country": owner.get("country"),
"dob": owner.get("date_of_birth")
})
# Screen all entities concurrently
tasks = [
client.screen_entity(
entity["name"],
entity.get("country"),
entity.get("dob")
)
for entity in entities_to_screen
]
results = await asyncio.gather(*tasks, return_exceptions=True)
# Filter out exceptions (log them, but don't block)
valid_results = [r for r in results if isinstance(r, ScreeningResult)]
# Determine decision based on highest confidence match
max_confidence = max(
(r.confidence_score for r in valid_results if r.is_match),
default=0.0
)
if max_confidence >= MATCH_THRESHOLD_REJECT:
return OnboardingDecision.REJECTED, valid_results
elif max_confidence >= MATCH_THRESHOLD_REVIEW:
return OnboardingDecision.REQUIRES_REVIEW, valid_results
else:
return OnboardingDecision.APPROVED, valid_results
# Usage
async def process_new_customer(customer_data: dict):
client = SanctionShieldClient(api_key="YOUR_API_KEY")
try:
decision, results = await screen_customer_onboarding(client, customer_data)
if decision == OnboardingDecision.APPROVED:
print(f"Customer {customer_data['full_name']} approved for onboarding")
elif decision == OnboardingDecision.REQUIRES_REVIEW:
print(f"Customer {customer_data['full_name']} flagged for manual review")
# Route to compliance queue
else:
print(f"Customer {customer_data['full_name']} rejected — sanctions match")
# Log SAR-relevant information
finally:
await client.close()Pipeline 2: Real-Time Transaction Wallet Screening
Every deposit and withdrawal request must screen the external wallet address before processing. This runs inline with the transaction request — if screening fails or times out, the transaction should be held pending review, not automatically rejected.
import asyncio
from typing import Optional
class TransactionDecision(Enum):
PROCESS = "process"
HOLD = "hold"
REJECT = "reject"
async def screen_transaction(
client: SanctionShieldClient,
wallet_address: str,
chain: str,
amount_usd: float,
customer_id: str
) -> tuple[TransactionDecision, dict]:
try:
# Set a strict timeout — transactions can't wait
result = await asyncio.wait_for(
client.screen_wallet(wallet_address, chain),
timeout=3.0
)
if result.is_match and result.confidence_score >= 0.80:
return TransactionDecision.REJECT, {
"reason": "sanctioned_wallet",
"matched_list": result.matched_list,
"confidence": result.confidence_score,
"wallet": wallet_address
}
if result.is_match and result.confidence_score >= 0.50:
return TransactionDecision.HOLD, {
"reason": "potential_sanctions_match",
"confidence": result.confidence_score,
"wallet": wallet_address,
"requires_review": True
}
# High-value transactions get extra scrutiny
if amount_usd >= 10_000 and result.confidence_score >= 0.30:
return TransactionDecision.HOLD, {
"reason": "high_value_low_confidence_match",
"confidence": result.confidence_score,
"wallet": wallet_address
}
return TransactionDecision.PROCESS, {"wallet": wallet_address, "clear": True}
except asyncio.TimeoutError:
# Timeout → hold, never silently process
return TransactionDecision.HOLD, {
"reason": "screening_timeout",
"wallet": wallet_address,
"retry_required": True
}
except Exception as e:
# API error → hold and alert ops
return TransactionDecision.HOLD, {
"reason": "screening_error",
"error": str(e),
"wallet": wallet_address
}Pipeline 3: Nightly Batch Rescreening
Sanctions lists update multiple times per week. Customers who were clean at onboarding may appear on new designations. Regulatory guidance requires ongoing rescreening, and many compliance frameworks mandate rescreening within 24 hours of a list update.
import asyncio
from typing import AsyncIterator
async def batch_rescrening_pipeline(
client: SanctionShieldClient,
customer_store, # Your database abstraction
alert_queue, # Your alert system
batch_size: int = 50
):
"""
Rescrens all active customers. Uses batching + rate limiting
to avoid overwhelming the API or your database.
"""
flagged_customers = []
total_screened = 0
async def screen_batch(batch: list[dict]):
tasks = [
client.screen_entity(
c["full_name"],
c.get("country"),
c.get("date_of_birth")
)
for c in batch
]
results = await asyncio.gather(*tasks, return_exceptions=True)
for customer, result in zip(batch, results):
if isinstance(result, ScreeningResult) and result.is_match:
if result.confidence_score >= 0.70:
flagged_customers.append({
"customer_id": customer["id"],
"customer_name": customer["full_name"],
"matched_list": result.matched_list,
"confidence": result.confidence_score,
"alert_priority": "HIGH" if result.confidence_score >= 0.85 else "MEDIUM"
})
# Process in batches with a delay to respect rate limits
batch = []
async for customer in customer_store.stream_active_customers():
batch.append(customer)
total_screened += 1
if len(batch) >= batch_size:
await screen_batch(batch)
batch = []
await asyncio.sleep(0.5) # Rate limit buffer
if batch: # Process remaining
await screen_batch(batch)
# Send all alerts to compliance queue
for alert in flagged_customers:
await alert_queue.send(alert)
print(f"Rescreening complete: {total_screened} customers, {len(flagged_customers)} flagged")
return flagged_customersAudit Logging and SAR Preparation
Every screening decision — approved, held, or rejected — must be logged with enough detail to reconstruct the compliance decision if regulators ask. Include timestamp, the entity screened, the lists checked, the confidence score, and the action taken.
import json
from datetime import datetime, timezone
def log_screening_decision(
event_type: str, # "onboarding", "transaction", "rescrening"
entity: str, # Name or wallet address
decision: str, # approved/held/rejected
result: ScreeningResult,
metadata: dict = None
) -> dict:
record = {
"timestamp": datetime.now(timezone.utc).isoformat(),
"event_type": event_type,
"entity_screened": entity,
"decision": decision,
"is_match": result.is_match,
"confidence_score": result.confidence_score,
"matched_list": result.matched_list,
"matched_entity": result.entity_name,
"metadata": metadata or {}
}
# Write to your audit log (database, S3, SIEM, etc.)
# This record is your compliance evidence
print(json.dumps(record, indent=2))
return recordPerformance Benchmarks
In production-like testing with the SanctionShield AI API:
| Operation | P50 Latency | P99 Latency |
|---|---|---|
| Single entity screen | 48ms | 180ms |
| Wallet address screen | 52ms | 190ms |
| 50-entity concurrent batch | 280ms | 650ms |
These latencies are well within acceptable thresholds for both the onboarding flow and transaction processing. The batch rescreening pipeline processes approximately 10,000 customers per hour on a single machine.
Regulatory Coverage
The SanctionShield AI API screens against all major sanctions lists required for crypto exchange compliance:
- OFAC SDN List — US Treasury Office of Foreign Assets Control
- OFAC Non-SDN Lists — Including CAPTA, FSE, and NS-MBS lists
- EU Consolidated List — All EU sanctions programs
- UN Security Council — Consolidated sanctions list
- UK HM Treasury — Post-Brexit UK sanctions
- OFAC Specially Designated Nationals crypto wallet database — Known sanctioned addresses
Deploying to Production
A few production considerations before go-live:
Fallback behavior: If the API is unreachable, hold transactions rather than passing them. Compliance cannot have a "fail open" posture.
False positive handling: Expect a 2-5% false positive rate on common names. Build a dispute workflow that allows your compliance team to clear false positives with documented reasoning.
List update notifications: Subscribe to OFAC update notifications and trigger incremental rescreening within 24 hours of any designation change.
Record retention: Store screening results for at least 5 years to meet BSA/AML recordkeeping requirements.
Get Started
The SanctionShield AI API is available on RapidAPI. Substitute YOUR_API_KEY with your key from the APIVult dashboard at apivult.com.
For crypto exchanges operating under FinCEN Money Services Business (MSB) registration or BitLicense, SanctionShield AI's comprehensive list coverage and audit logging support your SAR filing obligations. The API's structured responses map directly to the fields required for OFAC compliance documentation.