If you build insurance software — claims management SaaS, policy administration platforms, embedded insurance APIs, or actuarial data tools — you know the compliance stack: FNOL data carries policyholder PII, health claims trigger HIPAA, reinsurance treaty terms are under NDA, and actuarial model outputs are core IP that cannot leave your environment.
This guide is for InsurTech SaaS vendors — the companies building the software, not the insurance carriers using it. (For insurance end-users, see the insurance companies article in the FlowKit series.) Five production-ready n8n workflows, each with full import-ready JSON.
Self-hosted n8n runs inside your VPC. Policyholder data, claims events, and actuarial outputs never transit a third-party automation cloud — eliminating a GDPR Art.28 sub-processor entry and satisfying NAIC Model Cybersecurity Law requirements across 25+ states.
Workflow 1: FNOL Webhook Fanout & Claims Triage Pipeline
Use case: When a First Notice of Loss event arrives from a mobile app, call center system, or IoT device, classify it by claim type and severity, route to the right claims team Slack channel, acknowledge the policyholder immediately, and write to your audit trail — all within 200ms.
Why self-hosted: FNOL payloads contain policyholder name, policy number, date of loss, damage description, and often medical details. Every Zapier task touching that data adds Zapier to your NAIC cyber incident response documentation and your GDPR Art.28 processor list.
{
"name": "FNOL Intake & Claims Triage",
"nodes": [
{
"name": "FNOL Webhook",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "fnol-intake",
"responseMode": "onReceived",
"httpMethod": "POST"
}
},
{
"name": "Classify Claim",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const d = $input.first().json; const claimType = (d.claim_type || 'OTHER').toUpperCase(); const loss = parseFloat(d.estimated_loss_usd || 0); const severity = loss >= 100000 ? 'HIGH' : loss >= 25000 ? 'MEDIUM' : 'LOW'; return [{ json: { ...d, claimType, severity, ticketId: 'CLM-' + Date.now() } }];"
}
},
{
"name": "Route by Claim Type",
"type": "n8n-nodes-base.switch",
"parameters": {
"dataType": "string",
"value1": "={{ $json.claimType }}",
"rules": {
"rules": [
{ "value2": "AUTO" },
{ "value2": "HOME" },
{ "value2": "HEALTH" },
{ "value2": "LIFE" }
]
}
}
},
{ "name": "Slack #claims-auto", "type": "n8n-nodes-base.slack" },
{ "name": "Slack #claims-home", "type": "n8n-nodes-base.slack" },
{ "name": "Slack #claims-health", "type": "n8n-nodes-base.slack" },
{ "name": "Slack #claims-life", "type": "n8n-nodes-base.slack" },
{ "name": "Gmail ACK Policyholder", "type": "n8n-nodes-base.gmail" },
{
"name": "Postgres FNOL Audit Log",
"type": "n8n-nodes-base.postgres",
"parameters": { "operation": "insert", "table": "fnol_intake_log" }
}
]
}
Postgres table: fnol_intake_log(ticket_id TEXT PK, claim_type TEXT, severity TEXT, estimated_loss_usd NUMERIC, policyholder_email TEXT, event_payload JSONB, received_at TIMESTAMPTZ DEFAULT NOW())
Pro tips: Add a HIGH severity branch that pages on-call via PagerDuty API. Add CAT (catastrophe) detection — if 50+ FNOLs arrive within 1 hour from the same ZIP code, fire a #claims-cat alert with aggregate count and estimated total exposure.
Workflow 2: New Insurance Carrier / MGA Client Onboarding Drip
Use case: When a new carrier, MGA, or InsurTech startup is added to your client Sheets, classify by tier (premium volume), send API credentials + integration guides on Day 0, and run a multi-touch check-in sequence through Day 14.
Why self-hosted: Onboarding sequences contain API keys, sandbox credentials, data integration architecture docs, and premium volume data — all commercially sensitive and often under NDA.
{
"name": "Insurance Client Onboarding Drip",
"nodes": [
{
"name": "New Client Trigger",
"type": "n8n-nodes-base.googleSheetsTrigger",
"parameters": { "sheetId": "YOUR_SHEET_ID", "range": "Clients!A:H", "event": "rowAdded" }
},
{
"name": "Classify Client Tier",
"type": "n8n-nodes-base.code",
"parameters": { "jsCode": "const vol = parseFloat($input.first().json.annual_premium_volume_usd || 0); let tier = vol >= 500000000 ? 'TIER1_CARRIER' : vol >= 100000000 ? 'REGIONAL_CARRIER' : vol >= 10000000 ? 'MGA' : 'INSURTECH_STARTUP'; return [{ json: { ...$input.first().json, tier } }];" }
},
{
"name": "Day0 Welcome + API Keys",
"type": "n8n-nodes-base.gmail",
"parameters": { "subject": "Welcome — Your API Keys & Data Integration Guide" }
},
{ "name": "Slack CSM DM", "type": "n8n-nodes-base.slack" },
{ "name": "Wait 3 Days", "type": "n8n-nodes-base.wait", "parameters": { "resume": "timeInterval", "amount": 3, "unit": "days" } },
{ "name": "Day3 Data Import Check-In", "type": "n8n-nodes-base.gmail" },
{ "name": "Wait 4 Days", "type": "n8n-nodes-base.wait", "parameters": { "resume": "timeInterval", "amount": 4, "unit": "days" } },
{ "name": "Day7 First Claims Batch Review", "type": "n8n-nodes-base.gmail" },
{ "name": "Wait 7 Days", "type": "n8n-nodes-base.wait", "parameters": { "resume": "timeInterval", "amount": 7, "unit": "days" } },
{ "name": "Day14 QBR Invite (TIER1 only)", "type": "n8n-nodes-base.gmail" },
{
"name": "Mark Onboarding Complete",
"type": "n8n-nodes-base.googleSheets",
"parameters": { "operation": "update", "sheetId": "YOUR_SHEET_ID" }
}
]
}
Sheet columns: client_name, client_email, csm_slack_id, annual_premium_volume_usd, client_type, api_key, sandbox_url, onboarding_complete
Pro tips: Add a Switch after classify to skip the Day14 QBR email for INSURTECH_STARTUP tier — send a feature deep-dive instead. Add a Slack CSM DM at each Wait node completion so your CS team knows where each client sits in the sequence.
Workflow 3: Insurance Regulatory & Compliance Deadline Tracker
Use case: Daily morning check across all compliance deadlines — NAIC cyber audits, SOC2 evidence collection, state DOI filings, Solvency II Pillar 3 reports, ORSA submissions, HIPAA BAA reviews. Routes urgency-tiered alerts to Slack and Gmail, logs every alert to a compliance audit trail.
Why self-hosted: Compliance deadline data and audit evidence artifacts are privileged. An uncontrolled sub-processor listed in your compliance audit trail is a finding in its own right during state DOI examinations and SOC2 reviews.
{
"name": "Insurance Compliance Deadline Tracker",
"nodes": [
{
"name": "Weekdays 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * 1-5" }] } }
},
{
"name": "Fetch Compliance Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": { "operation": "readAllRows", "sheetId": "COMPLIANCE_SHEET_ID", "range": "Deadlines!A:G" }
},
{
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"parameters": { "jsCode": "const actionMap = { NAIC_MODEL_CYBER_AUDIT: 'Schedule internal readiness review — NAIC Model Law adopted in 25+ states', SOC2_EVIDENCE: 'Begin evidence collection in Vanta/Drata', GDPR_DPA_RENEWAL: 'Renew DPAs with all sub-processors — Art.28 requirement', STATE_DOI_FILING: 'Submit state DOI filing via SERFF', SOLVENCY_II_PILLAR3: 'Prepare quantitative reporting templates — QRT', ORSA_SUBMISSION: 'Complete Own Risk and Solvency Assessment', PCI_DSS_QSA: 'Schedule QSA assessment for payment processing', HIPAA_BAA_REVIEW: 'Audit Business Associate Agreements', NY_DFS_500_AUDIT: 'NY DFS 23 NYCRR 500 annual certification', CCPA_ANNUAL_DATA_MAP: 'Update personal information inventory' }; return $input.all().map(item => { const d = item.json; const daysLeft = Math.floor((new Date(d.deadline_date) - new Date()) / 86400000); let urgency; if (daysLeft < 0) urgency = 'OVERDUE'; else if (daysLeft <= 7) urgency = 'CRITICAL'; else if (daysLeft <= 21) urgency = 'URGENT'; else if (daysLeft <= 60) urgency = 'WARNING'; else if (daysLeft <= 90) urgency = 'NOTICE'; else return null; return { json: { ...d, daysLeft, urgency, recommendedAction: actionMap[d.regulation_type] || 'Review with compliance team' } }; }).filter(Boolean);" }
},
{
"name": "Filter Has Urgency",
"type": "n8n-nodes-base.filter",
"parameters": { "conditions": { "string": [{ "value1": "={{ $json.urgency }}", "operation": "isNotEmpty" }] } }
},
{ "name": "Slack #compliance-critical @here", "type": "n8n-nodes-base.slack" },
{ "name": "Gmail Owner", "type": "n8n-nodes-base.gmail" },
{
"name": "Postgres Compliance Audit Log",
"type": "n8n-nodes-base.postgres",
"parameters": { "operation": "insert", "table": "compliance_alert_log" }
}
]
}
Sheet columns: regulation_type, description, deadline_date, owner_email, owner_name, status, notes
Regulation types covered: NAIC_MODEL_CYBER_AUDIT, NY_DFS_500_AUDIT, STATE_DOI_FILING, SOC2_EVIDENCE, SOLVENCY_II_PILLAR3, ORSA_SUBMISSION, PCI_DSS_QSA, HIPAA_BAA_REVIEW, GDPR_DPA_RENEWAL, CCPA_ANNUAL_DATA_MAP
Workflow 4: Actuarial & Underwriting Data Pipeline Health Monitor
Use case: Every 15 minutes, verify that actuarial data pipelines — loss development triangles, exposure feeds, pricing model inputs — ran within their SLA window. Alert before breach, not after. Dedup alerts to prevent Slack noise.
Why self-hosted: Actuarial model inputs and outputs are core IP. Exposure data feeds often include reinsurer treaty terms and aggregate loss positions governed by confidentiality agreements — they cannot transit a third-party cloud.
{
"name": "Actuarial Pipeline Health Monitor",
"nodes": [
{
"name": "Every 15 Min",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": { "rule": { "interval": [{ "field": "minutes", "minutesInterval": 15 }] } }
},
{
"name": "Fetch Pipeline Runs",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT pipeline_id, pipeline_name, owner_team, last_run_at, sla_gap_minutes FROM actuarial_pipeline_runs WHERE last_run_at > NOW() - INTERVAL '48 hours'"
}
},
{
"name": "Classify & Dedup",
"type": "n8n-nodes-base.code",
"parameters": { "jsCode": "const stored = $getWorkflowStaticData('global'); return $input.all().map(item => { const d = item.json; const elapsedMin = Math.floor((Date.now() - new Date(d.last_run_at)) / 60000); const dedupKey = 'pipeline_' + d.pipeline_id; if (stored[dedupKey] && (Date.now() - stored[dedupKey]) < 30 * 60 * 1000) return null; let status; if (elapsedMin > d.sla_gap_minutes * 2) { status = 'CRITICAL'; stored[dedupKey] = Date.now(); } else if (elapsedMin > d.sla_gap_minutes) { status = 'DEGRADED'; stored[dedupKey] = Date.now(); } else return null; return { json: { ...d, elapsedMin, status } }; }).filter(Boolean);" }
},
{
"name": "Filter Non-OK",
"type": "n8n-nodes-base.filter",
"parameters": { "conditions": { "string": [{ "value1": "={{ $json.status }}", "operation": "isNotEmpty" }] } }
},
{ "name": "Slack #data-ops", "type": "n8n-nodes-base.slack" },
{
"name": "Postgres Health Events",
"type": "n8n-nodes-base.postgres",
"parameters": { "operation": "insert", "table": "pipeline_health_events" }
}
]
}
Tables needed:
actuarial_pipeline_runs(pipeline_id TEXT PK, pipeline_name TEXT, owner_team TEXT, last_run_at TIMESTAMPTZ, sla_gap_minutes INT)pipeline_health_events(id SERIAL PK, pipeline_id TEXT, status TEXT, elapsed_min INT, alerted_at TIMESTAMPTZ DEFAULT NOW())
Pro tips: Add a second Slack message to @owner_team DM for CRITICAL status. Add a Postgres query to check for pipelines that have never run (missing from actuarial_pipeline_runs entirely) — useful after new pipeline deployments.
Workflow 5: Weekly InsurTech Platform KPI Dashboard
Use case: Every Monday at 8AM, pull this week vs last week metrics — active policies, claims processed, average processing time, loss ratio, MRR, active carrier clients — and email a color-coded HTML dashboard to the leadership team.
Why self-hosted: Platform KPI data includes aggregate claims volumes, loss ratios, and MRR figures that are confidential under investor agreements and reinsurance arrangements.
{
"name": "Weekly InsurTech Platform KPI Dashboard",
"nodes": [
{
"name": "Monday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": { "rule": { "interval": [{ "field": "cronExpression", "expression": "0 8 * * 1" }] } }
},
{
"name": "This Week Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(DISTINCT policy_id) AS active_policies, COUNT(DISTINCT claim_id) FILTER (WHERE created_at >= NOW()-INTERVAL '7d') AS claims_week, ROUND(AVG(processing_time_hours) FILTER (WHERE created_at >= NOW()-INTERVAL '7d'), 1) AS avg_processing_hours, ROUND(SUM(incurred_loss_usd)/NULLIF(SUM(earned_premium_usd),0)*100,1) AS loss_ratio_pct, SUM(mrr_usd) AS mrr, COUNT(DISTINCT client_id) AS active_clients FROM platform_kpis WHERE snapshot_date = CURRENT_DATE"
}
},
{
"name": "Last Week Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(DISTINCT claim_id) FILTER (WHERE created_at BETWEEN NOW()-INTERVAL '14d' AND NOW()-INTERVAL '7d') AS claims_last_week, SUM(mrr_usd) AS mrr_last FROM platform_kpis WHERE snapshot_date = CURRENT_DATE - 7"
}
},
{
"name": "Merge",
"type": "n8n-nodes-base.merge",
"parameters": { "mode": "mergeByIndex" }
},
{
"name": "Build HTML Dashboard",
"type": "n8n-nodes-base.code",
"parameters": { "jsCode": "const d = $input.first().json; const stored = $getWorkflowStaticData('global'); const claimsWoW = d.claims_last_week > 0 ? Math.round((d.claims_week - d.claims_last_week) / d.claims_last_week * 100) : 0; const mrrWoW = d.mrr_last > 0 ? Math.round((d.mrr - d.mrr_last) / d.mrr_last * 100) : 0; const rows = [['Active Policies', d.active_policies?.toLocaleString(), '-'], ['Claims This Week', d.claims_week, claimsWoW + '%'], ['Avg Processing Time', d.avg_processing_hours + 'h', '-'], ['Loss Ratio', d.loss_ratio_pct + '%', '-'], ['MRR', '$' + (d.mrr/1000).toFixed(1) + 'K', mrrWoW + '%'], ['Active Clients', d.active_clients, '-']]; const tableRows = rows.map(r => '<tr><td>' + r[0] + '</td><td>' + r[1] + '</td><td>' + r[2] + '</td></tr>').join(''); const html = '<h2>InsurTech Weekly KPI Dashboard</h2><table border=1 cellpadding=6><tr><th>Metric</th><th>This Week</th><th>WoW</th></tr>' + tableRows + '</table>'; return [{ json: { html } }];" }
},
{
"name": "Gmail CEO + Leadership",
"type": "n8n-nodes-base.gmail",
"parameters": {
"subject": "InsurTech Weekly KPI Dashboard",
"emailType": "html"
}
},
{ "name": "Slack #platform-metrics", "type": "n8n-nodes-base.slack" }
]
}
Why Self-Hosted n8n for InsurTech SaaS
| Requirement | Zapier / Make | n8n (self-hosted) |
|---|---|---|
| NAIC Model Cybersecurity Law (25+ states) | Routes policyholder data through vendor cloud | Runs in your VPC — no external processor |
| NY DFS 23 NYCRR 500 annual certification | Third-party risk finding in cyber program | Controlled environment — eliminates finding |
| GDPR Art.28 sub-processor elimination | Zapier listed in every customer DPA | No sub-processor — DPA update not needed |
| HIPAA BAA (health/disability claims) | BAA required + breach scope expands to Zapier infra | PHI stays in VPC — BAA scope unchanged |
| Solvency II Pillar 3 data confidentiality | Regulatory reporting data exits controlled env | Stays in network — EU Art.44 transfer eliminated |
| Reinsurance treaty NDA compliance | Treaty terms transit third-party | Stays in your VPC under NDA — no transfer |
| Actuarial model IP protection | Model outputs route through vendor | Stays in your infrastructure entirely |
| Volume pricing (50K claims/day, ~10M ops/mo) | $4K–$8K/mo Zapier Teams | ~$40/mo VPS |
| SOC2 CM-3 / state DOI audit trail | Black-box vendor change management | Git-versioned workflow JSON = full change log |
Get All 15 Workflow Templates
These InsurTech SaaS workflows are part of the FlowKit collection — 15 production-ready n8n templates covering claims ops, client onboarding, monitoring, reporting, and more.
Browse and download at stripeai.gumroad.com.
Individual templates from $12. Full bundle (all 15): $97.
Alex Kane builds n8n automation templates at FlowKit — ready-to-import workflows for teams that move fast.
Top comments (0)