Education· Last updated April 7, 2026

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.

How to Build a Crypto Exchange Compliance System with SanctionShield AI API

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:

  1. Identity screening — Screen customers at onboarding and during ongoing relationship management against OFAC SDN, EU Consolidated, UN, and other sanctions lists
  2. 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-dotenv

Create 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_customers

Audit 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 record

Performance Benchmarks

In production-like testing with the SanctionShield AI API:

OperationP50 LatencyP99 Latency
Single entity screen48ms180ms
Wallet address screen52ms190ms
50-entity concurrent batch280ms650ms

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.