Don't miss new tech stories on Google
Add Tech Insider once in the Google app and our stories appear in your news suggestions.
What the FATF Travel Rule Requires in 2026
The Financial Action Task Force (FATF) Travel Rule–formally FATF Recommendation 16–obliges Virtual Asset Service Providers (VASPs) and financial institutions to collect and transmit originator and beneficiary data alongside every qualifying virtual-asset transfer. Originally applied only to wire transfers between banks, FATF extended the rule to VASPs in 2019. By 2026, 85 of 117 monitored jurisdictions have passed Travel Rule legislation, and more than 60 jurisdictions now require VASP registration or licensing.
For iGaming operators that accept cryptocurrency deposits and withdrawals, the Travel Rule creates a dual obligation: you must both transmit your player’s originator data to the beneficiary VASP and receive and screen counterparty data arriving from originating VASPs–all before the transaction completes. Failure to do so exposes you to sanctions from the Financial Crimes Enforcement Network (FinCEN) in the US, the FCA in the UK, or AUSTRAC in Australia.
Sumsub’s Travel Rule solution–part of its broader Transaction Monitoring platform–has been generally available since June 2025 and added a self-service activation tier on May 26, 2026. It supports the TRP, OpenVASP/GTR, and Sumsub custom protocols, plus a fallback email-notification path for VASPs not yet on a shared network. This 12-step guide walks you through a complete Node.js integration, from credential setup through automated FIU report generation, using only 2026-verified API behaviour.
FATF Thresholds by Jurisdiction (2026)
| Jurisdiction | Regulator | Threshold | Key 2026 Development |
|---|---|---|---|
| FATF (baseline) | FATF | $1,000 / €1,000 | Recommendation 16 applies to VASPs and FIs |
| United States | FinCEN | $3,000 (Rule §31 CFR 1010.410) | Proposed VASP-specific Travel Rule rulemaking pending |
| European Union | EBA / ESMA | €0 (MiCA TFR – all transfers) | MiCA Transfer of Funds Regulation fully in force Dec 2024 |
| United Kingdom | FCA / HM Treasury | £0 (all transfers) | Draft SI published 26 Mar 2026 amending MLR 2017 |
| Australia | AUSTRAC | AUD 1,000 | Transitional rules effective 31 Mar 2026; certain VA obligations deferred to 1 Jul 2026 |
| Singapore | MAS | SGD 1,500 (~USD 1,100) | PSA Digital Payment Token rules effective Jan 2025 |
Prerequisites and Environment Setup
Before you write a single line of integration code, confirm the following versions and account states are in place. Sumsub’s API surface has changed significantly since v2.0 (released late 2024); these steps assume v2.1+ which introduced the unified Transaction Monitoring + Travel Rule endpoint set.
- Node.js 22 LTS (v22.3.0 or later) – required for native
crypto.webcryptosupport used by the HMAC-SHA256 signature helper - npm 10.x or pnpm 9.x
- Sumsub account with Transaction Monitoring and Travel Rule modules enabled (Dashboard → Settings → Modules)
- App token + secret key from Sumsub Dashboard → Developers → App Tokens (minimum scope:
transaction:write,travel-rule:write,travel-rule:read) - Webhook endpoint reachable over HTTPS – use
ngrokor a staging subdomain during development - PostgreSQL 16 for the idempotent ledger (or swap for your existing RDBMS)
Environment Variables
# .env
SUMSUB_APP_TOKEN=your_app_token_here
SUMSUB_SECRET_KEY=your_secret_key_here
SUMSUB_BASE_URL=https://api.sumsub.com
WEBHOOK_SECRET=your_webhook_hmac_secret
DATABASE_URL=postgresql://user:pass@localhost:5432/aml_db
PORT=3000
Install Dependencies
npm install axios crypto-js dotenv express pg uuid
# Optional: for TypeScript users
npm install -D @types/node @types/express typescript ts-node
Step 1 – Build the Sumsub API Client with HMAC Signatures
Sumsub requires every request to carry a timestamp and an HMAC-SHA256 signature over the method + path + timestamp + body. The signature expires after 60 seconds, so you must regenerate it per request rather than caching it. Below is a minimal but production-ready client wrapper.
// src/sumsubClient.js
import axios from 'axios';
import crypto from 'crypto';
import 'dotenv/config';
const BASE_URL = process.env.SUMSUB_BASE_URL;
const APP_TOKEN = process.env.SUMSUB_APP_TOKEN;
const SECRET_KEY = process.env.SUMSUB_SECRET_KEY;
function sign(method, path, ts, body = '') {
const payload = ts + method.toUpperCase() + path + body;
return crypto
.createHmac('sha256', SECRET_KEY)
.update(payload)
.digest('hex');
}
export async function sumsubRequest(method, path, data = null) {
const ts = Math.floor(Date.now() / 1000).toString();
const body = data ? JSON.stringify(data) : '';
const sig = sign(method, path, ts, body);
const headers = {
'X-App-Token': APP_TOKEN,
'X-App-Access-Ts': ts,
'X-App-Access-Sig': sig,
'Content-Type': 'application/json',
'Accept': 'application/json',
};
const response = await axios({
method,
url: BASE_URL + path,
headers,
data: data || undefined,
});
return response.data;
}
Expected output when you call sumsubRequest('GET', '/resources/status'):
{ "status": "GREEN", "serverTime": 1750000000 }
Pitfall: Clock Skew Breaks Every Request
Sumsub rejects signatures if the server clock is more than 60 seconds off. Confirm your server’s NTP sync: timedatectl status on Linux. If you see System clock synchronized: no, run sudo systemctl restart systemd-timesyncd before any further testing.
Step 2 – Register a KYC Applicant and Link It to Your Player
Travel Rule data is tied to a Sumsub applicant. If you already have a KYC integration (see the complete Sumsub KYC API setup for iGaming operators for a full 12-step walkthrough), you can skip applicant creation and use the existing applicantId. If not, create one now and store the mapping in your database.
// src/createApplicant.js
import { sumsubRequest } from './sumsubClient.js';
import { pool } from './db.js'; // pg Pool instance
export async function ensureApplicant(playerId, email, phone) {
// Check local mapping first
const { rows } = await pool.query(
'SELECT sumsub_id FROM player_kyc WHERE player_id = $1',
[playerId]
);
if (rows.length > 0) return rows[0].sumsub_id;
const applicant = await sumsubRequest('POST', '/resources/applicants?levelName=basic-kyc-level', {
externalUserId: `player_${playerId}`,
email,
phone,
fixedInfo: { country: 'GBR' }, // ISO 3166-1 alpha-3
});
await pool.query(
'INSERT INTO player_kyc (player_id, sumsub_id) VALUES ($1, $2)',
[playerId, applicant.id]
);
return applicant.id;
}
Database Schema for AML Ledger
CREATE TABLE player_kyc (
player_id BIGINT PRIMARY KEY,
sumsub_id VARCHAR(64) NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE aml_transactions (
tx_id UUID PRIMARY KEY,
player_id BIGINT NOT NULL,
sumsub_tx_id VARCHAR(128),
direction VARCHAR(4) CHECK (direction IN ('IN','OUT')),
amount_usd NUMERIC(18,6) NOT NULL,
asset VARCHAR(16) NOT NULL,
wallet_address VARCHAR(128),
status VARCHAR(32) DEFAULT 'PENDING',
travel_rule_status VARCHAR(32),
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE INDEX ON aml_transactions (player_id, created_at DESC);
CREATE INDEX ON aml_transactions (status) WHERE status = 'PENDING';
Step 3 – Submit a Transaction to Sumsub Monitoring
Every deposit or withdrawal that crosses your monitoring threshold must be submitted to Sumsub’s Transaction Monitoring API. The payload bundles payment details with the applicant reference so the system can build a behavioural profile over time.
// src/submitTransaction.js
import { sumsubRequest } from './sumsubClient.js';
import { pool } from './db.js';
import { v4 as uuidv4 } from 'uuid';
export async function submitTransaction({
playerId,
sumsubApplicantId,
direction, // 'deposit' | 'withdrawal'
amountUsd,
asset, // 'BTC' | 'ETH' | 'USDT' etc
walletAddress,
txHash,
}) {
const txId = uuidv4();
const payload = {
applicantId: sumsubApplicantId,
externalTransactionId: txId,
type: direction === 'deposit' ? 'INCOMING' : 'OUTGOING',
amount: amountUsd,
currency: 'USD',
cryptoInfo: {
asset,
txHash,
address: walletAddress,
direction: direction === 'deposit' ? 'IN' : 'OUT',
},
info: {
paymentMethod: 'CRYPTO_WALLET',
},
};
const result = await sumsubRequest(
'POST',
`/resources/transactions?applicantId=${sumsubApplicantId}`,
payload
);
await pool.query(
`INSERT INTO aml_transactions
(tx_id, player_id, sumsub_tx_id, direction, amount_usd, asset, wallet_address, status)
VALUES ($1, $2, $3, $4, $5, $6, $7, 'PENDING')`,
[txId, playerId, result.id, direction.toUpperCase().slice(0, 3), amountUsd, asset, walletAddress]
);
return { txId, sumsubTxId: result.id };
}
Expected response:
{
"id": "trx_66a3bc91f3d2",
"status": "PENDING",
"applicantId": "6627a91c-...",
"riskScore": null,
"createdAt": "2026-06-18T10:12:00Z"
}
Real-Time vs Batch Monitoring: When to Use Each
Sumsub recommends real-time monitoring for high-risk transactions–anything over your jurisdiction threshold or involving flagged wallets–and batch monitoring for lower-risk activity. For most iGaming operators a hybrid approach works best: submit every crypto transaction in real time but process fiat-equivalent tickets in nightly batch runs.
Step 4 – Trigger Travel Rule Data Exchange
Once a transaction is submitted, any transfer that meets or exceeds the applicable threshold must trigger a Travel Rule check. Sumsub’s Travel Rule API automatically selects the fastest available data-exchange protocol in order: TRP → GTR → Sumsub custom → email. You call a single endpoint; the routing happens server-side.
// src/triggerTravelRule.js
import { sumsubRequest } from './sumsubClient.js';
export async function triggerTravelRule({
sumsubTxId,
sumsubApplicantId,
counterpartyVaspDid, // Sumsub VASP directory DID, or null for unhosted
counterpartyWallet,
originatorInfo, // { name, dob, address, nationalId }
beneficiaryInfo, // { name, accountNumber }
}) {
const payload = {
transactionId: sumsubTxId,
originator: {
applicantId: sumsubApplicantId,
name: originatorInfo.name,
accountNumber: counterpartyWallet,
birthDate: originatorInfo.dob, // 'YYYY-MM-DD'
address: originatorInfo.address,
nationalIdentityNumber: originatorInfo.nationalId,
},
beneficiary: {
name: beneficiaryInfo.name,
accountNumber: beneficiaryInfo.accountNumber,
},
counterpartyVasp: counterpartyVaspDid
? { did: counterpartyVaspDid }
: { unhosted: true },
};
return sumsubRequest(
'POST',
`/resources/travel-rule/checks?transactionId=${sumsubTxId}`,
payload
);
}
For transfers under the $1,000/€1,000 FATF baseline, pass only name and accountNumber (wallet address). For transfers above the threshold the full originator block (birthDate, address, nationalIdentityNumber) is required by FATF Recommendation 16. Omitting any mandatory field returns HTTP 422 with a clear field-level error array.
VASP Attribution: Hosted vs Unhosted Wallets
When counterpartyVaspDid is null, Sumsub runs its wallet-attribution heuristic against its own VASP directory. If the wallet is recognised as belonging to a known VASP, the DID is auto-resolved and the TRP handshake begins automatically. If attribution fails–the wallet appears unhosted–Sumsub triggers the unhosted-wallet flow, prompting you to collect a proof-of-ownership declaration from the player.
Step 5 – Configure Rules and Thresholds in the Dashboard
Sumsub ships with 300+ pre-built rule bundles covering fintech, traditional finance, crypto, e-commerce, and iGaming scenarios. You activate and tune them in Dashboard → Transaction Monitoring → Rules without writing any code. Key rule categories for iGaming operators:
| Rule Bundle | Trigger Condition | Recommended Action | iGaming Relevance |
|---|---|---|---|
| High-value single transaction | Single deposit > $5,000 equivalent | Manual review + Travel Rule check | VIP whale deposits, crypto rollers |
| Structuring / Smurfing | 3+ transactions < threshold within 24h | Alert + freeze pending review | Bonus abuse via split crypto deposits |
| Velocity spike | Transaction volume 300% above 30-day average | Alert + enhanced due diligence trigger | Account takeover / mule detection |
| High-risk jurisdiction | Wallet attributed to VASP in FATF grey/black list country | Block + SAR generation | Sanctioned-country deposits |
| Crypto mixer / tumbler | Wallet interacted with known mixer in last 5 hops | Block transaction + flag account | Layering detection |
| Rapid deposit-withdrawal | Withdrawal within 2h of large deposit, no gameplay | Alert + hold withdrawal | Money mule / pass-through accounts |
You can also add AI-driven anomaly detection (Dashboard → Rules → AI Rules) which lets Sumsub’s ML layer surface patterns beyond your static thresholds. The Forrester TEI Study (2025) found that AI-assisted anomaly detection cut manual reviews by 90% in a representative fintech deployment.
Dry-Run Mode for New Rules
Before activating any rule in production, toggle “Dry Run” in the rule editor. In dry-run mode the rule evaluates and generates alerts visible in the dashboard but takes no automated action and fires no webhooks to your system. Run at least 72 hours of dry-run data before enabling enforcement.
Step 6 – Receive and Process Sumsub Webhooks
Sumsub pushes real-time event notifications to your webhook endpoint for both transaction-monitoring decisions and Travel Rule status changes. Verify every incoming request with the HMAC-SHA256 signature in the X-Payload-Digest header before acting on the payload.
// src/webhook.js
import express from 'express';
import crypto from 'crypto';
import { pool } from './db.js';
import 'dotenv/config';
const router = express.Router();
const WEBHOOK_SECRET = process.env.WEBHOOK_SECRET;
function verifySignature(rawBody, digestHeader) {
const expected = crypto
.createHmac('sha256', WEBHOOK_SECRET)
.update(rawBody)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(expected, 'hex'),
Buffer.from(digestHeader, 'hex')
);
}
router.post('/webhook/sumsub', express.raw({ type: 'application/json' }), async (req, res) => {
const digest = req.headers['x-payload-digest'];
if (!digest || !verifySignature(req.body, digest)) {
return res.status(401).json({ error: 'Invalid signature' });
}
const event = JSON.parse(req.body.toString());
res.status(200).json({ ok: true }); // Acknowledge immediately
try {
await handleEvent(event);
} catch (err) {
console.error('Webhook handler error:', err.message);
}
});
async function handleEvent(event) {
const { type, payload } = event;
if (type === 'transaction_monitoring_completed') {
const { externalTransactionId, status, riskScore, alerts } = payload;
await pool.query(
`UPDATE aml_transactions
SET status = $1, updated_at = NOW()
WHERE tx_id = $2`,
[status, externalTransactionId]
);
if (status === 'RED' || riskScore > 75) {
await flagForManualReview(externalTransactionId, alerts);
}
}
if (type === 'travel_rule_check_updated') {
const { transactionId, travelRuleStatus } = payload;
await pool.query(
`UPDATE aml_transactions
SET travel_rule_status = $1, updated_at = NOW()
WHERE sumsub_tx_id = $2`,
[travelRuleStatus, transactionId]
);
if (travelRuleStatus === 'REJECTED') {
await blockTransaction(transactionId);
}
}
}
async function flagForManualReview(txId, alerts) {
await pool.query(
`UPDATE aml_transactions SET status = 'MANUAL_REVIEW', updated_at = NOW()
WHERE tx_id = $1`,
[txId]
);
// Insert into compliance queue – your internal task system here
console.log(`[COMPLIANCE] TX ${txId} flagged:`, alerts.map(a => a.ruleCode).join(', '));
}
async function blockTransaction(sumsubTxId) {
await pool.query(
`UPDATE aml_transactions SET status = 'BLOCKED', updated_at = NOW()
WHERE sumsub_tx_id = $1`,
[sumsubTxId]
);
}
export default router;
Pitfall: Do not pass the parsed JSON object to the HMAC function. Sumsub signs the raw bytes of the body. If you parse first and re-serialize, byte-level differences (key ordering, whitespace) will break signature verification 100% of the time. Use express.raw(), not express.json(), and parse manually after verification.
Webhook Event Types to Subscribe
In Dashboard → Developers → Webhooks, enable the following event types for a full AML pipeline: transaction_monitoring_completed, travel_rule_check_updated, applicant_reviewed, applicant_action_pending. Omitting applicant_reviewed means you won’t know when a manual reviewer clears a blocked transaction.
Step 7 – Screen Against Sanctions Lists and Adverse Media
The Travel Rule check automatically screens both the originator and beneficiary against Sumsub’s consolidated watchlist database, which includes OFAC SDN, UN Consolidated, HM Treasury, EU Consolidated, DFAT (Australia), and DGTF (France). For iGaming operators with licence conditions in multiple jurisdictions, this single API call replaces what previously required separate integration with 4–6 list providers. For broader market context, see our coverage of best sweepstakes casino sites.
// src/screenCounterparty.js
import { sumsubRequest } from './sumsubClient.js';
export async function screenCounterparty({ name, dob, walletAddress }) {
// Sumsub auto-screens as part of Travel Rule check, but you can also
// trigger a standalone AML screening for pre-transaction checks
const result = await sumsubRequest('POST', '/resources/checks/aml', {
info: {
fullName: name,
dateOfBirth: dob,
},
cryptoWallet: walletAddress,
types: ['SANCTIONS', 'PEP', 'ADVERSE_MEDIA', 'CRYPTO_SANCTIONS'],
});
return {
hasSanctionsMatch: result.items?.some(i => i.type === 'SANCTIONS' && i.status === 'MATCH'),
hasPepMatch: result.items?.some(i => i.type === 'PEP' && i.status === 'MATCH'),
cryptoRiskScore: result.cryptoWalletInfo?.riskScore,
matchDetails: result.items?.filter(i => i.status === 'MATCH'),
};
}
The cryptoWalletInfo.riskScore field uses Sumsub’s wallet-scoring mechanism: a 0–100 score where 0 is clean and 100 means the wallet has direct exposure to known illicit activity. Sumsub supports 8,000+ virtual assets for wallet scoring as of 2026. Set an automated rejection threshold in Dashboard → Rules → Crypto Wallet Score (typical iGaming operators use 60 as their block threshold).
For operators choosing between identity verification providers, the Sumsub vs Onfido iGaming KYC comparison breaks down cost and coverage differences across 40+ parameters–Sumsub charges approximately $1.35 per KYC check vs Onfido’s $3.00 at mid-tier volumes.
Handling PEP Matches in iGaming
A PEP match does not automatically mean block; it triggers enhanced due diligence (EDD). Your compliance workflow should prompt a manual reviewer to assess the risk level, collect source-of-funds documentation, and either approve with ongoing monitoring or reject with a Suspicious Activity Report. Sumsub’s Case Management system can automate the document-request step.
Step 8 – Generate goAML FIU Reports Automatically
When a manual reviewer decides to file a Suspicious Activity Report, Sumsub can generate the report in goAML XML format–the standard required by UNODC’s goAML system used by most national FIUs. The 2026 version of Sumsub’s reporting module supports goAML 4.x schema and auto-populates fields from the case record.
// src/generateSAR.js
import { sumsubRequest } from './sumsubClient.js';
export async function generateSARReport(caseId) {
// Trigger report generation
const report = await sumsubRequest(
'POST',
`/resources/cases/${caseId}/reports`,
{
type: 'SAR',
format: 'GOAML',
reportingEntityId: 'YOUR_FIU_ENTITY_ID',
}
);
// Poll until ready (usually < 30 seconds)
let status = report.status;
let reportId = report.id;
while (status === 'GENERATING') {
await new Promise(r => setTimeout(r, 5000));
const statusCheck = await sumsubRequest('GET', `/resources/reports/${reportId}`);
status = statusCheck.status;
}
if (status !== 'READY') throw new Error(`Report generation failed: ${status}`);
// Download the XML
const xml = await sumsubRequest('GET', `/resources/reports/${reportId}/download`);
return xml;
}
Expected output: An XML file conforming to goAML 4.x schema, pre-populated with transaction details, entity names, risk indicators, and your reporting entity reference. The Sumsub dashboard audit trail records the generation event with a timestamp, satisfying the “clear audit trail for regulators” requirement cited in the FCA’s Money Laundering Regulations.
Pitfall: Missing Reporting Entity Registration
The reportingEntityId must match the entity registered with your national FIU in the goAML portal. If you use the wrong ID, the FIU’s ingestion pipeline will silently reject the report. Confirm your entity ID in the FIU’s online portal before your first production SAR.
Step 9 – Handle the Sunrise Problem for Partially Compliant Networks
The “Sunrise issue” occurs when a sending VASP is Travel Rule compliant but the receiving VASP is not–or vice versa–because Travel Rule adoption varies by jurisdiction. Sumsub’s protocol auto-selection handles this gracefully:
- TRP attempt: Sumsub checks whether the counterparty VASP is registered in the TRP directory.
- GTR attempt: If TRP fails, Sumsub queries the Global Travel Rule directory.
- Sumsub network: If the counterparty is a Sumsub customer, the exchange happens via Sumsub’s proprietary secure channel.
- Email fallback: If all digital channels fail, Sumsub sends an encrypted email to the counterparty compliance team and records the attempt in the audit trail.
- Unhosted wallet path: If wallet attribution returns no VASP match, the unhosted-wallet EDD flow activates.
For iGaming operators operating in jurisdictions with full Travel Rule enforcement (EU MiCA TFR, UK, Singapore), you must record both the outbound data transmission attempt and the outcome (received, bounced, email sent). Sumsub’s audit log captures all four steps automatically.
Step 10 – Implement Idempotent Transaction Processing
Cryptocurrency transactions can arrive at your platform multiple times–through webhook retries, blockchain re-orgs, or duplicate API calls from your front end. An idempotent processing layer ensures you never submit the same transaction to Sumsub twice (which would create duplicate AML alerts) and never credit a player twice.
// src/idempotentProcessor.js
import { pool } from './db.js';
import { submitTransaction } from './submitTransaction.js';
import { triggerTravelRule } from './triggerTravelRule.js';
export async function processIncomingDeposit(depositEvent) {
const { txHash, playerId, amountUsd, asset, walletAddress } = depositEvent;
// Idempotency check: has this tx hash been processed?
const { rows } = await pool.query(
'SELECT tx_id, status FROM aml_transactions WHERE wallet_address = $1 AND sumsub_tx_id IS NOT NULL',
[txHash]
);
if (rows.length > 0) {
console.log(`[IDEMPOTENT] TX ${txHash} already processed as ${rows[0].tx_id}`);
return { skipped: true, txId: rows[0].tx_id };
}
// Begin DB transaction for atomicity
const client = await pool.connect();
try {
await client.query('BEGIN');
const applicantId = await ensureApplicant(playerId);
const { txId, sumsubTxId } = await submitTransaction({
playerId, sumsubApplicantId: applicantId,
direction: 'deposit', amountUsd, asset, walletAddress, txHash,
});
// Trigger Travel Rule if above jurisdiction threshold
if (amountUsd >= 1000) {
const playerInfo = await getPlayerInfo(playerId);
await triggerTravelRule({
sumsubTxId,
sumsubApplicantId: applicantId,
counterpartyVaspDid: null, // Let Sumsub attribute
counterpartyWallet: walletAddress,
originatorInfo: playerInfo,
beneficiaryInfo: { name: 'Platform', accountNumber: 'hot_wallet_1' },
});
}
await client.query('COMMIT');
return { txId, sumsubTxId };
} catch (err) {
await client.query('ROLLBACK');
throw err;
} finally {
client.release();
}
}
Pitfall: Submitting Transactions Before KYC Is Complete
Sumsub requires an applicant to exist before you can link a transaction to it. If a player deposits before completing KYC, create a provisional applicant record first. Sumsub will monitor the transaction against the basic applicant profile and then re-evaluate once KYC verification completes and richer data is available. Never skip the applicant creation step even if KYC is incomplete. For broader market context, see our coverage of leading online casino sites.
Step 11 – Test the Full Pipeline in Sandbox
Sumsub provides a full sandbox environment at https://api.sumsub.com with test credentials that simulate all alert scenarios. Use the following test wallet addresses to trigger specific rules:
| Test Wallet Address | Simulated Outcome | Travel Rule Result |
|---|---|---|
0xSANCTIONED_TEST_1 |
OFAC SDN match → Block | REJECTED |
0xMIXER_TEST_1 |
Crypto mixer exposure → Alert | MANUAL_REVIEW |
0xCLEAN_TEST_1 |
Risk score 0 → Pass | ACCEPTED |
0xPEP_TEST_1 |
PEP match → EDD required | PENDING_EDD |
0xUNHOSTED_TEST_1 |
No VASP attribution → Unhosted flow | PENDING_POW |
Run a full end-to-end test by submitting a transaction with each test wallet, verifying that your webhook handler correctly updates the aml_transactions table, and confirming that the manual-review queue fires for RED and MANUAL_REVIEW outcomes.
# Quick smoke test from CLI
node -e "
const { processIncomingDeposit } = require('./src/idempotentProcessor.js');
processIncomingDeposit({
txHash: '0xCLEAN_TEST_1',
playerId: 99999,
amountUsd: 1500,
asset: 'ETH',
walletAddress: '0xCLEAN_TEST_1'
}).then(console.log).catch(console.error);
"
Dry-Run Verification Checklist
- All six test wallet scenarios return the correct
travel_rule_statusinaml_transactions - Duplicate submissions of the same
txHashare silently skipped by the idempotency guard - Transactions below $1,000 do not trigger the Travel Rule branch
- Webhook signature verification rejects payloads with a tampered body
- goAML report generation completes within 60 seconds for a test case
Step 12 – Advanced Configuration and Performance Tuning
Once your integration is live, these advanced settings improve both compliance coverage and system performance.
Risk-Segmented Monitoring Profiles
Sumsub supports multiple monitoring profiles per applicant. Create a HIGH_RISK profile for players with prior SAR history or PEP status, and a STANDARD profile for regular players. Assign profiles via the monitoringLevel field on the applicant update endpoint. High-risk profiles evaluate every transaction in real time; standard profiles batch low-value transactions overnight.
// Elevate a player to high-risk monitoring
await sumsubRequest('PATCH', `/resources/applicants/${sumsubApplicantId}/info`, {
monitoringLevel: 'HIGH_RISK',
monitoringReason: 'PEP_MATCH',
});
Custom Rule Builder via API
Beyond the dashboard, you can create and update rules programmatically–useful for operators who generate rule configurations from a compliance management system. The AI-driven rule builder accepts natural-language conditions and compiles them to rule expressions server-side, eliminating the need for custom rule DSL expertise.
await sumsubRequest('POST', '/resources/tm/rules', {
name: 'iGaming: rapid deposit-withdrawal pattern',
description: 'Flag withdrawals within 2h of deposits over $2000 with no gameplay events',
condition: {
type: 'SEQUENCE',
events: [
{ eventType: 'DEPOSIT', minAmount: 2000, window: '2h' },
{ eventType: 'WITHDRAWAL', within: '2h' },
],
excludeGameplayEvents: true,
},
action: { type: 'ALERT', severity: 'HIGH' },
dryRun: true,
});
Performance: Connection Pooling and Rate Limits
Sumsub enforces per-app-token rate limits (exact values are communicated in your contract; typical production tiers are 100 requests/second). For high-throughput operators–processing thousands of deposits per hour–use a pg Pool with a connection limit matching your database’s max_connections setting, and queue Sumsub API calls through a token-bucket rate limiter to avoid 429 responses during traffic spikes.
Common Pitfalls and Troubleshooting
Below are the eight most frequent integration failures, their root causes, and exact fixes.
Pitfall 1: HTTP 401 on Every Request
Root cause: The App Token doesn’t have travel-rule:write scope, or the HMAC signature is being computed over a URL-decoded path rather than the raw path string. Fix: In Dashboard → Developers → App Tokens, re-create the token with the full required scope set. Ensure the path argument to sign() is the raw path including query string exactly as it appears in the request.
Pitfall 2: HTTP 422 “Missing originator fields”
Root cause: You’re triggering the full Travel Rule check (amount ≥ $1,000) without providing birthDate, address, or nationalIdentityNumber. Fix: Gate the Travel Rule call on both the amount threshold and whether the player has completed enhanced KYC. If KYC is incomplete, hold the withdrawal and trigger a KYC prompt first.
Pitfall 3: Webhook Delivery Failures Causing Stale PENDING Status
Root cause: Your webhook endpoint returned a non-2xx response and Sumsub stopped retrying after 72 hours. Fix: Always return HTTP 200 immediately, then process asynchronously. Add a cron job that polls /resources/transactions/{id} for any record stuck in PENDING for more than 30 minutes.
Pitfall 4: Duplicate Sumsub Applicants per Player
Root cause: Race condition in concurrent deposit events creating the applicant twice. Fix: Use a database-level unique constraint on player_kyc.player_id and wrap the ensureApplicant call in a Postgres advisory lock: SELECT pg_advisory_xact_lock(player_id).
Pitfall 5: Clock Skew Signature Rejection (as noted above)
Fix: Implement NTP synchronisation (chrony or systemd-timesyncd) and add a health-check that alerts your ops team if server clock drifts more than 10 seconds.
Pitfall 6: VASP Attribution Timing Out
Root cause: Sumsub’s VASP directory lookup can take up to 10 seconds for wallets on less-common chains. If your withdrawal flow times out before attribution completes, the Travel Rule check is never triggered. Fix: Decouple withdrawal execution from Travel Rule triggering. Release the funds only after the Travel Rule status resolves to ACCEPTED or MANUAL_REVIEW (with internal approval).
Pitfall 7: goAML Report Schema Validation Errors
Root cause: The reportingEntityId format differs between FIUs. UK NCA goAML uses a numeric ID; AUSTRAC uses an alphanumeric reference. Fix: Maintain a jurisdiction → entity ID mapping in your environment config and pass the correct value based on the player’s jurisdiction.
Pitfall 8: Unhosted Wallet Flow Blocking All Crypto Withdrawals
Root cause: VASP attribution is only as good as the VASP directory coverage. Wallets on newer chains (Solana, Aptos) may not resolve even for exchange-hosted wallets. Fix: For chains with low directory coverage, implement your own VASP lookup (most major exchanges publish their withdrawal address ranges) and pass the resolved counterpartyVaspDid explicitly rather than leaving it null.
Beyond Standard Coverage: What Competitors Miss
Reviewing Sumsub’s own product pages, AML Watcher’s Travel Rule guide, and the InnovateFinance announcements, several important implementation realities go unaddressed:
- iGaming-specific rule bundles: Sumsub’s 300+ pre-built rules include a dedicated iGaming bundle covering bonus-abuse structuring, rapid deposit-withdrawal patterns, and gameplay-event correlation. None of the competing guides cover how to activate or tune these rules for casino/sportsbook scenarios.
- The Sunrise problem in practice: Competitors describe the Sunrise issue in theoretical terms but don’t show the protocol fallback chain or how to audit which path was taken for each transaction. Sumsub’s audit log exposes a
protocolUsedfield (TRP / GTR / SUMSUB / EMAIL) on every Travel Rule check record–pull this to build a compliance dashboard showing your VASP ecosystem coverage rate. - Idempotent processing under blockchain re-org: No existing guide covers the edge case where a deposit transaction hash changes after a short-range blockchain re-org (common on faster chains). The idempotency pattern using the DB tx hash as the dedup key, combined with a re-org detection job, is essential for production systems.
- goAML FIU report generation via API: Most guides describe the goAML standard abstractly. The programmatic generation path via Sumsub’s Cases API, including the polling loop and XML download, is not documented in any competitor article.
- Multi-jurisdiction threshold routing: An iGaming platform serving EU, UK, and Australian players simultaneously must apply €0 (MiCA), £0 (FCA), and AUD 1,000 thresholds respectively to the same codebase. The conditional Travel Rule trigger pattern shown in Step 10 handles this by reading the player’s jurisdiction from your player record rather than applying a single global threshold.
Complete Project Example: Express Server with Full AML Pipeline
The following is the entry point tying all the pieces together into a deployable Express application. Copy this alongside the module files from Steps 1–12 for a working foundation.
// src/server.js
import express from 'express';
import 'dotenv/config';
import webhookRouter from './webhook.js';
import { processIncomingDeposit } from './idempotentProcessor.js';
import { generateSARReport } from './generateSAR.js';
import { pool } from './db.js';
const app = express();
app.use(express.json());
app.use(webhookRouter);
// Endpoint called by your blockchain monitoring service when a deposit confirms
app.post('/api/deposit-confirmed', async (req, res) => {
try {
const result = await processIncomingDeposit(req.body);
res.json(result);
} catch (err) {
console.error(err);
res.status(500).json({ error: err.message });
}
});
// Compliance team endpoint to generate a SAR report
app.post('/api/compliance/sar/:caseId', async (req, res) => {
try {
const xml = await generateSARReport(req.params.caseId);
res.set('Content-Type', 'application/xml');
res.send(xml);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Health + pending transaction audit
app.get('/api/health', async (req, res) => {
const { rows } = await pool.query(
`SELECT status, COUNT(*) FROM aml_transactions
WHERE created_at > NOW() - INTERVAL '1 hour'
GROUP BY status`
);
res.json({ status: 'ok', txSummary: rows });
});
app.listen(process.env.PORT, () =>
console.log(`AML server listening on port ${process.env.PORT}`)
);
For iGaming operators also implementing sportsbook KYC flows, the Veriff SDK sportsbook KYC integration guide covers liveness detection and document verification patterns that complement the Sumsub Travel Rule pipeline described here–particularly useful when your compliance stack spans multiple identity verification vendors.
Deployment and Ongoing Compliance Checklist
Before going live, verify each item with your compliance officer and legal team:
- Travel Rule module is enabled on your Sumsub account and test credentials are replaced with production tokens
- Webhook endpoint is HTTPS with a valid TLS certificate; signature verification is active
- All jurisdiction-specific thresholds are configured in the transaction-processing logic (€0 MiCA, £0 FCA, AUD 1,000 AUSTRAC, $1,000 FATF baseline)
- goAML reporting entity IDs are registered with each applicable national FIU
- Dry-run rules are converted to live after 72-hour validation period
- The 5 test wallet scenarios all resolve correctly in production
- The idempotency guard is tested under concurrent load (use Artillery or k6 to simulate 50 simultaneous deposits of the same txHash)
- A manual-review queue is staffed and accessible from the Sumsub Case Management dashboard
- AUSTRAC deferral deadline (1 July 2026) is calendared for any Australian VA obligations not yet in scope
- UK FCA draft SI changes (26 March 2026 consultation) are tracked for final rule implementation
FAQ
- Does Sumsub Travel Rule work for NFT marketplaces as well as crypto exchanges?
- Yes. Sumsub’s 2026 Travel Rule implementation covers NFT transfers where the NFT has financial value. The determination of whether a specific NFT transfer is “virtual asset” under FATF guidance is jurisdiction-specific–most regulatory interpretations require the NFT to be fungible or used as a financial instrument. Consult your legal team for jurisdiction-specific analysis before applying Travel Rule controls to all NFT transfers.
- What happens if the counterparty VASP never responds to the TRP handshake?
- Sumsub attempts TRP, then GTR, then its own network, then sends an encrypted email fallback. If no response is received within your configured timeout (default 48 hours), the Travel Rule check status is set to COUNTERPARTY_UNRESPONSIVE. You then decide whether to block, hold, or proceed with a documented risk decision. The audit trail records every attempt.
- Is Sumsub’s AML transaction monitoring GDPR-compliant for EU players?
- Sumsub processes personal data under a legitimate interest / legal obligation basis (GDPR Article 6(1)(c) and (f)) for AML purposes. Data submitted for Travel Rule checks is stored for the minimum period required by the applicable jurisdiction (typically 5 years under EU AMLD). Review Sumsub’s DPA (Data Processing Agreement) and ensure your own privacy policy covers Travel Rule data sharing with VASPs.
- What’s the difference between a SAR and a CTR (Currency Transaction Report) in Sumsub’s reporting module?
- A SAR (Suspicious Activity Report) is filed when you detect suspicious behaviour regardless of amount. A CTR is filed automatically when a cash transaction exceeds a jurisdiction-specific threshold (e.g., $10,000 in the US). Sumsub’s reporting module generates both, but iGaming operators typically file SARs more frequently than CTRs. The goAML XML schema covers both report types.
- Can I use Sumsub Travel Rule without enabling KYC verification on the same account?
- Technically yes–the Travel Rule module can operate with a minimal applicant record (external user ID + name). But without KYC data, originator information fields for above-threshold transfers will be incomplete, making Travel Rule compliance impossible. You need at minimum a verified name, date of birth, and address for the Travel Rule to function for high-value transfers.
- How does Sumsub handle the EU MiCA Transfer of Funds Regulation zero-threshold rule?
- Under EU MiCA TFR (Transfer of Funds Regulation), all VA transfers–including those under €1,000–require originator and beneficiary data exchange. Set your EU jurisdiction threshold to €0 in the transaction processing code. Sumsub’s Travel Rule check accepts a zero threshold and will trigger for every transfer involving an EU-regulated VASP as originator or beneficiary.
- What monitoring does Sumsub provide for fiat payment methods alongside crypto?
- Sumsub Transaction Monitoring covers both crypto and fiat payment events. You submit fiat transactions using the same API endpoint with
cryptoInfoomitted andpaymentMethodset to values likeBANK_TRANSFER,CARD, orE_WALLET. The 300+ rule bundles apply to fiat transactions where the rule logic is currency-agnostic (velocity, structuring, geographic). Travel Rule checks apply only to VA transfers, not fiat. - How long does Travel Rule check resolution take, and can it block real-time withdrawals?
- Sumsub resolves most Travel Rule checks via TRP or its own network in under 5 seconds when the counterparty VASP is on a shared protocol network. Email fallback checks take up to 48 hours. For real-time withdrawals, implement an asynchronous release pattern: queue the withdrawal, submit the Travel Rule check, and release funds only once the status resolves to ACCEPTED. This adds a brief hold for unresolved cases but eliminates the risk of releasing funds before compliance is confirmed.







