Didit Transaction Monitoring & Travel Rule API
Overview
Screens every transaction in real time against a configurable rule engine (velocity, thresholds, behavioral patterns, blocklists), automatically screens crypto wallets and hashes against blockchain analytics, and runs the full FATF / EU TFR Travel Rule exchange with counterparty VASPs.
Key facts:
POST /v3/transactions/is synchronous: rules, screening, and Travel Rule routing all run inside the call - the response already carries the verdict.- Transaction statuses:
APPROVED,IN_REVIEW,DECLINED,AWAITING_USER(user must act - readaction_required). - Pricing: $0.02 per transaction; Travel Rule transfers bill a dedicated $0.02 line instead (outbound only); crypto screening bills separately (from $0.15 managed, $0.02 BYOK), charged once even when Travel Rule attribution needs the same lookup.
txn_idis idempotent per application - resubmitting returns the existing transaction.- Sandbox applications skip rules, webhooks, and billing.
API Reference: https://docs.didit.me/management-api/transactions/create Feature guides: https://docs.didit.me/transaction-monitoring/overview · https://docs.didit.me/transaction-monitoring/travel-rule · https://docs.didit.me/transaction-monitoring/wallet-screening
Authentication
All requests require the x-api-key header. Get your key from Didit Business Console → API & Webhooks, or via programmatic registration (see below).
Getting Started (No Account Yet?)
- Register:
POST https://apx.didit.me/auth/v2/programmatic/register/with{"email": "you@gmail.com", "password": "MyStr0ng!Pass"} - Check email for a 6-character OTP code
- Verify:
POST https://apx.didit.me/auth/v2/programmatic/verify-email/with{"email": "you@gmail.com", "code": "A3K9F2"}→ response includesapi_key
See the didit-verification-management skill for full platform management (workflows, sessions, users, billing).
Create Transaction
POST https://verification.didit.me/v3/transactions/
Required: transaction_id (your unique id), transaction_category, transaction_details (direction, amount, currency), subject (entity_type, vendor_data, full_name).
Categories: finance, kyc, travel_rule (alias travelRule), user_event, audit_trail_event, gambling_bet, gambling_limit_change, gambling_bonus_change.
curl -X POST https://verification.didit.me/v3/transactions/ \
-H "x-api-key: $DIDIT_API_KEY" -H "Content-Type: application/json" \
-d '{
"transaction_id": "tx-0001",
"transaction_category": "finance",
"transaction_details": {"direction": "outbound", "amount": "1500.50", "currency": "EUR", "action_type": "withdrawal"},
"subject": {"entity_type": "person", "vendor_data": "user-42", "full_name": "John Doe"},
"counterparty": {"entity_type": "company", "full_name": "Acme Corp"},
"custom_properties": {"order_id": "ORD-7891"}
}'
Response essentials: uuid (Didit id, use in GET /v3/transactions/{uuid}/), status, score (0-100+), severity, decision_reason_code/decision_reason_label, rule_runs[], provider_results[], travel_rule (when applicable), action_required, cost_breakdown.
subject.vendor_datalinks to the user's KYC profile - the same id you used for verification sessions. This powers velocity rules, entity aggregation, and stored-face reuse for step-up verification.- camelCase aliases accepted:
txnId,type,info,applicant,props,travelRule,includeCryptoScreening. - Attach
subject.payment_method/counterparty.payment_method(method_type,account_id,issuing_country); a counterpartymethod_type: "unhosted_wallet"declares a self-hosted wallet for Travel Rule.
action_required (AWAITING_USER)
When a transaction needs the end user to act, the response (and every read + webhook) carries one of:
{"type": "verification_session", "url", "session_id", "session_token", "status"}- a rule demanded step-up verification (e.g. biometric re-check against the face captured at KYC onboarding). Send the user tourlor feedsession_tokento a Didit SDK.{"type": "wallet_ownership", "url", "widget_session_id", "expires_at"}- a Travel Rule transfer needs proof of wallet control. Openurlin a real browser (never a mobile webview).
action_required returns to null once resolved; the transaction re-evaluates automatically and fires transaction.status.updated.
Crypto screening
Runs automatically when transaction_details.currency_kind: "crypto" and monitoring is enabled (override per call with top-level include_crypto_screening: true|false).
Direction picks the screened wallet: inbound pre-transfer screens counterparty.payment_method.account_id (sender); outbound screens the destination in counterparty.payment_method.account_id. Include the on-chain hash in transaction_details.payment_reference_id for post-transfer screening.
On-demand (no transaction): POST /v3/wallet-screening/ with {"wallet_address": "0x...", "blockchain": "ETH"} (chains: BTC, ETH, LTC, XRP, BCH, DOGE, TRX, SOL, MATIC, BNB, USDT, USDC). Returns risk_score (0-100), severity, sanctions_hit, source_of_funds[], destination_of_funds[], counterparty_connections[]. 409 = no provider configured.
Travel Rule (FATF / EU TFR)
Enable once: PUT /v3/travel-rule/settings/ with {"is_enabled": true, "legal_name": "Your CASP SL", "jurisdiction": "EU"} (partial update; GET the same path to read, including networks modes and your travel_address).
Send a transfer: create a transaction with transaction_category: "travelRule" and travel_rule_details.beneficiary_data (wallet_address, name, plus optional counterparty identifiers: travel_address, counterparty_vasp_lei, counterparty_vasp_name, counterparty_vasp_email, network ids). Didit routes over seven rails in priority order (INTERNAL → TRP → GTR → TRUST → VERIFYVASP → SYGNA → EMAIL), falling back to blockchain-analytics attribution of the destination wallet.
Transfer status lives at travel_rule.status (13 states). The ones that matter operationally:
| Status | Meaning / action |
|---|---|
AWAITING_COUNTERPARTY |
Waiting on the counterparty VASP - do not settle yet. |
UNCONFIRMED_OWNERSHIP |
End user must prove wallet control (widget in action_required). |
COMPLETED |
Data matched - safe to broadcast on-chain now. |
FINISHED |
You reported the on-chain hash; exchange closed. |
COUNTERPARTY_VASP_NOT_FOUND / NOT_REACHABLE / NOT_ENOUGH_COUNTERPARTY_DATA |
Improve counterparty data, then resend. |
NOT_APPLICABLE |
Below your threshold_amount or Travel Rule disabled. |
Never broadcast before COMPLETED. Close out with PATCH /v3/transactions/{uuid}/travel-rule/:
{"payment_txn_id": "0xhash"}→FINISHED(outbound + COMPLETED only){"action": "resend"}→ re-run routing (from the three unroutable states){"action": "cancel"}→ cancel any non-terminal transfer
Other Travel Rule endpoints (all x-api-key):
GET /v3/travel-rule/vasps/?search=<name>- VASP directory with due-diligence scores + reachable rails.GET|POST /v3/travel-rule/wallet-addresses/,PATCH|DELETE .../{entry_uuid}/- the address book inbound INTERNAL-rail transfers resolve against (self_declared: truepre-verifies).POST /v3/transactions/{uuid}/travel-rule/ownership/with{"confirmed": true|false}- confirm/deny ownership (e.g. after screenshot review).POST /v3/travel-rule/widget-session/- mint a hosted wallet-ownership proof link (message signing, Satoshi test, screenshot, self-declaration); pollGET /v3/travel-rule/widget/{token}/forcompleted_at.POST /v3/travel-rule/inbound/- register an already-settled deposit (sunrise flow), dedupes ontxid+wallet_address.
Full guide: https://docs.didit.me/transaction-monitoring/travel-rule
SDK transaction tokens (client-side submission)
Mint on your backend: POST /v3/transactions/sdk-token/ with {"vendor_data": "user-42", "ttl_seconds": 900, "max_uses": 3} → {"sdk_token", "expires_at"}.
The device then calls POST /v1/transactions/ (and GET /v1/transactions/{uuid}/) with the X-Transaction-Token header - subject identity is enforced from the token, device intelligence is attached automatically, and the Didit SDKs wrap this as submitTransaction. Never ship an API key in an app.
Guide: https://docs.didit.me/transaction-monitoring/sdk-transaction-submission
Webhooks
Subscribe via POST /v3/webhook/destinations/ to:
transaction.created- every submitted transaction (payload:transaction_id,txn_id,status,score,severity,amount,currency,direction,action_required).transaction.status.updated- every status change; fetch the full record for the reason.travel_rule.status.updated- every exchange transition (travel_rule_status,rail,direction).
Flat envelope with webhook_type, event_id (dedupe key), timestamp, application_id, environment; HMAC headers X-Signature-V2 (recommended), X-Signature, X-Signature-Simple, X-Timestamp.
Common Workflows
Crypto withdrawal with Travel Rule (end to end)
PUT /v3/travel-rule/settings/once:is_enabled: true+legal_name.POST /v3/transactions/withtransaction_category: "travelRule", cryptotransaction_details, the user assubject, destination wallet as counterpartypayment_method(unhosted_walletif the user's own wallet), andtravel_rule_details.beneficiary_data.- If
action_required.type == "wallet_ownership", send the user to itsurl. - Wait for
travel_rule.status == "COMPLETED"(webhook orGET /v3/transactions/{uuid}/). - Broadcast on-chain, then
PATCH .../travel-rule/withpayment_txn_id→FINISHED.
Step-up biometrics for high-value transactions
- In the console (Transactions → Rules), create a rule:
amount gte 5000→ change statusAWAITING_USER+ select a Biometric Authentication workflow. - Submit transactions as usual; when the rule fires,
action_required.type == "verification_session". - Send the user to the session
url- Didit face-matches a live selfie against the face verified at their KYC onboarding (samevendor_data), defeating stolen-credential ATO. - The transaction auto-resolves to
APPROVED/DECLINED;transaction.status.updatedfires.