If your iGaming or sports betting SaaS platform routes player transactions through a cloud automation vendor, that vendor's execution log — not your compliance team's spreadsheet — is where the timestamp of a $10,000 same-day deposit aggregate actually lives. Under the Bank Secrecy Act (31 CFR §1021.313), that timestamp starts a 15-day Currency Transaction Report clock. Miss it because the aggregation logic was buried in a third-party workflow tool you don't operate, and the missed filing is on you, not the vendor.
Gaming compliance software vendors face a stack of obligations that most generic fintech automation content never touches: BSA/AML reporting specific to casinos and sportsbooks, self-exclusion list enforcement across states with zero tolerance for lag, and a patchwork of state gaming commission rules that differ by jurisdiction and change on their own schedules.
The Compliance Surface Area
1. BSA Suspicious Activity Reports — 30 Days from Detection
Under 31 CFR §1021.311, casinos and card clubs (and by extension, the platforms processing their transactions) must file a SAR within 30 days of detecting suspicious activity involving $5,000 or more. "Detection" is defined broadly — it includes automated pattern flags, not just manual review. If your platform's fraud engine flags a structuring pattern and nobody routes that flag into a tracked review queue, the 30-day clock is already running against evidence your own system generated.
2. BSA Currency Transaction Reports — 15 Days, Same-Day Aggregation
31 CFR §1021.313 requires a CTR for cash-equivalent transactions aggregating $10,000 or more in a single gaming day for a single patron. This is not a per-transaction check — it requires aggregating every deposit, buy-in, and cash-out event for the same player across the day. A platform that checks transactions individually instead of aggregating by player-per-day will systematically under-file CTRs without anyone noticing until an audit.
3. Self-Exclusion List Enforcement — Zero Tolerance, No Grace Period
Every regulated US gaming state maintains a self-exclusion list, and state gaming commissions treat any deposit or session activity from a self-excluded player as a strict-liability violation — there is no "we didn't sync the list yet" defense. Lists must be checked before every deposit, not on a nightly batch. A platform running self-exclusion checks on a delayed cron job is one missed sync away from a state enforcement action.
4. Multi-State Gaming License Renewal — Different Cadence Per State
Operators licensed in multiple states (NJ Division of Gaming Enforcement, PA Gaming Control Board, Michigan Gaming Control Board, Colorado Division of Gaming, Illinois Gaming Board, and others) face renewal cycles, reporting formats, and audit requirements that do not align across jurisdictions. Tracking eight state renewal calendars in a shared spreadsheet is how operators end up with a lapsed license in one state while fully compliant everywhere else.
5. Responsible Gambling Monitoring — Increasingly a Regulatory Requirement, Not Just a PR Program
States are moving from "nice to have" responsible gambling programs to mandated intervention triggers: rapid-deposit patterns, loss-chasing behavior (consecutive deposits immediately following losses), and extended session length now carry regulatory expectations for automated detection and intervention, with annual reporting requirements in several states.
Five n8n Automations for Gaming Compliance
Workflow 1: iGaming Operator Onboarding & Compliance Classifier
Classifies new operator accounts into tiers (sportsbook, online casino, DFS, poker, multi-state, offshore) and sets compliance flags — BSA/casino status, UIGEA payment-blocking exposure, multi-state license tracking, self-exclusion requirements, OFAC SDN screening — at the moment of account creation, not discovered later during an audit.
{
"name": "iGaming Operator Onboarding & Compliance Classifier",
"nodes": [
{
"id": "1",
"name": "New Operator Account Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "igaming-onboarding",
"httpMethod": "POST"
}
},
{
"id": "2",
"name": "Classify Tier & Flags",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const d = $input.first().json;\nconst states = (d.licensed_states || '').split(',').map(s => s.trim()).filter(Boolean);\nlet tier = 'STARTUP_OPERATOR';\nif (states.length >= 8) tier = 'MULTI_STATE_OPERATOR';\nelse if (d.vertical === 'sportsbook') tier = 'SPORTSBOOK_SAAS';\nelse if (d.vertical === 'online_casino') tier = 'ONLINE_CASINO_SAAS';\nelse if (d.vertical === 'daily_fantasy') tier = 'DFS_SAAS';\nelse if (d.vertical === 'poker') tier = 'POKER_SAAS';\nelse if (d.offshore === 'true') tier = 'OFFSHORE_OPERATOR';\nconst flags = {\n bsa_msb_or_casino_status: ['MULTI_STATE_OPERATOR','ONLINE_CASINO_SAAS'].includes(tier),\n uigea_payment_blocking_subject: !d.offshore || d.offshore === 'false',\n multi_state_gaming_license: states.length > 1,\n self_exclusion_cross_check_required: true,\n responsible_gambling_monitor_required: ['ONLINE_CASINO_SAAS','SPORTSBOOK_SAAS','MULTI_STATE_OPERATOR'].includes(tier),\n ofac_sdn_screening_required: true\n};\nreturn [{json: {...d, tier, licensed_states: states, flags, onboarding_ts: new Date().toISOString()}}];"
}
},
{
"id": "3",
"name": "Day 0 Welcome Email",
"type": "n8n-nodes-base.gmail",
"parameters": {
"toList": "={{ $json.contact_email }}",
"subject": "=Welcome to FlowKit \u2014 your {{ $json.tier }} gaming compliance automation kit",
"message": "=Welcome to FlowKit.\n\nYour multi-state gaming compliance stack is ready. Based on your profile ({{ $json.tier }}, {{ $json.licensed_states.length }} licensed states):\n\n{{ $json.flags.bsa_msb_or_casino_status ? '\u2713 BSA Title 31 CFR 1021.311 SAR filing pipeline enabled (30-day clock)\\n' : '' }}{{ $json.flags.multi_state_gaming_license ? '\u2713 Multi-state gaming commission deadline tracker configured\\n' : '' }}\u2713 Self-exclusion cross-check wired into every deposit event\n\u2713 OFAC SDN screening on account creation\n\nOnboarding call: {{ $json.csm_calendly_link }}\n\n\u2014 FlowKit Team"
}
},
{
"id": "4",
"name": "Slack Compliance Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#gaming-onboarding",
"text": "=\ud83c\udfb0 New {{ $json.tier }}: {{ $json.company_name }} ({{ $json.licensed_states.length }} states)\nFlags: BSA={{ $json.flags.bsa_msb_or_casino_status }} | MultiState={{ $json.flags.multi_state_gaming_license }} | RG={{ $json.flags.responsible_gambling_monitor_required }}\nCSM: {{ $json.csm_name }}"
}
},
{
"id": "5",
"name": "Audit Log",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "igaming_onboarding_audit",
"columns": "company_name,tier,licensed_states,flags_json,onboarding_ts",
"values": "={{ $json.company_name }},={{ $json.tier }},={{ JSON.stringify($json.licensed_states) }},={{ JSON.stringify($json.flags) }},={{ $json.onboarding_ts }}"
}
}
],
"connections": {
"New Operator Account Webhook": {
"main": [
[
{
"node": "Classify Tier & Flags",
"type": "main",
"index": 0
}
]
]
},
"Classify Tier & Flags": {
"main": [
[
{
"node": "Day 0 Welcome Email",
"type": "main",
"index": 0
},
{
"node": "Slack Compliance Alert",
"type": "main",
"index": 0
},
{
"node": "Audit Log",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 2: Multi-State Gaming Compliance Deadline Tracker
Runs daily, classifies every tracked deadline (SAR 30-day, CTR 15-day, state license renewals, self-exclusion list refresh, biennial AML independent testing, OFAC list sync, geolocation vendor cert renewal) by urgency, and routes CRITICAL/OVERDUE items to a dedicated Slack channel separate from routine WARNING items.
{
"name": "Multi-State Gaming Compliance Deadline Tracker",
"nodes": [
{
"id": "1",
"name": "Daily 7AM UTC",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * *"
}
]
}
}
},
{
"id": "2",
"name": "Fetch Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "read",
"sheetId": "YOUR_SHEET_ID",
"range": "Gaming_Compliance_Deadlines!A:J"
}
},
{
"id": "3",
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const items = $input.all();\nconst now = new Date();\nconst DEADLINE_MAP = {\n 'BSA_SAR_30DAY': 'BSA 31 CFR 1021.311 \u2014 Suspicious Activity Report within 30 days of detection ($5,000+ threshold)',\n 'BSA_CTR_15DAY': 'BSA 31 CFR 1021.313 \u2014 Currency Transaction Report within 15 days ($10,000+ same-day aggregate)',\n 'STATE_LICENSE_RENEWAL': 'State gaming commission license renewal (NJ DGE / PA PGCB / MI MGCB / CO DOR / IL Gaming Board \u2014 cadence varies by state)',\n 'SELF_EXCLUSION_LIST_REFRESH': 'State self-exclusion list sync \u2014 most states require daily or near-real-time refresh before allowing deposits',\n 'RESPONSIBLE_GAMBLING_ANNUAL_REPORT': 'State-mandated annual responsible gambling program report',\n 'AML_INDEPENDENT_TESTING_BIENNIAL': 'BSA 31 CFR 1021.210 \u2014 independent AML program testing every 2 years',\n 'OFAC_SDN_LIST_SYNC': 'OFAC SDN list sync \u2014 Treasury updates list on rolling basis, must re-screen active accounts',\n 'GEOLOCATION_VENDOR_CERT_RENEWAL': 'State geolocation vendor certification renewal (required per licensed state)'\n};\nreturn items.map(i => {\n const d = i.json;\n const due = new Date(d.due_date);\n const daysLeft = Math.ceil((due - now) / 86400000);\n let urgency = 'NORMAL';\n if (daysLeft < 0) urgency = 'OVERDUE';\n else if (daysLeft <= 3) urgency = 'CRITICAL';\n else if (daysLeft <= 14) urgency = 'WARNING';\n return {json: {...d, description: DEADLINE_MAP[d.deadline_type] || d.deadline_type, days_left: daysLeft, urgency}};\n});"
}
},
{
"id": "4",
"name": "Route by Urgency",
"type": "n8n-nodes-base.switch",
"parameters": {
"rules": {
"values": [
{
"outputKey": "critical_overdue",
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.urgency }}",
"rightValue": "CRITICAL",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
},
{
"outputKey": "warning",
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.urgency }}",
"rightValue": "WARNING",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
]
}
}
},
{
"id": "5",
"name": "Slack + Email Critical",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#gaming-compliance-critical",
"text": "=\ud83d\udea8 {{ $json.description }} \u2014 {{ $json.days_left }} days ({{ $json.urgency }})"
}
},
{
"id": "6",
"name": "Slack Warning Queue",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#gaming-compliance-queue",
"text": "=\u26a0\ufe0f {{ $json.description }} \u2014 {{ $json.days_left }} days"
}
}
],
"connections": {
"Daily 7AM UTC": {
"main": [
[
{
"node": "Fetch Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Fetch Deadlines": {
"main": [
[
{
"node": "Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify Urgency": {
"main": [
[
{
"node": "Route by Urgency",
"type": "main",
"index": 0
}
]
]
},
"Route by Urgency": {
"main": [
[
{
"node": "Slack + Email Critical",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack Warning Queue",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 3: Real-Time BSA/CTR/SAR Transaction Monitor
Runs every 5 minutes, aggregates each player's same-day transaction total (the aggregation step most platforms get wrong), and flags CTR-required accounts at the $10,000 same-day threshold and SAR-review candidates combining dollar threshold with a risk score — writing every flag to an audit-tracked review queue with the applicable deadline attached.
{
"name": "Real-Time BSA/CTR/SAR Transaction Monitor",
"nodes": [
{
"id": "1",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/5 * * * *"
}
]
}
}
},
{
"id": "2",
"name": "Fetch Recent Transactions",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT * FROM player_transactions WHERE processed_at > NOW() - INTERVAL '5 minutes'"
}
},
{
"id": "3",
"name": "Aggregate & Flag",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const items = $input.all();\nconst staticData = $getWorkflowStaticData('node');\nstaticData.dailyTotals = staticData.dailyTotals || {};\nconst today = new Date().toISOString().slice(0,10);\nstaticData.dailyTotals[today] = staticData.dailyTotals[today] || {};\n\nconst flagged = [];\nfor (const i of items) {\n const d = i.json;\n const key = d.player_id;\n staticData.dailyTotals[today][key] = (staticData.dailyTotals[today][key] || 0) + parseFloat(d.amount_usd);\n const dayTotal = staticData.dailyTotals[today][key];\n\n if (dayTotal >= 10000) flagged.push({...d, flag: 'BSA_CTR_REQUIRED', reason: `Same-day aggregate $${dayTotal.toFixed(2)} triggers 31 CFR 1021.313 CTR`, deadline_days: 15});\n if (d.pattern_score >= 0.7) flagged.push({...d, flag: 'STRUCTURING_SUSPICIOUS', reason: 'Pattern consistent with structuring (multiple sub-threshold deposits)', deadline_days: 30});\n if (dayTotal >= 5000 && d.risk_score >= 0.6) flagged.push({...d, flag: 'SAR_REVIEW_REQUIRED', reason: `31 CFR 1021.311 \u2014 $${dayTotal.toFixed(2)} + elevated risk score`, deadline_days: 30});\n}\nreturn flagged.map(f => ({json: f}));"
}
},
{
"id": "4",
"name": "Insert to AML Review Queue",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "aml_review_queue",
"columns": "player_id,flag,reason,deadline_days,flagged_at",
"values": "={{ $json.player_id }},={{ $json.flag }},={{ $json.reason }},={{ $json.deadline_days }},={{ new Date().toISOString() }}"
}
},
{
"id": "5",
"name": "Slack AML Team",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#aml-review",
"text": "=\ud83d\udd0d {{ $json.flag }}: player {{ $json.player_id }} \u2014 {{ $json.reason }} (deadline: {{ $json.deadline_days }} days)"
}
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Fetch Recent Transactions",
"type": "main",
"index": 0
}
]
]
},
"Fetch Recent Transactions": {
"main": [
[
{
"node": "Aggregate & Flag",
"type": "main",
"index": 0
}
]
]
},
"Aggregate & Flag": {
"main": [
[
{
"node": "Insert to AML Review Queue",
"type": "main",
"index": 0
},
{
"node": "Slack AML Team",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 4: Self-Exclusion & Responsible Gambling Intervention Pipeline
Fires on every deposit and session event. Checks the self-exclusion list first (immediate block, no exceptions), then evaluates rapid-deposit patterns, too-soon limit increase requests, loss-chasing sequences, and extended sessions — routing IMMEDIATE-severity events to an enforcement log and Slack alert in the same execution.
{
"name": "Self-Exclusion & Responsible Gambling Intervention Pipeline",
"nodes": [
{
"id": "1",
"name": "Deposit/Session Event Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "player-activity",
"httpMethod": "POST"
}
},
{
"id": "2",
"name": "Classify Event",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const d = $input.first().json;\nconst staticData = $getWorkflowStaticData('node');\nstaticData.playerHistory = staticData.playerHistory || {};\nconst h = staticData.playerHistory[d.player_id] = staticData.playerHistory[d.player_id] || {deposits: [], losses: 0, sessionMinutes: 0};\n\nconst events = [];\nif (d.self_exclusion_list_hit === true) {\n events.push({type: 'SELF_EXCLUSION_BLOCK', severity: 'IMMEDIATE', action: 'block_deposit_and_lock_account'});\n}\nif (d.event_type === 'deposit') {\n h.deposits.push({amount: parseFloat(d.amount_usd), ts: d.timestamp});\n const last24h = h.deposits.filter(x => (new Date(d.timestamp) - new Date(x.ts)) < 86400000);\n if (last24h.length >= 5) events.push({type: 'RAPID_DEPOSIT_PATTERN', severity: 'HIGH', action: 'trigger_cooldown_prompt'});\n if (d.limit_increase_requested === true && d.hours_since_last_limit_change < 24) {\n events.push({type: 'LIMIT_INCREASE_TOO_SOON', severity: 'MEDIUM', action: 'apply_24h_cooling_period'});\n }\n}\nif (d.event_type === 'loss') {\n h.losses += parseFloat(d.amount_usd);\n if (d.consecutive_loss_chasing_deposits >= 3) events.push({type: 'LOSS_CHASING_DETECTED', severity: 'HIGH', action: 'mandatory_reality_check_popup'});\n}\nif (d.event_type === 'session_end' && d.session_minutes > 360) {\n events.push({type: 'EXTENDED_SESSION', severity: 'MEDIUM', action: 'prompt_break_reminder'});\n}\nreturn events.map(e => ({json: {...d, ...e}}));"
}
},
{
"id": "3",
"name": "Route by Severity",
"type": "n8n-nodes-base.switch",
"parameters": {
"rules": {
"values": [
{
"outputKey": "immediate",
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.severity }}",
"rightValue": "IMMEDIATE",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
]
}
}
},
{
"id": "4",
"name": "Block Account + Compliance Log",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "self_exclusion_enforcement_log",
"columns": "player_id,type,action,ts",
"values": "={{ $json.player_id }},={{ $json.type }},={{ $json.action }},={{ new Date().toISOString() }}"
}
},
{
"id": "5",
"name": "Slack Compliance Team",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#responsible-gambling",
"text": "=\u26a0\ufe0f {{ $json.type }} ({{ $json.severity }}): player {{ $json.player_id }} \u2014 action: {{ $json.action }}"
}
}
],
"connections": {
"Deposit/Session Event Webhook": {
"main": [
[
{
"node": "Classify Event",
"type": "main",
"index": 0
}
]
]
},
"Classify Event": {
"main": [
[
{
"node": "Route by Severity",
"type": "main",
"index": 0
}
]
]
},
"Route by Severity": {
"main": [
[
{
"node": "Block Account + Compliance Log",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 5: Weekly Multi-State Gaming Compliance KPI Dashboard
A Monday report to the compliance officer covering CTRs filed, SARs pending review, and self-exclusion blocks enforced over the previous 7 days — the numbers a state gaming commission audit will ask for first.
{
"name": "Weekly Multi-State Gaming Compliance KPI Dashboard",
"nodes": [
{
"id": "1",
"name": "Monday 7AM UTC",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1"
}
]
}
}
},
{
"id": "2",
"name": "Fetch KPIs",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) FILTER (WHERE flag='BSA_CTR_REQUIRED') as ctrs_filed, COUNT(*) FILTER (WHERE flag='SAR_REVIEW_REQUIRED') as sars_pending, COUNT(*) as total_aml_events FROM aml_review_queue WHERE flagged_at > NOW() - INTERVAL '7 days'"
}
},
{
"id": "3",
"name": "Fetch Self-Exclusion Blocks",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) as blocks FROM self_exclusion_enforcement_log WHERE ts > NOW() - INTERVAL '7 days'"
}
},
{
"id": "4",
"name": "Build HTML Report",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const kpi = $input.first().json;\nreturn [{json: {html: `<h2>Weekly Gaming Compliance KPIs</h2><p>CTRs filed: ${kpi.ctrs_filed}</p><p>SARs pending review: ${kpi.sars_pending}</p><p>Self-exclusion blocks enforced: ${kpi.blocks}</p>`}}];"
}
},
{
"id": "5",
"name": "Email Compliance Officer",
"type": "n8n-nodes-base.gmail",
"parameters": {
"toList": "compliance@company.com",
"subject": "Weekly Gaming Compliance KPI Report",
"message": "={{ $json.html }}"
}
}
],
"connections": {
"Monday 7AM UTC": {
"main": [
[
{
"node": "Fetch KPIs",
"type": "main",
"index": 0
},
{
"node": "Fetch Self-Exclusion Blocks",
"type": "main",
"index": 0
}
]
]
},
"Fetch KPIs": {
"main": [
[
{
"node": "Build HTML Report",
"type": "main",
"index": 0
}
]
]
},
"Build HTML Report": {
"main": [
[
{
"node": "Email Compliance Officer",
"type": "main",
"index": 0
}
]
]
}
}
}
Why Self-Hosted n8n Matters Here
1. Same-day transaction aggregation logic stays under your control. CTR compliance depends on correctly aggregating a player's transactions across a gaming day. That logic — and its audit trail — needs to live somewhere your compliance team can inspect and defend during an examination, not inside a black-box SaaS automation vendor's execution history.
2. Self-exclusion checks run at deposit-time, not on someone else's batch schedule. A cloud automation platform with rate limits or scheduled sync windows introduces exactly the kind of lag that turns a self-exclusion miss into a strict-liability violation. Self-hosted n8n lets you wire the check directly into the deposit path with no external dependency.
3. Multi-state deadline tracking is a real audit artifact. When a state gaming commission asks how you track license renewals across jurisdictions, "a shared spreadsheet someone updates manually" is a weaker answer than an automated, logged deadline tracker with a documented escalation path.
4. AML review queue history has to survive scrutiny. BSA independent testing (required every 2 years under 31 CFR §1021.210) will examine how SARs and CTRs were identified and processed. A Postgres-backed audit log under your infrastructure is easier to produce and defend than pulling execution history from a third-party vendor's retention window.
These workflows are available in the FlowKit n8n Template Store. Import-ready JSON — no vendor lock-in, runs on your own infrastructure.
Top comments (0)