If you're building WealthTech or asset management software, your platform touches some of the most regulated data in finance: client portfolios, investment strategies, retirement plan assets, and trade decision rationale. Most cloud automation platforms (Zapier, Make) were not designed for this compliance environment.
This guide covers 5 n8n automations purpose-built for WealthTech SaaS — with import-ready JSON you can deploy today.
Why WealthTech SaaS needs self-hosted automation
SEC §275.204-2 recordkeeping (5 years): Advisory records — emails, order rationale, compliance decisions — must be retained 5 years. Zapier's 30-day task log retention is a per-record violation at up to $10,000/day.
Reg BI §240.15l-1 best interest: Your platform's recommendation workflows are compliance evidence. If those workflows run through Zapier, the audit trail lives in Zapier's multi-tenant task log — not producible to SEC examiners in the format required.
DOL ERISA §404 fiduciary duty: If your platform handles retirement plan assets, any service provider touching plan data faces co-fiduciary exposure. Routing ERISA plan participant data through a cloud iPaaS without a specific sub-processor agreement is a Plan Asset Regulation gap.
MNPI isolation: Client portfolio positions, investment strategies, and pending trades are material nonpublic information. Zapier creates uncontrolled data copies that could create insider trading risk if accessed by non-credentialed staff.
Self-hosted n8n: Runs in your VPC, all workflow logic is git-versioned JSON (your compliance evidence package), all execution logs stay in your Postgres, zero third-party sub-processor exposure.
Workflow 1: RIA Client Onboarding & Reg BI Compliance Drip
When a new advisory client is added to your CRM or Google Sheet, this workflow classifies them by account type (INSTITUTIONAL_INVESTOR, HNWI_RETAIL, RETAIL_ADVISORY, RETIREMENT_PLAN_ERISA, FAMILY_OFFICE), applies the correct regulatory flags (Reg BI, ERISA, MiFID II, GDPR), and sends a compliance-aware 7-day onboarding sequence.
Reg BI angle: For retail clients, Day 0 email delivers Form CRS (client relationship summary) — SEC §204-3 requires delivery at the start of the advisory relationship. This workflow creates a timestamped audit trail that SEC examiners ask for.
ERISA angle: For retirement plan clients, Day 0 email includes ERISA §408(b)(2) fee disclosure notice. Your platform documents the service provider relationship from day one.
{
"name": "RIA Client Onboarding & Reg BI Compliance Drip",
"nodes": [
{
"id": "1",
"name": "New Client Added",
"type": "n8n-nodes-base.googleSheetsTrigger",
"parameters": {
"sheetId": "YOUR_SHEET_ID",
"range": "Clients!A:Z",
"event": "rowAdded"
},
"position": [
250,
300
]
},
{
"id": "2",
"name": "Classify Client",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const row = $input.first().json;\nconst typeMap = {\n INSTITUTIONAL_INVESTOR: { regBI: false, erisa: false, formCRS: false, mifid: false },\n HNWI_RETAIL: { regBI: true, erisa: false, formCRS: true, mifid: false },\n RETAIL_ADVISORY: { regBI: true, erisa: false, formCRS: true, mifid: false },\n RETIREMENT_PLAN_ERISA: { regBI: false, erisa: true, formCRS: false, mifid: false },\n FAMILY_OFFICE: { regBI: false, erisa: false, formCRS: false, mifid: false },\n};\nconst flags = typeMap[row.client_type] || typeMap.RETAIL_ADVISORY;\nconst gdpr = (row.country || '').match(/^(DE|FR|NL|BE|IT|GB|IE)$/) ? true : false;\nreturn [{ json: { ...row, ...flags, gdpr, classified_at: new Date().toISOString() } }];"
},
"position": [
450,
300
]
},
{
"id": "3",
"name": "Switch Tier",
"type": "n8n-nodes-base.switch",
"parameters": {
"dataPropertyName": "client_type",
"rules": {
"rules": [
{
"value": "INSTITUTIONAL_INVESTOR",
"output": 0
},
{
"value": "RETIREMENT_PLAN_ERISA",
"output": 1
},
{
"value": "RETAIL_ADVISORY",
"output": 2
},
{
"value": "HNWI_RETAIL",
"output": 2
},
{
"value": "FAMILY_OFFICE",
"output": 3
}
]
}
},
"position": [
650,
300
]
},
{
"id": "4",
"name": "Day 0 - Institutional Welcome",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.email }}",
"subject": "Welcome to [Platform] \u2014 Institutional Onboarding",
"message": "Dear {{ $json.name }},\n\nThank you for choosing [Platform]...\n\nYour dedicated relationship manager: {{ $json.rm_email }}"
},
"position": [
900,
150
]
},
{
"id": "5",
"name": "Day 0 - ERISA Welcome + BAA",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.email }}",
"subject": "Welcome \u2014 ERISA Retirement Plan Onboarding",
"message": "Dear {{ $json.plan_administrator }},\n\nWelcome to [Platform] for your retirement plan...\n\nNote: As a plan service provider, [Platform] operates as a service provider under ERISA \u00a7408(b)(2)..."
},
"position": [
900,
300
]
},
{
"id": "6",
"name": "Day 0 - Reg BI Form CRS Delivery",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.email }}",
"subject": "Important: Your Form CRS (Client Relationship Summary)",
"message": "Dear {{ $json.name }},\n\nPer SEC Rule \u00a7204-3, we are required to deliver our Form CRS at the beginning of our advisory relationship. Please find it attached.\n\nReg BI Best Interest Obligation: We act in your best interest and will document all recommendations.\n\nNext steps: {{ $json.onboarding_link }}"
},
"position": [
900,
450
]
},
{
"id": "7",
"name": "Wait 3 Days",
"type": "n8n-nodes-base.wait",
"parameters": {
"resume": "timeInterval",
"unit": "days",
"amount": 3
},
"position": [
1100,
300
]
},
{
"id": "8",
"name": "Day 3 - Platform Setup Guide",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.email }}",
"subject": "Setting Up Your [Platform] Dashboard \u2014 3 Quick Steps",
"message": "Day 3 platform setup guide email..."
},
"position": [
1300,
300
]
},
{
"id": "9",
"name": "Wait 4 Days",
"type": "n8n-nodes-base.wait",
"parameters": {
"resume": "timeInterval",
"unit": "days",
"amount": 4
},
"position": [
1500,
300
]
},
{
"id": "10",
"name": "Day 7 - Compliance Check-In",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.email }}",
"subject": "Week 1 Complete \u2014 Compliance Configuration Review",
"message": "Week 1 compliance check-in email with Form ADV review, Reg BI documentation setup confirmation..."
},
"position": [
1700,
300
]
},
{
"id": "11",
"name": "Log to Compliance Audit Sheet",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "append",
"sheetId": "YOUR_AUDIT_SHEET_ID",
"range": "OnboardingLog!A:Z",
"values": [
[
"={{ $json.client_id }}",
"={{ $json.client_type }}",
"={{ $json.email }}",
"={{ $json.regBI }}",
"={{ $json.erisa }}",
"={{ new Date().toISOString() }}",
"DRIP_INITIATED"
]
]
},
"position": [
1700,
500
]
}
],
"connections": {
"New Client Added": {
"main": [
[
{
"node": "Classify Client"
}
]
]
},
"Classify Client": {
"main": [
[
{
"node": "Switch Tier"
}
]
]
},
"Switch Tier": {
"main": [
[
{
"node": "Day 0 - Institutional Welcome"
}
],
[
{
"node": "Day 0 - ERISA Welcome + BAA"
}
],
[
{
"node": "Day 0 - Reg BI Form CRS Delivery"
}
],
[
{
"node": "Day 0 - Reg BI Form CRS Delivery"
}
]
]
},
"Day 0 - Reg BI Form CRS Delivery": {
"main": [
[
{
"node": "Wait 3 Days"
}
]
]
},
"Day 0 - Institutional Welcome": {
"main": [
[
{
"node": "Wait 3 Days"
}
]
]
},
"Day 0 - ERISA Welcome + BAA": {
"main": [
[
{
"node": "Wait 3 Days"
}
]
]
},
"Wait 3 Days": {
"main": [
[
{
"node": "Day 3 - Platform Setup Guide"
}
]
]
},
"Day 3 - Platform Setup Guide": {
"main": [
[
{
"node": "Wait 4 Days"
}
]
]
},
"Wait 4 Days": {
"main": [
[
{
"node": "Day 7 - Compliance Check-In"
}
]
]
},
"Day 7 - Compliance Check-In": {
"main": [
[
{
"node": "Log to Compliance Audit Sheet"
}
]
]
}
}
}
Workflow 2: Trading System & Portfolio API Health Monitor
Polls your OMS, portfolio accounting, market data feed, compliance screening, and custodian API endpoints every 5 minutes. Uses $getWorkflowStaticData to track state transitions and prevent duplicate alerts.
ERISA fiduciary angle: OMS or portfolio accounting downtime during market hours = failure to fulfill ERISA §404 duty of prudence for retirement plan clients. Your SRE team gets alerted before the plan's trustee notices.
Reg BI best execution: Compliance screening system downtime means trades may execute without pre-trade restricted securities check — a Reg BI best interest violation waiting to happen.
Custodian API note: Form ADV §204-3(f) requires timely account reconciliation. Custodian API downtime creates downstream position reporting delays that affect Reg BI best execution documentation.
{
"name": "Trading System & Portfolio API Health Monitor",
"nodes": [
{
"id": "1",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 5
}
]
}
},
"position": [
250,
300
]
},
{
"id": "2",
"name": "Get API Endpoints",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "getAll",
"sheetId": "YOUR_SHEET_ID",
"range": "TradingAPIs!A:F"
},
"position": [
450,
300
]
},
{
"id": "3",
"name": "Split by Endpoint",
"type": "n8n-nodes-base.splitInBatches",
"parameters": {
"batchSize": 1
},
"position": [
650,
300
]
},
{
"id": "4",
"name": "Ping Endpoint",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "={{ $json.endpoint_url }}",
"method": "GET",
"options": {
"timeout": 8000,
"continueOnFail": true
},
"sendHeaders": true,
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Bearer {{ $json.api_key }}"
}
]
}
},
"position": [
850,
300
]
},
{
"id": "5",
"name": "Evaluate Health",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const endpoint = $('Get API Endpoints').first().json;\nconst response = $input.first().json;\nconst statusCode = response.statusCode || 0;\nconst responseTime = response.responseTime || 9999;\nlet status = 'OK';\nlet severity = 'INFO';\nlet complianceNote = '';\nif (statusCode === 0 || statusCode >= 500) {\n status = 'DOWN';\n severity = 'CRITICAL';\n if (endpoint.system_type === 'ORDER_MANAGEMENT_SYSTEM') {\n complianceNote = 'OMS DOWN: Reg BI \u00a7240.15l-1 best execution documentation gap. ' +\n 'ERISA \u00a7404 duty of prudence: market hours downtime creates fiduciary exposure for retirement plan clients.';\n } else if (endpoint.system_type === 'PORTFOLIO_ACCOUNTING') {\n complianceNote = 'Portfolio accounting down: SEC \u00a7275.204-2 recordkeeping gap risk. ' +\n 'Net asset value calculations may be delayed, impacting ERISA plan participant statements.';\n } else if (endpoint.system_type === 'COMPLIANCE_SCREENING') {\n complianceNote = 'Compliance screening DOWN: trades may execute without pre-trade compliance check. ' +\n 'MNPI/restricted securities screening gap \u2014 escalate to CCO immediately.';\n } else if (endpoint.system_type === 'CUSTODIAN_API') {\n complianceNote = 'Custodian API DOWN: Form ADV \u00a7204-3(f) requires timely account reconciliation. ' +\n 'Downstream position reporting delays may affect Reg BI best execution documentation.';\n }\n} else if (responseTime > 3000) {\n status = 'DEGRADED';\n severity = 'WARNING';\n complianceNote = 'Degraded response time may impact trade execution quality. ' +\n 'Review for Reg BI best execution obligations if consistently slow during market hours.';\n}\nreturn [{ json: { ...endpoint, status, severity, complianceNote, responseTime, checkedAt: new Date().toISOString() } }];"
},
"position": [
1050,
300
]
},
{
"id": "6",
"name": "Filter Non-OK",
"type": "n8n-nodes-base.filter",
"parameters": {
"conditions": {
"string": [
{
"value1": "={{ $json.status }}",
"operation": "notEqual",
"value2": "OK"
}
]
}
},
"position": [
1250,
300
]
},
{
"id": "7",
"name": "Dedup 30min",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const item = $input.first().json;\nconst state = $getWorkflowStaticData('global');\nconst key = `alert_${item.endpoint_id}_${item.status}`;\nconst last = state[key] || 0;\nif (Date.now() - last < 30 * 60 * 1000) return [];\nstate[key] = Date.now();\nreturn [$input.first()];"
},
"position": [
1450,
300
]
},
{
"id": "8",
"name": "Slack Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#platform-trading-ops",
"text": "{{ $json.severity }} | {{ $json.system_type }}: {{ $json.endpoint_name }} is {{ $json.status }}\nResponse time: {{ $json.responseTime }}ms\nCompliance note: {{ $json.complianceNote }}\nEndpoint: {{ $json.endpoint_url }}\nChecked: {{ $json.checkedAt }}"
},
"position": [
1650,
200
]
},
{
"id": "9",
"name": "Log to Postgres",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO trading_api_incidents (endpoint_id, system_type, status, severity, compliance_note, response_time_ms, occurred_at) VALUES ('{{ $json.endpoint_id }}', '{{ $json.system_type }}', '{{ $json.status }}', '{{ $json.severity }}', '{{ $json.complianceNote }}', {{ $json.responseTime }}, NOW()) ON CONFLICT (endpoint_id, status, DATE(occurred_at)) DO NOTHING;"
},
"position": [
1650,
400
]
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Get API Endpoints"
}
]
]
},
"Get API Endpoints": {
"main": [
[
{
"node": "Split by Endpoint"
}
]
]
},
"Split by Endpoint": {
"main": [
[
{
"node": "Ping Endpoint"
}
]
]
},
"Ping Endpoint": {
"main": [
[
{
"node": "Evaluate Health"
}
]
]
},
"Evaluate Health": {
"main": [
[
{
"node": "Filter Non-OK"
}
]
]
},
"Filter Non-OK": {
"main": [
[
{
"node": "Dedup 30min"
}
]
]
},
"Dedup 30min": {
"main": [
[
{
"node": "Slack Alert"
},
{
"node": "Log to Postgres"
}
]
]
}
}
}
Workflow 3: SEC/FINRA/DOL Compliance Deadline Tracker
Runs weekdays at 8AM. Checks 12 compliance deadline types against a Google Sheet, calculates urgency, deduplicates against alert_sent_date, and routes alerts to Slack and email.
Deadlines covered:
-
FORM_ADV_ANNUAL_UPDATE— SEC IAA §204(b): 90 days after fiscal year end -
FORM_ADV_INTERIM_AMENDMENT— §204(b): 30 days after any material change -
FORM_PF_QUARTERLY— Dodd-Frank §404: 60 days after quarter end (large advisers) -
REG_BI_ANNUAL_REVIEW— SEC §240.15l-1(a)(2)(iii): annual best interest obligation review -
FINRA_4370_BCP_ANNUAL— FINRA Rule 4370: annual business continuity plan test -
ERISA_5500_ANNUAL— ERISA §103: July 31 deadline (Oct 15 with extension) -
SEC_MARKETING_RULE_AUDIT— 17 CFR §275.206(4)-1: annual testimonial & track record review -
FORM_CRS_REVIEW— SEC §204-3(f): annual review of client relationship summary -
NFA_ANNUAL_REGISTRATION— NFA Rule 2-36: for commodity pool operators -
GDPR_DPA_REVIEW— GDPR Art.28(3): annual sub-processor DPA review for EU clients -
MIFID_II_TRANSACTION_REPORTING— MiFID II Art.26: quarterly reconciliation for EU clients -
DODD_FRANK_SWAP_REPORTING— CFTC §45: biweekly reconciliation if commodity pools
{
"name": "SEC/FINRA/DOL Compliance Deadline Tracker",
"nodes": [
{
"id": "1",
"name": "Weekdays 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"position": [
250,
300
]
},
{
"id": "2",
"name": "Get Compliance Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "getAll",
"sheetId": "YOUR_SHEET_ID",
"range": "Deadlines!A:I"
},
"position": [
450,
300
]
},
{
"id": "3",
"name": "Calculate Urgency",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const today = new Date();\nconst results = [];\nconst actionMap = {\n FORM_ADV_ANNUAL_UPDATE: { citation: 'SEC IAA \u00a7204(b) \u2014 90 days after FYE', penalty: 'SEC enforcement action, potential registration revocation', owner: 'CCO' },\n FORM_ADV_INTERIM_AMENDMENT: { citation: 'SEC IAA \u00a7204(b) \u2014 30 days after material change', penalty: '$10,000/day per SEC \u00a7203(e)', owner: 'CCO' },\n FORM_PF_QUARTERLY: { citation: 'Dodd-Frank \u00a7404, 17 CFR \u00a7275.204(b) \u2014 60 days after quarter end (large advisers)', penalty: 'SEC enforcement + disgorgement', owner: 'CFO' },\n REG_BI_ANNUAL_REVIEW: { citation: 'SEC Rule \u00a7240.15l-1(a)(2)(iii) \u2014 annual best interest obligation review', penalty: 'FINRA/SEC enforcement, customer restitution', owner: 'CCO' },\n FINRA_4370_BCP_ANNUAL: { citation: 'FINRA Rule 4370 \u2014 annual BCP testing', penalty: 'FINRA AWC, fine up to $100K', owner: 'COO' },\n ERISA_5500_ANNUAL: { citation: 'ERISA \u00a7103, IRS \u00a76058 \u2014 July 31 (Oct 15 extension)', penalty: '$250/day, up to $150K per plan', owner: 'CFO' },\n SEC_MARKETING_RULE_AUDIT: { citation: '17 CFR \u00a7275.206(4)-1 \u2014 annual testimonial/track record review', penalty: 'SEC enforcement, client restitution, disgorgement', owner: 'CCO' },\n FORM_CRS_REVIEW: { citation: 'SEC \u00a7204-3(f) \u2014 annual review of relationship summary', penalty: 'SEC enforcement, customer harm findings', owner: 'CCO' },\n NFA_ANNUAL_REGISTRATION: { citation: 'NFA Compliance Rule 2-36 \u2014 annual registration renewal', penalty: 'NFA suspension, commodity pool ban', owner: 'CCO' },\n GDPR_DPA_REVIEW: { citation: 'GDPR Art.28(3) \u2014 annual sub-processor DPA review (EU clients)', penalty: '4% global turnover or \u20ac20M', owner: 'DPO' },\n MIFID_II_TRANSACTION_REPORTING: { citation: 'MiFID II Art.26 \u2014 quarterly reconciliation (EU clients)', penalty: 'FCA/ESMA enforcement, \u20ac5M fine', owner: 'Compliance' },\n DODD_FRANK_SWAP_REPORTING: { citation: 'CFTC Reg \u00a745 \u2014 biweekly reconciliation (commodity pools)', penalty: 'CFTC enforcement, up to $1M/violation/day', owner: 'CFO' }\n};\nfor (const row of $input.all()) {\n const d = new Date(row.json.deadline_date);\n const daysUntil = Math.ceil((d - today) / 86400000);\n let urgency;\n if (daysUntil < 0) urgency = 'OVERDUE';\n else if (daysUntil <= 7) urgency = 'CRITICAL';\n else if (daysUntil <= 21) urgency = 'URGENT';\n else if (daysUntil <= 45) urgency = 'WARNING';\n else if (daysUntil <= 90) urgency = 'NOTICE';\n else urgency = 'GREEN';\n if (urgency === 'GREEN') continue;\n const meta = actionMap[row.json.deadline_type] || {};\n results.push({ json: { ...row.json, urgency, daysUntil, ...meta } });\n}\nreturn results;"
},
"position": [
650,
300
]
},
{
"id": "4",
"name": "Dedup alert_sent_date",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const today = new Date().toISOString().split('T')[0];\nreturn $input.all().filter(item => item.json.alert_sent_date !== today);"
},
"position": [
850,
300
]
},
{
"id": "5",
"name": "Switch Urgency",
"type": "n8n-nodes-base.switch",
"parameters": {
"dataPropertyName": "urgency",
"rules": {
"rules": [
{
"value": "OVERDUE",
"output": 0
},
{
"value": "CRITICAL",
"output": 0
},
{
"value": "URGENT",
"output": 1
},
{
"value": "WARNING",
"output": 2
},
{
"value": "NOTICE",
"output": 2
}
]
}
},
"position": [
1050,
300
]
},
{
"id": "6",
"name": "Slack CRITICAL",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#compliance-critical",
"text": ":rotating_light: {{ $json.urgency }} | {{ $json.deadline_type }}\nDays until deadline: {{ $json.daysUntil }}\nCitation: {{ $json.citation }}\nPenalty exposure: {{ $json.penalty }}\nOwner: {{ $json.owner }}\nClient: {{ $json.client_name }}"
},
"position": [
1250,
150
]
},
{
"id": "7",
"name": "Gmail Owner",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "={{ $json.owner_email }}",
"cc": "compliance@yourfirm.com",
"subject": "[{{ $json.urgency }}] {{ $json.deadline_type }} \u2014 {{ $json.daysUntil }} days remaining",
"message": "Compliance deadline alert for client {{ $json.client_name }}...\n\nRegulation: {{ $json.citation }}\nPenalty: {{ $json.penalty }}\nAction required by: {{ $json.owner }}"
},
"position": [
1250,
300
]
},
{
"id": "8",
"name": "Mark Alert Sent",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "update",
"sheetId": "YOUR_SHEET_ID",
"range": "Deadlines",
"lookupColumn": "deadline_id",
"lookupValue": "={{ $json.deadline_id }}",
"values": [
{
"column": "alert_sent_date",
"value": "={{ new Date().toISOString().split('T')[0] }}"
}
]
},
"position": [
1450,
300
]
}
],
"connections": {
"Weekdays 8AM": {
"main": [
[
{
"node": "Get Compliance Deadlines"
}
]
]
},
"Get Compliance Deadlines": {
"main": [
[
{
"node": "Calculate Urgency"
}
]
]
},
"Calculate Urgency": {
"main": [
[
{
"node": "Dedup alert_sent_date"
}
]
]
},
"Dedup alert_sent_date": {
"main": [
[
{
"node": "Switch Urgency"
}
]
]
},
"Switch Urgency": {
"main": [
[
{
"node": "Slack CRITICAL"
},
{
"node": "Gmail Owner"
}
],
[
{
"node": "Gmail Owner"
}
],
[
{
"node": "Gmail Owner"
}
]
]
},
"Gmail Owner": {
"main": [
[
{
"node": "Mark Alert Sent"
}
]
]
}
}
}
Workflow 4: Client Complaint & Reg BI Incident Pipeline
Processes compliance incidents posted to a webhook, classifies them by type, calculates regulatory self-reporting deadlines, and routes to CCO and legal with full citation context.
Incident types:
-
REG_BI_BEST_INTEREST_VIOLATION— 30-day self-reporting window to FINRA -
SEC_WHISTLEBLOWER_ESCALATION— route to CCO + outside counsel immediately -
MATERIAL_NONPUBLIC_INFORMATION— mandatory CCO + legal escalation, halt related trades -
ERISA_FIDUCIARY_BREACH— DOL investigation trigger, notify fidelity bond carrier -
FORM_U5_FILING_TRIGGER— 30 days after termination per FINRA Rule 2010 -
SEC_MARKETING_RULE_VIOLATION— corrective disclosure within 60 days -
CLIENT_COMPLAINT_WRITTEN— FINRA Rule 4530 quarterly reporting threshold
{
"name": "Client Complaint & Reg BI Incident Pipeline",
"nodes": [
{
"id": "1",
"name": "Compliance Incident Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "wealthtech-compliance-incident",
"responseMode": "lastNode"
},
"position": [
250,
300
]
},
{
"id": "2",
"name": "Classify Incident",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const payload = $input.first().json;\nconst incidentId = `WCI-${Date.now()}-${Math.random().toString(36).slice(2,7).toUpperCase()}`;\nconst incidentMap = {\n REG_BI_BEST_INTEREST_VIOLATION: {\n severity: 'CRITICAL', channel: '#compliance-critical',\n selfReportingDeadline: 30, regBody: 'FINRA',\n citation: 'SEC Rule \u00a7240.15l-1 \u2014 30-day self-reporting window to FINRA after discovery',\n action: 'Immediately document recommendation rationale. Notify CCO. Legal review required before FINRA disclosure.'\n },\n SEC_WHISTLEBLOWER_ESCALATION: {\n severity: 'CRITICAL', channel: '#compliance-critical',\n selfReportingDeadline: 0, regBody: 'SEC',\n citation: 'SEC Rule \u00a721F \u2014 whistleblower tip may trigger SEC investigation',\n action: 'Route to CCO and outside counsel immediately. Do not alter any records.'\n },\n MATERIAL_NONPUBLIC_INFORMATION: {\n severity: 'CRITICAL', channel: '#compliance-critical',\n selfReportingDeadline: 0, regBody: 'SEC',\n citation: 'Exchange Act \u00a710(b) \u2014 MNPI trading prohibition. Mandatory escalation.',\n action: 'Immediately halt any trades in the affected security. Notify CCO + legal.'\n },\n ERISA_FIDUCIARY_BREACH: {\n severity: 'HIGH', channel: '#compliance-erisa',\n selfReportingDeadline: 0, regBody: 'DOL',\n citation: 'ERISA \u00a7409 \u2014 personal liability for fiduciary breaches. DOL investigation trigger.',\n action: 'Notify plan trustee, outside ERISA counsel, and relevant insurance carrier (ERISA fidelity bond).'\n },\n FORM_U5_FILING_TRIGGER: {\n severity: 'HIGH', channel: '#compliance-hr',\n selfReportingDeadline: 30, regBody: 'FINRA',\n citation: 'FINRA Rule 2010 / Reg Notice 10-39 \u2014 Form U5 within 30 days of termination',\n action: 'Initiate Form U5 filing in FINRA WebCRD within 30 calendar days of termination date.'\n },\n SEC_MARKETING_RULE_VIOLATION: {\n severity: 'HIGH', channel: '#compliance-marketing',\n selfReportingDeadline: 60, regBody: 'SEC',\n citation: '17 CFR \u00a7275.206(4)-1 \u2014 corrective disclosure within 60 days',\n action: 'Remove or correct violating content immediately. Prepare corrective disclosure.'\n },\n CLIENT_COMPLAINT_WRITTEN: {\n severity: 'MEDIUM', channel: '#compliance-complaints',\n selfReportingDeadline: 0, regBody: 'FINRA',\n citation: 'FINRA Rule 4530 \u2014 customer complaint recordkeeping and quarterly reporting',\n action: 'Log in complaint register. Review for FINRA Rule 4530 quarterly reporting threshold.'\n }\n};\nconst meta = incidentMap[payload.incident_type] || { severity: 'LOW', channel: '#compliance-general', citation: 'Review required', action: 'Document and route to CCO.' };\nconst reportingDeadline = meta.selfReportingDeadline > 0\n ? new Date(Date.now() + meta.selfReportingDeadline * 86400000).toISOString().split('T')[0]\n : null;\nreturn [{ json: { ...payload, incidentId, ...meta, reportingDeadline, discoveredAt: new Date().toISOString() } }];"
},
"position": [
450,
300
]
},
{
"id": "3",
"name": "Dedup 30min",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const item = $input.first().json;\nconst state = $getWorkflowStaticData('global');\nconst key = `incident_${item.client_id}_${item.incident_type}`;\nconst last = state[key] || 0;\nif (Date.now() - last < 30 * 60 * 1000) return [];\nstate[key] = Date.now();\nreturn [$input.first()];"
},
"position": [
650,
300
]
},
{
"id": "4",
"name": "Slack CCO Alert",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "={{ $json.channel }}",
"text": "{{ $json.severity }} COMPLIANCE INCIDENT | {{ $json.incident_type }}\nClient: {{ $json.client_name }} ({{ $json.client_id }})\nIncident ID: {{ $json.incidentId }}\nCitation: {{ $json.citation }}\nRequired action: {{ $json.action }}\nReporting deadline: {{ $json.reportingDeadline || 'Immediate' }}\nReg body: {{ $json.regBody }}"
},
"position": [
850,
150
]
},
{
"id": "5",
"name": "Email CCO + Legal",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "cco@yourfirm.com",
"cc": "legal@yourfirm.com",
"subject": "[{{ $json.severity }}] {{ $json.incident_type }} \u2014 {{ $json.incidentId }}",
"message": "Compliance incident requires immediate attention.\n\nIncident: {{ $json.incident_type }}\nClient: {{ $json.client_name }}\nCitation: {{ $json.citation }}\nAction: {{ $json.action }}\nReporting deadline: {{ $json.reportingDeadline }}"
},
"position": [
850,
300
]
},
{
"id": "6",
"name": "Log to Postgres",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "INSERT INTO compliance_incidents (incident_id, client_id, incident_type, severity, citation, action, reporting_deadline, reg_body, discovered_at) VALUES ('{{ $json.incidentId }}', '{{ $json.client_id }}', '{{ $json.incident_type }}', '{{ $json.severity }}', '{{ $json.citation }}', '{{ $json.action }}', '{{ $json.reportingDeadline }}', '{{ $json.regBody }}', NOW()) ON CONFLICT (incident_id) DO NOTHING;"
},
"position": [
850,
450
]
},
{
"id": "7",
"name": "ACK 200",
"type": "n8n-nodes-base.respondToWebhook",
"parameters": {
"respondWith": "json",
"responseBody": "{\"status\":\"received\",\"incidentId\":\"={{ $json.incidentId }}\",\"reportingDeadline\":\"={{ $json.reportingDeadline }}\"}"
},
"position": [
1050,
300
]
}
],
"connections": {
"Compliance Incident Webhook": {
"main": [
[
{
"node": "Classify Incident"
}
]
]
},
"Classify Incident": {
"main": [
[
{
"node": "Dedup 30min"
}
]
]
},
"Dedup 30min": {
"main": [
[
{
"node": "Slack CCO Alert"
},
{
"node": "Email CCO + Legal"
},
{
"node": "Log to Postgres"
}
]
]
},
"Email CCO + Legal": {
"main": [
[
{
"node": "ACK 200"
}
]
]
}
}
}
Workflow 5: Weekly WealthTech Platform KPI Dashboard
Runs Monday at 8AM. Pulls platform metrics (AUM, client counts by type, ERISA plans) and compliance events (Reg BI incidents, ERISA breaches, MNPI flags, overdue reports) from Postgres in parallel, merges them, builds a color-coded HTML executive briefing.
Subject line flags: [REPORTING OVERDUE: N] / [REG BI INCIDENT: N] / [ERISA BREACH: N] / [MNPI FLAG: N] — executive can triage from email preview without opening.
CCO BCC is intentional: Chief Compliance Officer receives every weekly KPI without being the primary recipient — closes the governance gap where SEC examiners ask whether senior management has visibility into compliance metrics.
{
"name": "Weekly WealthTech Platform KPI Dashboard",
"nodes": [
{
"id": "1",
"name": "Monday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
},
"position": [
250,
300
]
},
{
"id": "2",
"name": "Platform Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(DISTINCT client_id) as total_clients, SUM(aum_usd) as total_aum, COUNT(DISTINCT CASE WHEN client_type='RETAIL_ADVISORY' OR client_type='HNWI_RETAIL' THEN client_id END) as reg_bi_clients, COUNT(DISTINCT CASE WHEN client_type='RETIREMENT_PLAN_ERISA' THEN client_id END) as erisa_plans, SUM(CASE WHEN client_type='RETIREMENT_PLAN_ERISA' THEN aum_usd ELSE 0 END) as erisa_aum, COUNT(DISTINCT CASE WHEN country IN ('DE','FR','NL','BE','IT','GB','IE') THEN client_id END) as eu_clients FROM client_accounts WHERE status='ACTIVE';"
},
"position": [
450,
200
]
},
{
"id": "3",
"name": "Compliance Events 7D",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(CASE WHEN incident_type='REG_BI_BEST_INTEREST_VIOLATION' THEN 1 END) as reg_bi_incidents, COUNT(CASE WHEN incident_type='ERISA_FIDUCIARY_BREACH' THEN 1 END) as erisa_breaches, COUNT(CASE WHEN incident_type='MATERIAL_NONPUBLIC_INFORMATION' THEN 1 END) as mnpi_flags, COUNT(CASE WHEN reporting_deadline IS NOT NULL AND reporting_deadline < CURRENT_DATE THEN 1 END) as overdue_reports, COUNT(CASE WHEN urgency IN ('OVERDUE','CRITICAL') THEN 1 END) as critical_deadlines FROM compliance_incidents WHERE discovered_at >= NOW() - INTERVAL '7 days';"
},
"position": [
450,
450
]
},
{
"id": "4",
"name": "Merge Results",
"type": "n8n-nodes-base.merge",
"parameters": {
"mode": "mergeByPosition"
},
"position": [
650,
300
]
},
{
"id": "5",
"name": "Build KPI Report",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const m = $input.first().json;\nconst state = $getWorkflowStaticData('global');\nconst lastAUM = state.lastAUM || m.total_aum;\nconst aumWoW = lastAUM > 0 ? (((m.total_aum - lastAUM) / lastAUM) * 100).toFixed(1) : '0.0';\nstate.lastAUM = m.total_aum;\nconst flags = [];\nif (m.overdue_reports > 0) flags.push(`[REPORTING OVERDUE: ${m.overdue_reports}]`);\nif (m.reg_bi_incidents > 0) flags.push(`[REG BI INCIDENT: ${m.reg_bi_incidents}]`);\nif (m.erisa_breaches > 0) flags.push(`[ERISA BREACH: ${m.erisa_breaches}]`);\nif (m.mnpi_flags > 0) flags.push(`[MNPI FLAG: ${m.mnpi_flags}]`);\nconst subject = flags.length > 0\n ? `${flags.join(' ')} WealthTech Weekly KPI \u2014 ${new Date().toLocaleDateString('en-US',{month:'short',day:'numeric'})}`\n : `WealthTech Weekly KPI \u2014 ${new Date().toLocaleDateString('en-US',{month:'short',day:'numeric'})}`;\nconst html = `<h2>WealthTech Platform KPI \u2014 Weekly Briefing</h2>\n<table border='1' cellpadding='6' style='border-collapse:collapse'>\n<tr><th>Metric</th><th>Value</th><th>WoW</th></tr>\n<tr><td>Total AUM</td><td>$${(m.total_aum/1e9).toFixed(2)}B</td><td>${aumWoW >= 0 ? '+' : ''}${aumWoW}%</td></tr>\n<tr><td>Total Clients</td><td>${m.total_clients}</td><td>\u2014</td></tr>\n<tr><td>Reg BI Clients</td><td>${m.reg_bi_clients}</td><td>\u2014</td></tr>\n<tr><td>ERISA Plans</td><td>${m.erisa_plans}</td><td>\u2014</td></tr>\n<tr><td>ERISA AUM</td><td>$${(m.erisa_aum/1e9).toFixed(2)}B</td><td>\u2014</td></tr>\n<tr><td>EU Clients (MiFID II)</td><td>${m.eu_clients}</td><td>\u2014</td></tr>\n</table>\n<h3>Compliance Health (Last 7 Days)</h3>\n<table border='1' cellpadding='6' style='border-collapse:collapse'>\n<tr><th>Issue</th><th>Count</th><th>Status</th></tr>\n<tr style='background:${m.reg_bi_incidents > 0 ? '#ffcccc' : '#ccffcc'}'><td>Reg BI Incidents</td><td>${m.reg_bi_incidents}</td><td>${m.reg_bi_incidents > 0 ? 'REVIEW REQUIRED' : 'OK'}</td></tr>\n<tr style='background:${m.erisa_breaches > 0 ? '#ffcccc' : '#ccffcc'}'><td>ERISA Breach Flags</td><td>${m.erisa_breaches}</td><td>${m.erisa_breaches > 0 ? 'ESCALATE IMMEDIATELY' : 'OK'}</td></tr>\n<tr style='background:${m.mnpi_flags > 0 ? '#ffcccc' : '#ccffcc'}'><td>MNPI Flags</td><td>${m.mnpi_flags}</td><td>${m.mnpi_flags > 0 ? 'CCO REVIEW REQUIRED' : 'OK'}</td></tr>\n<tr style='background:${m.overdue_reports > 0 ? '#ff6666' : '#ccffcc'}'><td>Overdue Regulatory Reports</td><td>${m.overdue_reports}</td><td>${m.overdue_reports > 0 ? 'FILE IMMEDIATELY' : 'OK'}</td></tr>\n<tr style='background:${m.critical_deadlines > 0 ? '#ffcccc' : '#ccffcc'}'><td>Critical Deadlines (0-7 days)</td><td>${m.critical_deadlines}</td><td>${m.critical_deadlines > 0 ? 'ACTION REQUIRED' : 'OK'}</td></tr>\n</table>\n<p><small>Generated by n8n self-hosted \u2014 data stays in your VPC. Powered by <a href='https://stripeai.gumroad.com'>FlowKit WealthTech Bundle</a>.</small></p>`;\nreturn [{ json: { ...m, subject, html, aumWoW } }];"
},
"position": [
850,
300
]
},
{
"id": "6",
"name": "Email CEO + BCC CCO",
"type": "n8n-nodes-base.gmail",
"parameters": {
"to": "ceo@yourfirm.com",
"bcc": "cco@yourfirm.com",
"subject": "={{ $json.subject }}",
"message": "={{ $json.html }}",
"options": {
"isBodyHtml": true
}
},
"position": [
1050,
200
]
},
{
"id": "7",
"name": "Slack Exec Summary",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#exec-wealthtech-kpis",
"text": "WealthTech Weekly: AUM ${{ ($json.total_aum/1e9).toFixed(2) }}B ({{ $json.aumWoW >= 0 ? '+' : '' }}{{ $json.aumWoW }}% WoW) | {{ $json.total_clients }} clients | Reg BI: {{ $json.reg_bi_incidents }} incidents | ERISA plans: {{ $json.erisa_plans }} | Critical deadlines: {{ $json.critical_deadlines }}"
},
"position": [
1050,
400
]
}
],
"connections": {
"Monday 8AM": {
"main": [
[
{
"node": "Platform Metrics"
},
{
"node": "Compliance Events 7D"
}
]
]
},
"Platform Metrics": {
"main": [
[
{
"node": "Merge Results"
}
]
]
},
"Compliance Events 7D": {
"main": [
[
{
"node": "Merge Results"
}
]
]
},
"Merge Results": {
"main": [
[
{
"node": "Build KPI Report"
}
]
]
},
"Build KPI Report": {
"main": [
[
{
"node": "Email CEO + BCC CCO"
},
{
"node": "Slack Exec Summary"
}
]
]
}
}
}
Self-hosted n8n vs Zapier for WealthTech
| n8n (self-hosted) | Zapier | Make.com | |
|---|---|---|---|
| Client portfolio data sovereignty | ✅ In your VPC | ❌ Multi-tenant cloud | ❌ Multi-tenant cloud |
| SEC §275.204-2 (5-year recordkeeping) | ✅ Your Postgres | ❌ 30-day task log | ❌ 30-day task log |
| ERISA co-fiduciary exposure | ✅ Single sub-processor | ⚠️ Uncontrolled copy | ⚠️ Uncontrolled copy |
| Reg BI audit trail | ✅ Git-versioned JSON | ❌ Not producible | ❌ Not producible |
| MNPI data isolation | ✅ Network-isolated | ❌ Cloud egress | ❌ Cloud egress |
| Form ADV compliance evidence | ✅ Workflow = evidence package | ❌ No equivalent | ❌ No equivalent |
| MiFID II Art.28 sub-processor DPA | ✅ You control | ⚠️ Requires separate DPA | ⚠️ Requires separate DPA |
| Cost at 10M events/month | ✅ ~$50/mo VPS | ❌ $5,000+/mo | ❌ $2,000+/mo |
Get the full WealthTech automation bundle
All 5 workflows above — plus 10 more for other verticals — are available as import-ready JSON with full setup guides in the FlowKit n8n Automation Bundle.
Questions about implementation? Drop them in the comments.
Top comments (0)