n8n for GovTech & Civic Engagement SaaS Vendors: 5 Automations for FOIA Response, Election Compliance, and ADA Title II
If your SaaS platform serves government agencies, election officials, or public sector clients, you already know the stakes:
- A FOIA request that misses the 20-working-day federal clock (or 10-day California CPRA, 5-day New York FOIL acknowledgment) exposes your government customer to DOJ referral — and your platform to contract termination
- An election management system that goes down on election day is a HAVA §21083 compliance event and a CISA critical infrastructure reportable incident
- Failing the DOJ's ADA Title II WCAG 2.1 AA deadline (April 24, 2026 for state agencies) creates private right of action exposure for every government portal your SaaS powers
- Routing voter PII, FOIA request data, or election configuration through Zapier or Make creates a data egress problem that breaks your government customer's data sovereignty agreements
Here are 5 production-ready n8n workflows — with full import-ready JSON — that GovTech and civic engagement SaaS vendors use to manage these compliance obligations on autopilot.
Who This Is For
These workflows target SaaS vendors selling into government — engineering, ops, and compliance teams at:
- ELECTION_MANAGEMENT_SAAS: Statewide election management platforms (ballot design, polling place management, results reporting) — HAVA 52 USC §21083 EAC certification, VRA §2 disparate impact, ADA Title II WCAG 2.1 AA April 2026
- VOTER_REGISTRATION_SAAS: Voter registration and roll maintenance systems — NVRA 52 USC §20501, HAVA §21083 security, State voter data retention laws
- FOIA_REQUEST_MANAGEMENT_SAAS: FOIA/open records request tracking and response platforms — 5 USC §552 20-day, CA CPRA 10-day, NY FOIL 5-day ACK/20-day, IL FOIA 5-day
- CIVIC_ENGAGEMENT_PLATFORM_SAAS: Public comment portals, participatory budgeting, constituent engagement tools — ADA Title II WCAG 2.1 AA, eGov Act 44 USC §3501, CCPA/state privacy
- GOVERNMENT_CONTENT_MANAGEMENT_SAAS: Government websites, portals, and digital content platforms — Section 508 29 USC §794d, ADA Title II WCAG 2.1 AA, FedRAMP (if federal)
- DIGITAL_GOVERNMENT_SERVICES_SAAS: Permitting, licensing, payment, and service delivery platforms for government — ADA Title II WCAG 2.1 AA, PCI DSS v4.0 (payments), eGov Act digital-first mandate
- GOVTECH_CIVIC_STARTUP_SAAS: Early-stage GovTech/civic tech startups pre-Series A — ADA Title II WCAG 2.1 AA, FOIA compliance by product type, State open records acts
Why Self-Hosted n8n vs. Zapier / Make for GovTech
| Capability | n8n (self-hosted) | Zapier / Make |
|---|---|---|
| FOIA SLA multi-jurisdiction clock | ✅ Custom JS per state law | ❌ No built-in deadline engine |
| Election-day zero-downtime monitoring | ✅ 5-min health checks + CISA alerts | ❌ No HAVA-aware routing |
| ADA Title II deadline tracking | ✅ 8 deadline types, DOJ rule dates | ❌ No government-specific nodes |
| VRA §2 audit trail (git-versioned JSON) | ✅ Version control = forensic audit log | ❌ SaaS audit log, not court-ready |
| Voter PII / FOIA request data egress | ✅ Stays in your VPC/network | ❌ Routes through cloud vendor servers |
| Multi-state open records jurisdiction | ✅ Switch node per state law | ❌ Single-logic only |
| CISA election infrastructure reporting | ✅ Webhook to CISA ISAC endpoint | ❌ No CISA-specific integrations |
The core problem: voter PII, FOIA request content, election configuration data, and open records documents are subject to strict data sovereignty requirements — routing them through a cloud automation vendor creates a data egress audit finding. Self-hosted n8n keeps every byte in your controlled environment.
Workflow 1: FOIA / Open Records Request Triage & Multi-Jurisdiction Response Clock
The compliance trap: federal FOIA gives 20 working days. California CPRA gives 10. New York FOIL requires a 5-business-day acknowledgment followed by a 20-business-day response. Illinois FOIA gives 5 business days. Miss any of these and your government customer faces DOJ referral, statutory damages, and fee-shifting attorney awards — and your platform gets blamed.
This workflow runs every weekday morning and calculates each open request's SLA status under the correct jurisdiction's statute:
{
"name": "FOIA / Open Records Request Triage & Multi-Jurisdiction Response Clock",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"name": "Weekdays 8 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"id": "wf1-n1"
},
{
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"value": "={{$vars.FOIA_SHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "Requests",
"mode": "name"
},
"options": {
"where": {
"values": [
{
"lookupColumn": "status",
"lookupValue": "OPEN"
}
]
}
}
},
"name": "Get Open Requests",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.4,
"position": [
460,
300
],
"id": "wf1-n2"
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst items = [];\nfor (const row of $input.all()) {\n const r = row.json;\n const received = new Date(r.received_date);\n const jurisdiction = r.jurisdiction || 'FEDERAL';\n let deadline_days = 20;\n let ack_days = null;\n let law_cite = '5 USC \u00a7552(a)(6)(A)(i) 20 working days';\n if (jurisdiction === 'CA_CPRA') { deadline_days = 10; law_cite = 'Cal. Gov. Code \u00a77922.535 10 working days'; }\n else if (jurisdiction === 'NY_FOIL') { deadline_days = 20; ack_days = 5; law_cite = 'NY PO \u00a789(3) 5-day ACK + 20-day response'; }\n else if (jurisdiction === 'TX_PIA') { deadline_days = 10; law_cite = 'Tex. Gov. Code \u00a7552.301 10 business days'; }\n else if (jurisdiction === 'IL_FOIA') { deadline_days = 5; law_cite = 'Illinois FOIA 5 ILCS 140/3 5 business days'; }\n let business_days = 0;\n let d = new Date(received);\n while (d < today) {\n d.setDate(d.getDate() + 1);\n if (d.getDay() !== 0 && d.getDay() !== 6) business_days++;\n }\n const days_remaining = deadline_days - business_days;\n let urgency = 'NOTICE';\n if (days_remaining < 0) urgency = 'OVERDUE';\n else if (days_remaining <= 2) urgency = 'CRITICAL';\n else if (days_remaining <= 5) urgency = 'URGENT';\n else if (days_remaining <= 10) urgency = 'WARNING';\n if (ack_days && !r.ack_sent && business_days >= ack_days) {\n items.push({json: {...r, urgency: 'ACK_OVERDUE', days_remaining, business_days, deadline_days, law_cite, note: 'ACK not sent \u2014 ' + ack_days + '-day ACK window breached'}});\n } else if (days_remaining <= 10 || urgency === 'OVERDUE') {\n items.push({json: {...r, urgency, days_remaining, business_days, deadline_days, law_cite}});\n }\n}\nreturn items.length ? items : [{json:{skip:true}}];"
},
"name": "Calculate SLA Status",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"id": "wf1-n3"
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": true
}
]
}
},
"name": "Skip if None",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
],
"id": "wf1-n4"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "={{$vars.SLACK_FOIA_CHANNEL}}",
"mode": "id"
},
"text": "={{$json.urgency === 'OVERDUE' ? '\ud83d\udea8' : $json.urgency === 'CRITICAL' ? '\ud83d\udd34' : '\ud83d\udfe1'}} *FOIA/Open Records {{$json.urgency}}* \u2014 Request #{{$json.request_id}}\\nRequester: {{$json.requester_name}} | Jurisdiction: {{$json.jurisdiction}}\\nReceived: {{$json.received_date}} | Days elapsed: {{$json.business_days}} / {{$json.deadline_days}}\\nDays remaining: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) : $json.days_remaining}}\\nLaw: {{$json.law_cite}}\\nAssigned: {{$json.assigned_staff}} | Topic: {{$json.request_topic}}"
},
"name": "Slack #foia-compliance",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
1120,
200
],
"id": "wf1-n5"
},
{
"parameters": {
"sendTo": "={{$json.assigned_staff_email}}",
"subject": "={{$json.urgency}}: FOIA Request #{{$json.request_id}} \u2014 {{$json.days_remaining < 0 ? 'OVERDUE' : $json.days_remaining + ' days remaining'}} ({{$json.law_cite}})",
"message": "={{$json.urgency}} \u2014 FOIA/Open Records request #{{$json.request_id}} requires immediate attention.\\n\\nRequester: {{$json.requester_name}}\\nJurisdiction: {{$json.jurisdiction}}\\nLaw: {{$json.law_cite}}\\nReceived: {{$json.received_date}}\\nDays elapsed: {{$json.business_days}} of {{$json.deadline_days}} working days\\nStatus: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) + ' working days' : $json.days_remaining + ' working days remaining'}}\\n\\nNote: {{$json.note || 'Respond or issue extension via 5 USC \u00a7552(a)(6)(B) before deadline.'}}"
},
"name": "Gmail assigned staff",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1120,
400
],
"id": "wf1-n6"
}
],
"connections": {
"Weekdays 8 AM": {
"main": [
[
{
"node": "Get Open Requests",
"type": "main",
"index": 0
}
]
]
},
"Get Open Requests": {
"main": [
[
{
"node": "Calculate SLA Status",
"type": "main",
"index": 0
}
]
]
},
"Calculate SLA Status": {
"main": [
[
{
"node": "Skip if None",
"type": "main",
"index": 0
}
]
]
},
"Skip if None": {
"main": [
[],
[
{
"node": "Slack #foia-compliance",
"type": "main",
"index": 0
},
{
"node": "Gmail assigned staff",
"type": "main",
"index": 0
}
]
]
}
}
}
How it works:
- Pulls all open FOIA/open records requests from Google Sheets
- Counts working days elapsed since receipt, per-jurisdiction business day rules
- Maps jurisdiction code to statute:
FEDERAL→ 5 USC §552(a)(6)(A)(i) 20 working days,CA_CPRA→ Cal. Gov. Code §7922.535 10 working days,NY_FOIL→ 5-day ACK + 20-day response,TX_PIA→ 10 business days,IL_FOIA→ 5 business days - Flags
ACK_OVERDUEseparately for NY FOIL (acknowledgment window different from response window) - Routes OVERDUE/CRITICAL/URGENT/WARNING to Slack #foia-compliance + Gmail to assigned staff member
Workflow 2: Election Platform Uptime & HAVA §21083 Security Compliance Monitor
The compliance trap: election management systems, voter registration databases, and results reporting platforms are CISA-designated critical infrastructure. Under HAVA 52 USC §21083, state election systems must meet EAC security certification standards. A breach or extended outage during an election window is a CISA Election ISAC reportable event — and your SaaS contract likely includes 99.9% uptime SLAs with liquidated damages.
This workflow runs every 5 minutes and monitors all registered election infrastructure endpoints:
{
"name": "Election Platform Uptime & HAVA \u00a721083 Security Compliance Monitor",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/5 * * * *"
}
]
}
},
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"id": "wf2-n1"
},
{
"parameters": {
"jsCode": "const endpoints = JSON.parse(process.env.ELECTION_ENDPOINTS || '[]');\nreturn endpoints.map(ep => ({json: ep}));"
},
"name": "Load Endpoints",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
],
"id": "wf2-n2"
},
{
"parameters": {
"url": "={{$json.health_url}}",
"options": {
"timeout": 10000,
"response": {
"response": {
"neverError": true
}
}
}
},
"name": "HTTP Health Check",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
680,
300
],
"id": "wf2-n3"
},
{
"parameters": {
"jsCode": "const endpoint = $('Load Endpoints').item.json;\nconst resp = $input.item.json;\nconst status = resp.$response?.status || 0;\nconst latency_ms = resp.$response?.headers?.['x-response-time'] || null;\nconst is_election_day = new Date().toISOString().substring(0,10) === process.env.ELECTION_DATE;\nlet severity = 'OK';\nif (status === 0 || status >= 500) severity = is_election_day ? 'ELECTION_DAY_CRITICAL' : 'CRITICAL';\nelse if (status >= 400) severity = 'DEGRADED';\nelse if (latency_ms && parseInt(latency_ms) > 3000) severity = is_election_day ? 'ELECTION_DAY_SLA_BREACH' : 'HIGH_LATENCY';\nconst hava_risk = ['VOTER_REGISTRATION_DB','ELECTION_MANAGEMENT_SYSTEM','RESULTS_REPORTING'].includes(endpoint.system_type);\nreturn [{json: {...endpoint, severity, status, latency_ms, hava_risk, is_election_day,\n law_cite: hava_risk ? 'HAVA 52 USC \u00a721083 \u2014 election system security + CISA election infrastructure' : 'Platform SLA',\n cisa_note: hava_risk ? 'CISA Election Infrastructure ISAC reportable event if unresolved >15min' : null}}];"
},
"name": "Evaluate Severity",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
900,
300
],
"id": "wf2-n4"
},
{
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json.severity}}",
"operation": "notEqual",
"value2": "OK"
}
]
}
},
"name": "Filter Issues",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1120,
300
],
"id": "wf2-n5"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "={{$json.is_election_day ? $vars.SLACK_ELECTION_OPS : $vars.SLACK_PLATFORM_OPS}}",
"mode": "id"
},
"text": "={{$json.severity.includes('ELECTION_DAY') ? '\ud83d\udea8 ELECTION DAY INCIDENT' : '\ud83d\udd34 PLATFORM ALERT'}} \u2014 *{{$json.severity}}*\\nSystem: {{$json.system_name}} ({{$json.system_type}})\\nHTTP: {{$json.status}} | Latency: {{$json.latency_ms || 'N/A'}}ms\\nHAVA Risk: {{$json.hava_risk ? 'YES \u2014 ' + $json.law_cite : 'No'}}\\n{{$json.cisa_note ? 'CISA: ' + $json.cisa_note : ''}}"
},
"name": "Slack alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
1340,
300
],
"id": "wf2-n6"
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Load Endpoints",
"type": "main",
"index": 0
}
]
]
},
"Load Endpoints": {
"main": [
[
{
"node": "HTTP Health Check",
"type": "main",
"index": 0
}
]
]
},
"HTTP Health Check": {
"main": [
[
{
"node": "Evaluate Severity",
"type": "main",
"index": 0
}
]
]
},
"Evaluate Severity": {
"main": [
[
{
"node": "Filter Issues",
"type": "main",
"index": 0
}
]
]
},
"Filter Issues": {
"main": [
[
{
"node": "Slack alert",
"type": "main",
"index": 0
}
],
[]
]
}
}
}
How it works:
- Loads endpoint list from environment config (voter registration DB, election management system, results reporting portal)
- HTTP health checks with 10-second timeout
- Detects
ELECTION_DAY_CRITICALvs.CRITICALbased onELECTION_DATEenvironment variable — escalation path changes on election day - HAVA-risk flag:
VOTER_REGISTRATION_DB,ELECTION_MANAGEMENT_SYSTEM,RESULTS_REPORTINGsystem types trigger CISA note - Routes alerts to
#election-opson election day vs.#platform-opsotherwise
Workflow 3: ADA Title II WCAG 2.1 AA & VRA §2 Accessibility Compliance Deadline Tracker
The compliance trap: the DOJ finalized its ADA Title II WCAG 2.1 AA rule in April 2024 — state agencies with populations over 50,000 had until April 24, 2026 to comply; smaller localities have until April 26, 2027. Every government portal your SaaS powers is subject. VRA §2 algorithmic disparate impact audits are required any time you deploy a change that affects voter registration or ballot access eligibility logic.
This workflow tracks 8 compliance deadline types specific to civic tech:
{
"name": "ADA Title II WCAG 2.1 AA & VRA \u00a72 Accessibility Compliance Deadline Tracker",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
},
"name": "Weekdays 8 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"id": "wf3-n1"
},
{
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"value": "={{$vars.GOVTECH_COMPLIANCE_SHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "AccessibilityDeadlines",
"mode": "name"
}
},
"name": "Get Deadlines",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.4,
"position": [
460,
300
],
"id": "wf3-n2"
},
{
"parameters": {
"jsCode": "const today = new Date();\nconst results = [];\nconst DEADLINE_TYPES = {\n ADA_TITLE_II_WCAG_STATE: {cite: 'DOJ 28 CFR \u00a735.200 ADA Title II WCAG 2.1 AA \u2014 State agencies >50K pop: April 24 2026', risk: 'DOJ enforcement + private right of action'},\n ADA_TITLE_II_WCAG_LOCAL: {cite: 'DOJ 28 CFR \u00a735.200 ADA Title II WCAG 2.1 AA \u2014 Local govts <50K pop: April 26 2027', risk: 'DOJ enforcement + private right of action'},\n VRA_SECTION_2_AUDIT: {cite: 'VRA 52 USC \u00a710301 \u2014 algorithmic disparate impact audit on any system change', risk: 'DOJ/private litigation disparate impact claim'},\n HAVA_SYSTEM_CERTIFICATION: {cite: 'HAVA 52 USC \u00a721083 \u2014 EAC certification renewal on upgrade cycle', risk: 'State decertification, election integrity audit'},\n CISA_ELECTION_SECURITY_ASSESSMENT: {cite: 'CISA Election Security \u2014 annual risk assessment recommended', risk: 'Critical infrastructure incident response gap'},\n SECTION_508_VPAT_RENEWAL: {cite: 'Section 508 29 USC \u00a7794d \u2014 VPAT attestation on federal contract renewal', risk: 'Federal contract non-renewal'},\n FOIA_ANNUAL_REPORT: {cite: '5 USC \u00a7552(e) DOJ annual FOIA report \u2014 Feb 1 deadline', risk: 'DOJ compliance referral'},\n STATE_PRIVACY_LAW_MAPPING: {cite: 'State open records modernization \u2014 varies by state', risk: 'Litigation exposure on novel state statutes'}\n};\nfor (const row of $input.all()) {\n const r = row.json;\n const deadline = new Date(r.deadline_date);\n const days_remaining = Math.ceil((deadline - today) / (1000 * 60 * 60 * 24));\n const meta = DEADLINE_TYPES[r.deadline_type] || {cite: r.deadline_type, risk: 'Review required'};\n let urgency = 'NOTICE';\n if (days_remaining < 0) urgency = 'OVERDUE';\n else if (days_remaining <= 14) urgency = 'CRITICAL';\n else if (days_remaining <= 30) urgency = 'URGENT';\n else if (days_remaining <= 60) urgency = 'WARNING';\n else if (days_remaining <= 90) urgency = 'NOTICE';\n else continue;\n results.push({json: {...r, urgency, days_remaining, ...meta}});\n}\nreturn results.length ? results : [{json:{skip:true}}];"
},
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"id": "wf3-n3"
},
{
"parameters": {
"conditions": {
"boolean": [
{
"value1": "={{$json.skip}}",
"value2": true
}
]
}
},
"name": "Skip if None",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
],
"id": "wf3-n4"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "={{$vars.SLACK_COMPLIANCE_CHANNEL}}",
"mode": "id"
},
"text": "={{$json.urgency === 'OVERDUE' ? '\ud83d\udea8' : $json.urgency === 'CRITICAL' ? '\ud83d\udd34' : $json.urgency === 'URGENT' ? '\ud83d\udfe0' : '\ud83d\udfe1'}} *{{$json.urgency}}: {{$json.deadline_name}}*\\nDeadline: {{$json.deadline_date}} ({{$json.days_remaining < 0 ? 'OVERDUE ' + Math.abs($json.days_remaining) + 'd' : $json.days_remaining + 'd remaining'}})\\nLaw: {{$json.cite}}\\nRisk: {{$json.risk}}\\nOwner: {{$json.owner}}"
},
"name": "Slack #accessibility-compliance",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
1120,
200
],
"id": "wf3-n5"
},
{
"parameters": {
"sendTo": "={{$json.owner_email}}",
"subject": "={{$json.urgency}}: {{$json.deadline_name}} \u2014 {{$json.days_remaining < 0 ? 'OVERDUE' : $json.days_remaining + ' days'}} ({{$json.cite}})",
"message": "={{$json.urgency}} compliance deadline: {{$json.deadline_name}}\\n\\nDeadline: {{$json.deadline_date}}\\nStatus: {{$json.days_remaining < 0 ? 'OVERDUE by ' + Math.abs($json.days_remaining) + ' days' : $json.days_remaining + ' days remaining'}}\\nLaw: {{$json.cite}}\\nRisk if missed: {{$json.risk}}\\nOwner: {{$json.owner}}"
},
"name": "Gmail owner",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1120,
400
],
"id": "wf3-n6"
}
],
"connections": {
"Weekdays 8 AM": {
"main": [
[
{
"node": "Get Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Get Deadlines": {
"main": [
[
{
"node": "Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify Urgency": {
"main": [
[
{
"node": "Skip if None",
"type": "main",
"index": 0
}
]
]
},
"Skip if None": {
"main": [
[],
[
{
"node": "Slack #accessibility-compliance",
"type": "main",
"index": 0
},
{
"node": "Gmail owner",
"type": "main",
"index": 0
}
]
]
}
}
}
Deadline types covered:
-
ADA_TITLE_II_WCAG_STATE— DOJ 28 CFR §35.200, state agencies >50K: April 24 2026 -
ADA_TITLE_II_WCAG_LOCAL— local govts <50K: April 26 2027 -
VRA_SECTION_2_AUDIT— 52 USC §10301 disparate impact audit on system changes -
HAVA_SYSTEM_CERTIFICATION— EAC certification renewal on upgrade cycles -
CISA_ELECTION_SECURITY_ASSESSMENT— annual election security assessment -
SECTION_508_VPAT_RENEWAL— federal contract renewal VPAT attestation -
FOIA_ANNUAL_REPORT— 5 USC §552(e) DOJ annual report, Feb 1 deadline -
STATE_PRIVACY_LAW_MAPPING— state open records modernization tracking
Workflow 4: Civic Platform Data Breach & Election Infrastructure Security Alert Pipeline
The compliance trap: a voter PII breach triggers state breach notification laws (most: 72 hours), HAVA §21083 security requirements, and CISA Election ISAC reporting. An election system compromise must be treated as a critical infrastructure incident with immediate response. FOIA system breaches expose request content that may be legally protected under exemptions — a double liability.
{
"name": "Civic Platform Data Breach & Election Infrastructure Security Alert Pipeline",
"nodes": [
{
"parameters": {
"httpMethod": "POST",
"path": "civic-security-alert",
"options": {}
},
"name": "Webhook Trigger",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
],
"id": "wf4-n1"
},
{
"parameters": {
"jsCode": "const event = $input.item.json;\nconst INCIDENT_TYPES = {\n VOTER_PII_BREACH: {severity: 'CRITICAL', notify_72h: true, cite: 'State notification laws + HAVA \u00a721083 + CISA election infrastructure', cisa_reportable: true, response_window_h: 24},\n ELECTION_SYSTEM_COMPROMISE: {severity: 'CRITICAL', notify_72h: true, cite: 'CISA Election ISAC + HAVA \u00a721083 + state election code', cisa_reportable: true, response_window_h: 1},\n FOIA_SYSTEM_BREACH: {severity: 'HIGH', notify_72h: true, cite: '5 USC \u00a7552 + state open records act + state breach notification', cisa_reportable: false, response_window_h: 72},\n GOVERNMENT_PORTAL_DEFACEMENT: {severity: 'HIGH', notify_72h: false, cite: 'CISA web defacement \u2014 election integrity visual impact', cisa_reportable: true, response_window_h: 4},\n CIVIC_DATA_UNAUTHORIZED_ACCESS: {severity: 'HIGH', notify_72h: true, cite: 'State breach notification + ADA audit trail integrity', cisa_reportable: false, response_window_h: 72},\n API_CREDENTIAL_EXPOSURE: {severity: 'CRITICAL', notify_72h: true, cite: 'HAVA \u00a721083 security requirement + CIS Controls v8', cisa_reportable: true, response_window_h: 2},\n ACCESSIBILITY_AUDIT_LOG_DELETION: {severity: 'MEDIUM', notify_72h: false, cite: 'ADA Title II evidence preservation + VRA \u00a72 audit trail', cisa_reportable: false, response_window_h: 48}\n};\nconst meta = INCIDENT_TYPES[event.incident_type] || {severity: 'HIGH', notify_72h: false, cite: 'Review required', cisa_reportable: false, response_window_h: 72};\nconst deadline = new Date(Date.now() + meta.response_window_h * 3600000).toISOString();\nreturn [{json: {...event, ...meta, response_deadline: deadline}}];"
},
"name": "Classify Incident",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
],
"id": "wf4-n2"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "={{$vars.SLACK_SECURITY_CHANNEL}}",
"mode": "id"
},
"text": "\ud83d\udea8 *CIVIC SECURITY INCIDENT: {{$json.severity}}*\\nType: {{$json.incident_type}}\\nPlatform: {{$json.platform_name}}\\nDetected: {{new Date().toISOString()}}\\nResponse deadline: {{$json.response_deadline}} ({{$json.response_window_h}}h window)\\nLaw: {{$json.cite}}\\nCISA Reportable: {{$json.cisa_reportable ? 'YES \u2014 report to CISA Election ISAC' : 'No'}}\\nNotify-72h Required: {{$json.notify_72h ? 'YES \u2014 state breach notification clock running' : 'No'}}"
},
"name": "Slack #security-incident",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
680,
300
],
"id": "wf4-n3"
},
{
"parameters": {
"operation": "append",
"documentId": {
"__rl": true,
"value": "={{$vars.INCIDENT_LOG_SHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "IncidentLog",
"mode": "name"
},
"columns": {
"mappingMode": "autoMapInputData"
}
},
"name": "Log to Postgres Audit Trail",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.4,
"position": [
680,
500
],
"id": "wf4-n4"
}
],
"connections": {
"Webhook Trigger": {
"main": [
[
{
"node": "Classify Incident",
"type": "main",
"index": 0
}
]
]
},
"Classify Incident": {
"main": [
[
{
"node": "Slack #security-incident",
"type": "main",
"index": 0
},
{
"node": "Log to Postgres Audit Trail",
"type": "main",
"index": 0
}
]
]
}
}
}
Incident types and clocks:
-
VOTER_PII_BREACH— 24h response window, state notification + CISA Election ISAC reportable -
ELECTION_SYSTEM_COMPROMISE— 1h response, CISA + HAVA §21083 + state election code -
FOIA_SYSTEM_BREACH— 72h window, 5 USC §552 + state breach notification -
GOVERNMENT_PORTAL_DEFACEMENT— 4h, CISA web defacement — election integrity visual impact -
CIVIC_DATA_UNAUTHORIZED_ACCESS— 72h, state breach notification + ADA audit trail integrity -
API_CREDENTIAL_EXPOSURE— 2h, HAVA §21083 + CIS Controls v8 -
ACCESSIBILITY_AUDIT_LOG_DELETION— 48h, ADA Title II evidence preservation + VRA §2 audit trail
Workflow 5: Weekly GovTech Platform KPI Dashboard
Every Monday morning, your CEO and compliance officer get a single HTML email covering the metrics that matter for GovTech SaaS: FOIA SLA compliance rate, average response days, election system uptime, accessibility issues open, and MRR trends — with automatic flags when FOIA SLA drops below 90% (DOJ referral risk) or election uptime drops below 99.9% (HAVA risk).
{
"name": "Weekly GovTech Platform KPI Dashboard",
"nodes": [
{
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
},
"name": "Monday 8 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"id": "wf5-n1"
},
{
"parameters": {
"operation": "getAll",
"documentId": {
"__rl": true,
"value": "={{$vars.GOVTECH_METRICS_SHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "WeeklyMetrics",
"mode": "name"
}
},
"name": "Get Platform Metrics",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4.4,
"position": [
460,
300
],
"id": "wf5-n2"
},
{
"parameters": {
"jsCode": "const rows = $input.all().map(r => r.json);\nif (!rows.length) return [{json:{skip:true}}];\nconst cur = rows[rows.length - 1];\nconst prev = rows.length > 1 ? rows[rows.length - 2] : null;\nconst pct = (a, b) => b && b != 0 ? ((a - b) / b * 100).toFixed(1) + '%' : 'N/A';\nconst arrow = v => parseFloat(v) > 0 ? '\u25b2' : parseFloat(v) < 0 ? '\u25bc' : '\u2014';\nconst metrics = {\n active_customers: cur.active_customers,\n foia_requests_processed: cur.foia_requests_processed,\n foia_sla_compliance_pct: cur.foia_sla_compliance_pct,\n avg_response_days: cur.avg_response_days,\n accessibility_issues_open: cur.accessibility_issues_open,\n election_system_uptime_pct: cur.election_system_uptime_pct,\n new_trials: cur.new_trials,\n mrr_usd: cur.mrr_usd,\n week: cur.week\n};\nconst trends = prev ? {\n foia_sla_trend: pct(cur.foia_sla_compliance_pct, prev.foia_sla_compliance_pct),\n mrr_trend: pct(cur.mrr_usd, prev.mrr_usd),\n accessibility_trend: pct(cur.accessibility_issues_open, prev.accessibility_issues_open)\n} : {};\nconst sla_flag = parseFloat(cur.foia_sla_compliance_pct) < 90 ? '\u26a0\ufe0f FOIA SLA <90% \u2014 DOJ referral risk' : '';\nconst uptime_flag = parseFloat(cur.election_system_uptime_pct) < 99.9 ? '\ud83d\udea8 Uptime <99.9% \u2014 HAVA \u00a721083 risk' : '';\nreturn [{json: {...metrics, ...trends, sla_flag, uptime_flag}}];"
},
"name": "Compute KPIs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"id": "wf5-n3"
},
{
"parameters": {
"sendTo": "={{$vars.CEO_EMAIL}}",
"subject": "GovTech Weekly KPIs \u2014 Week of {{$json.week}}",
"message": "=<h2>GovTech Platform Weekly Dashboard \u2014 {{$json.week}}</h2>{{$json.sla_flag ? '<p style=\"color:orange\"><strong>' + $json.sla_flag + '</strong></p>' : ''}}{{$json.uptime_flag ? '<p style=\"color:red\"><strong>' + $json.uptime_flag + '</strong></p>' : ''}}<table border='1' cellpadding='6'><tr><th>Metric</th><th>This Week</th><th>Trend</th></tr><tr><td>Active Customers</td><td>{{$json.active_customers}}</td><td>\u2014</td></tr><tr><td>FOIA Requests Processed</td><td>{{$json.foia_requests_processed}}</td><td>\u2014</td></tr><tr><td>FOIA SLA Compliance</td><td>{{$json.foia_sla_compliance_pct}}%</td><td>{{$json.foia_sla_trend || 'N/A'}}</td></tr><tr><td>Avg Response Days</td><td>{{$json.avg_response_days}}</td><td>\u2014</td></tr><tr><td>Accessibility Issues Open</td><td>{{$json.accessibility_issues_open}}</td><td>{{$json.accessibility_trend || 'N/A'}}</td></tr><tr><td>Election System Uptime</td><td>{{$json.election_system_uptime_pct}}%</td><td>\u2014</td></tr><tr><td>New Trials</td><td>{{$json.new_trials}}</td><td>\u2014</td></tr><tr><td>MRR</td><td>${{$json.mrr_usd}}</td><td>{{$json.mrr_trend || 'N/A'}}</td></tr></table>",
"options": {
"appendAttribution": false
}
},
"name": "Gmail CEO Report",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
300
],
"id": "wf5-n4"
},
{
"parameters": {
"select": "channel",
"channelId": {
"__rl": true,
"value": "={{$vars.SLACK_LEADERSHIP_CHANNEL}}",
"mode": "id"
},
"text": "=\ud83d\udcca *GovTech Weekly KPIs \u2014 {{$json.week}}*\\nFOIA SLA: {{$json.foia_sla_compliance_pct}}% {{$json.foia_sla_trend || ''}} | Avg Response: {{$json.avg_response_days}}d | Election Uptime: {{$json.election_system_uptime_pct}}%\\nActive Customers: {{$json.active_customers}} | MRR: ${{$json.mrr_usd}} {{$json.mrr_trend || ''}}\\n{{$json.sla_flag}} {{$json.uptime_flag}}"
},
"name": "Slack one-liner",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.1,
"position": [
900,
500
],
"id": "wf5-n5"
}
],
"connections": {
"Monday 8 AM": {
"main": [
[
{
"node": "Get Platform Metrics",
"type": "main",
"index": 0
}
]
]
},
"Get Platform Metrics": {
"main": [
[
{
"node": "Compute KPIs",
"type": "main",
"index": 0
}
]
]
},
"Compute KPIs": {
"main": [
[
{
"node": "Gmail CEO Report",
"type": "main",
"index": 0
},
{
"node": "Slack one-liner",
"type": "main",
"index": 0
}
]
]
}
}
}
Get the Full FlowKit Bundle
All 5 workflows above, plus 10 more production-ready n8n templates covering CRM automation, lead capture, invoice generation, AI customer support, and more — available at FlowKit on Gumroad.
Individual templates start at $12. The full bundle is $97 at https://stripeai.gumroad.com.
Built with n8n. Self-hosted. All JSON is import-ready — paste into Settings → Import Workflow.
Top comments (0)