Guide: Onboarding Flow
Screen wallet addresses at user signup before granting platform access.
When to screen
Screen immediately when a user registers a wallet address — before account activation, before any deposits, and before any permissions are granted. This is the lowest-cost intervention point.
Decision flowchart
User submits wallet address
│
▼
POST /v1/screen
│
├─ CLEAN_NO_MATCH ──────────────▶ Activate account ✓
│
├─ LOW / MEDIUM ────────────────▶ Flag for manual review
│ Queue for compliance team
│ Send "under review" email
│
└─ HIGH / CRITICAL ─────────────▶ Reject account ✗
Log for reporting
Do not reveal reasonCode
import os
import requests
from enum import Enum
SCREENING_API_KEY = os.environ["SCREENING_API_KEY"]
BASE_URL = "https://anchorapi.dev/api/v1"
class OnboardingDecision(str, Enum):
ALLOW = "allow"
REVIEW = "review"
REJECT = "reject"
def screen(address: str, chain: str) -> dict:
response = requests.post(
f"{BASE_URL}/screen",
headers={"Authorization": f"Bearer {SCREENING_API_KEY}"},
json={"address": address, "chain": chain},
timeout=10,
)
response.raise_for_status()
return response.json()
def onboarding_decision(wallet_address: str, chain: str) -> tuple[OnboardingDecision, dict]:
"""
Screen a wallet at user signup.
Returns (decision, screening_result) for audit logging.
"""
result = screen(wallet_address, chain)
level = result["level"]
if level == "CLEAN_NO_MATCH":
decision = OnboardingDecision.ALLOW
elif level in ("CRITICAL", "HIGH"):
decision = OnboardingDecision.REJECT
else: # MEDIUM (LOW is reserved; not currently produced)
decision = OnboardingDecision.REVIEW
return decision, result
# In your signup handler:
def handle_signup(user_id: str, wallet_address: str, chain: str):
decision, result = onboarding_decision(wallet_address, chain)
# Always log the screening result for compliance audit trail
log_screening_event(
user_id=user_id,
address=wallet_address,
chain=chain,
level=result["level"],
score=result["score"],
labels=[l["label"] for l in result["labels"]],
scoring_version=result["scoring_version"],
decision=decision,
)
if decision == OnboardingDecision.ALLOW:
activate_account(user_id)
elif decision == OnboardingDecision.REVIEW:
flag_for_manual_review(user_id)
send_pending_email(user_id) # "Your account is under review"
elif decision == OnboardingDecision.REJECT:
reject_account(user_id)
# Do not reveal the reason to the user — standard compliance practiceCompliance notes
- Always log the full result — score, level, labels, scoring_version, and timestamp — for your audit trail. Regulators may ask for evidence of the screening.
- Do not reveal rejection reasons to users. Disclosing that an address is on a sanctions list can constitute tipping off, which may violate laws in some jurisdictions.
- MEDIUM/LOW is not auto-pass. Route these to a compliance queue for manual review within your defined SLA (e.g., 24 hours).
- CLEAN_NO_MATCH is not a guarantee. Screening should be one layer of your compliance program, not the only one.