n8n for GovTech/Public Safety SaaS Vendors: 5 Automations for CJIS, NG911, and StateRAMP Compliance
If your SaaS platform serves PSAPs, law enforcement agencies, fire/EMS operations, corrections facilities, or emergency management teams, you're operating in one of the most heavily regulated technology environments in the US. The FBI CJIS Security Policy v5.9, CALEA lawful intercept requirements, NG911/i3 interoperability mandates, StateRAMP authorization requirements, and IPAWS authentication obligations create a compliance matrix that touches every layer of your platform.
The automation patterns below address the compliance deadlines, integration health signals, and regulatory reporting obligations that GovTech/Public Safety SaaS vendors routinely miss — not from negligence, but from the sheer number of moving parts across dozens of agency customers, each with their own ATO conditions and CJIS Security Addendum terms.
Who This Applies To
| Customer Tier | Example Platforms | Primary Compliance Obligations |
|---|---|---|
| PSAP_DISPATCH_SAAS | 911 CAD/dispatch, ESInet routing | CJIS v5.9, NG911 i3, CALEA, IPAWS |
| LAW_ENFORCEMENT_RECORDS_SAAS | RMS, body camera, investigative | CJIS v5.9 §5.2.1.1 Advanced Auth, NCIC access rules |
| FIRE_EMS_OPERATIONS_SAAS | Fire/EMS CAD, asset tracking | NG911, FirstNet Band 14, HIPAA PHI in patient data |
| EMERGENCY_MANAGEMENT_SAAS | EOC management, FEMA coordination | IPAWS, FEMA BPAS, StateRAMP |
| CORRECTIONS_FACILITY_SAAS | Jail/prison management, inmate tracking | CJIS v5.9, PREA, ADA Title II |
| COURT_CASE_MANAGEMENT_SAAS | Judiciary SaaS, case/docket tracking | CJIS v5.9, HIPAA (mental health records), ADA §508 |
| PUBLIC_SAFETY_ANALYTICS_SAAS | Crime analytics, predictive oversight | CJIS v5.9, CCPA, Title VI disparate impact |
The Compliance Clock Problem
CJIS Security Policy v5.9 §5.4.7 mandates quarterly log reviews — but the clock starts when the audit period ends, not when someone manually runs the report. CALEA technical capability orders require compliance within 90 days of receipt. StateRAMP continuous monitoring requires annual POA&M updates. NG911 ESInet availability standards (FCC recommends 99.999%) mean every minute of downtime is a documented gap in your ATO evidence package.
The standard response — spreadsheets, calendar reminders, manual quarterly sweeps — doesn't scale past 10 agency customers. At 50+ agencies, each with their own CJIS Security Addendum terms and StateRAMP ATO conditions, the compliance matrix becomes unmanageable without automation.
Why Self-Hosted n8n Matters Here
CJIS Security Policy v5.9 §5.3.1 requires AES 256-bit encryption for CJI at rest and in transit, with the cloud service provider (CSP) explicitly listed in the agency's CJIS Security Addendum. Routing criminal history records, warrant data, CAD incident reports, or inmate records through Zapier/Make requires CJIS to review and approve that CSP — a process that takes months and often fails.
Self-hosted n8n running in your FedRAMP/StateRAMP-authorized cloud environment keeps CJI in the authorized enclave. The workflow JSON lives in Git — creating the auditable evidence trail that CJIS auditors require under §5.4.1.
Workflow 1: CJIS v5.9 Compliance Deadline Tracker
Who needs it: Any SaaS platform where agencies access CJI (criminal justice information).
What it catches:
- CJIS §5.4.7 quarterly log reviews (clock starts at end of each quarter)
- §5.12.1 personnel security screening (30-day window for new hires with CJI access)
- §5.2.1.1 advanced authentication renewals (MFA credentials expire)
- Annual CJIS Security Addendum renewals per subscribing agency
- CALEA annual technical capability demonstration deadlines
Your Sheets schema (cjis_compliance_items):
| requirement_id | agency_name | saas_module | requirement_type | due_date | responsible_officer | responsible_officer_email | tier |
|---|---|---|---|---|---|---|---|
| CJIS-001 | Metro PD | Records Module | CJIS_ANNUAL_ADDENDUM | 2026-06-15 | J. Smith | j.smith@metropd.gov | LAW_ENFORCEMENT_RECORDS_SAAS |
Urgency tiers: OVERDUE → CRITICAL (≤14d) → URGENT (≤30d) → WARNING (≤60d) → NOTICE (≤90d)
{
"name": "CJIS v5.9 Compliance Deadline Tracker",
"nodes": [
{
"id": "1",
"name": "Every Weekday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1-5"
}
]
}
}
},
{
"id": "2",
"name": "Load CJIS Items",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
460,
300
],
"parameters": {
"operation": "readRows",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "cjis_compliance_items",
"mode": "name"
},
"options": {}
}
},
{
"id": "3",
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"parameters": {
"jsCode": "const today = new Date();\nconst items = $input.all().map(item => {\n const row = item.json;\n const dueDate = new Date(row.due_date);\n const daysUntil = Math.floor((dueDate - today) / (1000 * 60 * 60 * 24));\n let urgency = 'OK';\n if (daysUntil < 0) urgency = 'OVERDUE';\n else if (daysUntil <= 14) urgency = 'CRITICAL';\n else if (daysUntil <= 30) urgency = 'URGENT';\n else if (daysUntil <= 60) urgency = 'WARNING';\n else if (daysUntil <= 90) urgency = 'NOTICE';\n return { json: { ...row, daysUntil, urgency } };\n});\nreturn items.filter(i => i.json.urgency !== 'OK');"
}
},
{
"id": "4",
"name": "IF Actionable",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
900,
300
],
"parameters": {
"conditions": {
"options": {
"combinator": "and"
},
"conditions": [
{
"leftValue": "={{$json.urgency}}",
"rightValue": "OK",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
]
}
}
},
{
"id": "5",
"name": "Slack Alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
1120,
200
],
"parameters": {
"channel": "#compliance-team",
"text": "={{$json.urgency}}: {{$json.requirement_type}} for {{$json.agency_name}} \u2014 due {{$json.due_date}} ({{$json.daysUntil}} days). Module: {{$json.saas_module}}. Tier: {{$json.tier}}. Owner: {{$json.responsible_officer}}",
"otherOptions": {}
}
},
{
"id": "6",
"name": "Email Officer",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1120,
400
],
"parameters": {
"operation": "send",
"toList": "={{$json.responsible_officer_email}}",
"subject": "=[CJIS {{$json.urgency}}] {{$json.requirement_type}} \u2014 {{$json.daysUntil < 0 ? 'OVERDUE' : $json.daysUntil + ' days remaining'}}",
"message": "=CJIS v5.9 Compliance Alert for {{$json.agency_name}}\\n\\nRequirement: {{$json.requirement_type}}\\nModule: {{$json.saas_module}}\\nDue Date: {{$json.due_date}}\\nDays Until Due: {{$json.daysUntil}}\\nUrgency: {{$json.urgency}}\\nTier: {{$json.tier}}\\n\\nCJIS \u00a75.4.7 requires quarterly log reviews. \u00a75.12.1 requires personnel screening within 30 days. Advanced authentication under \u00a75.2.1.1 must be active for all remote access. Non-compliance risks Criminal Justice Information Services (CJIS) Security Addendum termination \u2014 which terminates your agency's access to the FBI NCIC and state criminal repositories.\\n\\nAction required by {{$json.due_date}}.\\n\\n\u2014 FlowKit Compliance Monitor",
"options": {}
}
}
],
"connections": {
"Every Weekday 7AM": {
"main": [
[
{
"node": "Load CJIS Items",
"type": "main",
"index": 0
}
]
]
},
"Load CJIS Items": {
"main": [
[
{
"node": "Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify Urgency": {
"main": [
[
{
"node": "IF Actionable",
"type": "main",
"index": 0
}
]
]
},
"IF Actionable": {
"main": [
[
{
"node": "Slack Alert",
"type": "main",
"index": 0
}
],
[
{
"node": "Email Officer",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 2: NG911/i3 ESInet Integration Health Monitor
Who needs it: CAD vendors, ESInet operators, TextTo911 platforms, PSAP software vendors.
What it catches:
- ESInet endpoint failures (NG911 i3 §5.1 availability requirements)
- TextTo911 service gaps (FCC §20.18(n) accessibility mandate — civil penalty for downtime)
- ECRF/LVF location routing degradation (mis-routed 911 calls = liability)
- ESInet response time violations (FCC recommends <100ms for emergency call routing)
Your Sheets schema (ng911_integrations):
| psap_id | psap_name | endpoint_url | integration_type | tier |
|---|---|---|---|---|
| PSAP-001 | County 911 Center | https://esinet.county.gov/health | ESInet | PSAP_DISPATCH_SAAS |
Integration types: ESInet, TextTo911, ECRF, LVF, CallHandling, PSAP_Gateway
{
"name": "NG911/i3 ESInet Integration Health Monitor",
"nodes": [
{
"id": "1",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 5
}
]
}
}
},
{
"id": "2",
"name": "Load NG911 Endpoints",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
460,
300
],
"parameters": {
"operation": "readRows",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "ng911_integrations",
"mode": "name"
},
"options": {}
}
},
{
"id": "3",
"name": "Batch 3",
"type": "n8n-nodes-base.splitInBatches",
"typeVersion": 3,
"position": [
680,
300
],
"parameters": {
"batchSize": 3,
"options": {}
}
},
{
"id": "4",
"name": "Ping Endpoint",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
900,
300
],
"parameters": {
"method": "GET",
"url": "={{$json.endpoint_url}}",
"options": {
"timeout": 5000,
"response": {
"response": {
"neverError": true
}
}
}
}
},
{
"id": "5",
"name": "Classify Health",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1120,
300
],
"parameters": {
"jsCode": "const item = $input.first().json;\nconst statusCode = item.statusCode || 0;\nconst responseTime = item.responseTime || 9999;\nlet health = 'OK';\nlet risk = '';\nif (statusCode === 0 || statusCode >= 500) {\n health = 'DOWN';\n risk = item.integration_type === 'ESInet'\n ? 'CRITICAL: NG911 i3 interoperability gap \u2014 911 calls may not route to correct PSAP'\n : item.integration_type === 'TextTo911'\n ? 'HIGH: Text-to-911 service unavailable \u2014 ADA Title II disability accommodation gap'\n : 'HIGH: ' + item.integration_type + ' offline';\n} else if (responseTime > 2000 || (statusCode >= 400 && statusCode < 500)) {\n health = 'DEGRADED';\n risk = 'NG911 i3 standard recommends <100ms ESInet response \u2014 ' + responseTime + 'ms degrades call routing';\n}\nreturn [{ json: { ...item, health, risk, statusCode, responseTime, checked_at: new Date().toISOString() } }];"
}
},
{
"id": "6",
"name": "IF Not OK",
"type": "n8n-nodes-base.if",
"typeVersion": 2,
"position": [
1340,
300
],
"parameters": {
"conditions": {
"options": {
"combinator": "and"
},
"conditions": [
{
"leftValue": "={{$json.health}}",
"rightValue": "OK",
"operator": {
"type": "string",
"operation": "notEquals"
}
}
]
}
}
},
{
"id": "7",
"name": "Slack NOC Alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
1560,
200
],
"parameters": {
"channel": "#noc-public-safety",
"text": "=\ud83d\udea8 {{$json.health}}: {{$json.psap_name}} \u2014 {{$json.integration_type}} at {{$json.endpoint_url}}\\nRisk: {{$json.risk}}\\nHTTP: {{$json.statusCode}} | Response: {{$json.responseTime}}ms\\nTier: {{$json.tier}} | Checked: {{$json.checked_at}}",
"otherOptions": {}
}
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Load NG911 Endpoints",
"type": "main",
"index": 0
}
]
]
},
"Load NG911 Endpoints": {
"main": [
[
{
"node": "Batch 3",
"type": "main",
"index": 0
}
]
]
},
"Batch 3": {
"main": [
[
{
"node": "Ping Endpoint",
"type": "main",
"index": 0
}
]
]
},
"Ping Endpoint": {
"main": [
[
{
"node": "Classify Health",
"type": "main",
"index": 0
}
]
]
},
"Classify Health": {
"main": [
[
{
"node": "IF Not OK",
"type": "main",
"index": 0
}
]
]
},
"IF Not OK": {
"main": [
[
{
"node": "Slack NOC Alert",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 3: CALEA/CJIS Breach & Audit Event Pipeline
Who needs it: Any GovTech SaaS vendor with CALEA lawful intercept capability or CJIS data access.
The deadline structure:
| Event Type | Reporting Body | Citation | Deadline |
|---|---|---|---|
| CJIS_UNAUTHORIZED_ACCESS | FBI CJIS Division | CJIS Policy v5.9 §5.4.7 | 1 hour |
| CALEA_INTERCEPT_UNAUTHORIZED | FBI/DOJ | 47 USC §1002 — $10K/day civil penalty | 1 hour |
| NG911_DATA_BREACH | FCC/CISA | NIST SP 800-187 + HIPAA if PHI | 24 hours |
| CJIS_POLICY_VIOLATION | State CSO | CJIS Policy §5.4.1 | 24 hours |
| STATERAMP_FINDING | StateRAMP PMO | ATO conditions — open POA&M item | 72 hours |
| FIRSTNET_CUI_EXPOSURE | FirstNet Authority | Band 14 security requirements | 72 hours |
| IPAWS_AUTH_FAILURE | FEMA IPAWS PMO | 47 CFR §11.61 EAS test failure | 24 hours |
{
"name": "CALEA/CJIS Breach & Audit Event Pipeline",
"nodes": [
{
"id": "1",
"name": "Audit Event Webhook",
"type": "n8n-nodes-base.webhook",
"typeVersion": 2,
"position": [
240,
300
],
"parameters": {
"path": "public-safety-audit",
"responseMode": "onReceived",
"options": {}
}
},
{
"id": "2",
"name": "Classify Event",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
460,
300
],
"parameters": {
"jsCode": "const event = $input.first().json;\nconst typeMap = {\n CJIS_UNAUTHORIZED_ACCESS: { severity: 'CRITICAL', deadline_hours: 1, body: 'FBI CJIS', citation: 'CJIS Security Policy v5.9 \u00a75.4.7 \u2014 mandatory incident reporting' },\n CALEA_INTERCEPT_UNAUTHORIZED: { severity: 'CRITICAL', deadline_hours: 1, body: 'FBI/DOJ', citation: '47 USC \u00a71002 CALEA \u2014 unauthorized intercept capability = $10K/day civil penalty' },\n NG911_DATA_BREACH: { severity: 'CRITICAL', deadline_hours: 24, body: 'FCC/CISA', citation: 'NIST SP 800-187 NG911 Security Framework + HIPAA 45 CFR \u00a7164.408 if PHI involved' },\n CJIS_POLICY_VIOLATION: { severity: 'HIGH', deadline_hours: 24, body: 'State CSO', citation: 'CJIS Security Policy v5.9 \u00a75.4.1 \u2014 violation must be reported to appointing authority' },\n STATERAMP_FINDING: { severity: 'HIGH', deadline_hours: 72, body: 'StateRAMP PMO', citation: 'StateRAMP Continuous Monitoring \u2014 open POA&M item deadline per ATO conditions' },\n FIRSTNET_CUI_EXPOSURE: { severity: 'HIGH', deadline_hours: 72, body: 'FirstNet Authority', citation: 'FirstNet Band 14 security requirements + DFARS 252.204-7012 if DoD overlap' },\n IPAWS_AUTH_FAILURE: { severity: 'MEDIUM', deadline_hours: 24, body: 'FEMA IPAWS PMO', citation: '47 CFR \u00a711.61 \u2014 EAS monthly test failure must be reported' }\n};\nconst meta = typeMap[event.event_type] || { severity: 'LOW', deadline_hours: 72, body: 'Security Team', citation: 'Internal security policy' };\nconst deadline = new Date(Date.now() + meta.deadline_hours * 3600000).toISOString();\nreturn [{ json: { ...event, ...meta, report_deadline: deadline, logged_at: new Date().toISOString() } }];"
}
},
{
"id": "3",
"name": "Slack Emergency",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
680,
200
],
"parameters": {
"channel": "#security-emergency",
"text": "=\ud83d\udea8 {{$json.severity}} PUBLIC SAFETY INCIDENT: {{$json.event_type}}\\nAgency: {{$json.agency_name}} | Module: {{$json.saas_module}}\\nCitation: {{$json.citation}}\\nReport to: {{$json.body}} by {{$json.report_deadline}}\\nEvent: {{$json.description}}",
"otherOptions": {}
}
},
{
"id": "4",
"name": "Log Incident",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
680,
400
],
"parameters": {
"operation": "appendOrUpdate",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "cjis_incident_log",
"mode": "name"
},
"columns": {
"mappingMode": "autoMapInputData",
"value": {}
},
"options": {}
}
}
],
"connections": {
"Audit Event Webhook": {
"main": [
[
{
"node": "Classify Event",
"type": "main",
"index": 0
}
]
]
},
"Classify Event": {
"main": [
[
{
"node": "Slack Emergency",
"type": "main",
"index": 0
},
{
"node": "Log Incident",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 4: StateRAMP/FirstNet Certification Tracker
Who needs it: GovTech vendors seeking or maintaining state/local government authorizations.
What it tracks:
- StateRAMP High/Moderate ATO renewals (typically 2-3 year cycles with annual ConMon)
- FirstNet Band 14 authentication credential renewals
- CJIS Security Addendum renewals per subscribing agency
- CALEA annual technical capability test deadlines
The business impact: Expired StateRAMP ATO = immediate suspension of new agency seat provisioning. Lapsed CJIS Security Addendum = loss of NCIC/state repository access for all agency users on that module.
Your Sheets schema (agency_certifications):
| agency_id | agency_name | certification_type | status | expiry_date | account_manager | account_manager_email | tier |
|---|---|---|---|---|---|---|---|
| AG-001 | State Police | STATERAMP_HIGH | ACTIVE | 2026-07-01 | K. Lee | k.lee@yourco.com | LAW_ENFORCEMENT_RECORDS_SAAS |
{
"name": "StateRAMP/FirstNet Certification Tracker",
"nodes": [
{
"id": "1",
"name": "Daily 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
}
},
{
"id": "2",
"name": "Load Certifications",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
460,
300
],
"parameters": {
"operation": "readRows",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "agency_certifications",
"mode": "name"
},
"options": {}
}
},
{
"id": "3",
"name": "Classify Expiry",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
680,
300
],
"parameters": {
"jsCode": "const today = new Date();\nconst items = $input.all().map(item => {\n const row = item.json;\n const expiry = new Date(row.expiry_date);\n const daysUntil = Math.floor((expiry - today) / (1000 * 60 * 60 * 24));\n let urgency = 'OK';\n if (daysUntil < 0) urgency = 'EXPIRED';\n else if (daysUntil <= 30) urgency = 'CRITICAL';\n else if (daysUntil <= 60) urgency = 'URGENT';\n else if (daysUntil <= 90) urgency = 'WARNING';\n else if (daysUntil <= 120) urgency = 'NOTICE';\n const dedup_key = row.agency_id + '_' + row.certification_type + '_' + new Date().toISOString().slice(0, 10);\n return { json: { ...row, daysUntil, urgency, dedup_key } };\n});\nreturn items.filter(i => i.json.urgency !== 'OK');"
}
},
{
"id": "4",
"name": "Slack CS Alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 2.2,
"position": [
900,
200
],
"parameters": {
"channel": "#customer-success-gov",
"text": "={{$json.urgency}}: {{$json.agency_name}} \u2014 {{$json.certification_type}} expires {{$json.expiry_date}} ({{$json.daysUntil}} days). Account Manager: {{$json.account_manager}}. Tier: {{$json.tier}}",
"otherOptions": {}
}
},
{
"id": "5",
"name": "Email Account Manager",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
900,
400
],
"parameters": {
"operation": "send",
"toList": "={{$json.account_manager_email}}",
"subject": "=[{{$json.urgency}}] {{$json.agency_name}} \u2014 {{$json.certification_type}} Renewal Required",
"message": "=Certification Renewal Alert\\n\\nAgency: {{$json.agency_name}}\\nCertification: {{$json.certification_type}}\\nExpiry: {{$json.expiry_date}} ({{$json.daysUntil}} days)\\nStatus: {{$json.urgency}}\\n\\nAction Required:\\n- STATERAMP_HIGH/MODERATE: Initiate StateRAMP annual continuous monitoring submission via StateRAMP PMO portal\\n- FIRSTNET_BAND14: Renew FirstNet Authority authentication credentials\\n- CJIS_SECURITY_ADDENDUM: Obtain updated signed Security Addendum from agency head\\n- CALEA_CAPABILITY_TEST: Schedule annual CALEA technical capability demonstration with FBI/DOJ\\n\\nExpired certifications block new agency seat provisioning and may trigger ATO suspension.\\n\\n\u2014 FlowKit Compliance Monitor",
"options": {}
}
}
],
"connections": {
"Daily 8AM": {
"main": [
[
{
"node": "Load Certifications",
"type": "main",
"index": 0
}
]
]
},
"Load Certifications": {
"main": [
[
{
"node": "Classify Expiry",
"type": "main",
"index": 0
}
]
]
},
"Classify Expiry": {
"main": [
[
{
"node": "Slack CS Alert",
"type": "main",
"index": 0
},
{
"node": "Email Account Manager",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 5: Weekly Public Safety Platform KPI Dashboard
Who needs it: GovTech SaaS CEOs and CISOs tracking revenue, compliance, and operational health in one view.
KPIs in the report:
- MRR + WoW% change (PSAP seat revenue)
- Active PSAPs on platform
- NG911/ESInet aggregate uptime percentage
- CJIS incidents in last 30 days
- Certification renewals due in ≤30 days
- StateRAMP ATO status
The governance angle: StateRAMP Continuous Monitoring requires a designated Authorizing Official (AO) to review monthly security metrics. This report BCC'd to your StateRAMP ATO officer closes that evidence gap automatically — every Monday, documented, timestamped, in your ATO evidence package.
{
"name": "Weekly Public Safety Platform KPI Dashboard",
"nodes": [
{
"id": "1",
"name": "Monday 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.1,
"position": [
240,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
}
},
{
"id": "2",
"name": "Load Platform Metrics",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
460,
200
],
"parameters": {
"operation": "readRows",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "platform_metrics",
"mode": "name"
},
"options": {}
}
},
{
"id": "3",
"name": "Load Compliance Events",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 4,
"position": [
460,
400
],
"parameters": {
"operation": "readRows",
"documentId": {
"__rl": true,
"value": "{{YOUR_SPREADSHEET_ID}}",
"mode": "id"
},
"sheetName": {
"__rl": true,
"value": "cjis_incident_log",
"mode": "name"
},
"options": {}
}
},
{
"id": "4",
"name": "Merge Datasets",
"type": "n8n-nodes-base.merge",
"typeVersion": 3,
"position": [
680,
300
],
"parameters": {
"mode": "combine",
"combineBy": "combineAll",
"options": {}
}
},
{
"id": "5",
"name": "Compute KPIs",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
900,
300
],
"parameters": {
"jsCode": "const allItems = $input.all().map(i => i.json);\nconst metrics = allItems.find(i => i.mrr_usd !== undefined) || {};\nconst prevMetrics = allItems.find(i => i.prev_mrr_usd !== undefined) || {};\nconst events = allItems.filter(i => i.event_type !== undefined);\nconst mrrCurrent = parseFloat(metrics.mrr_usd || 0);\nconst mrrPrev = parseFloat(metrics.prev_mrr_usd || mrrCurrent);\nconst mrrWoW = mrrPrev > 0 ? (((mrrCurrent - mrrPrev) / mrrPrev) * 100).toFixed(1) : '0.0';\nconst cjisIncidents30d = events.filter(e => {\n const d = new Date(e.logged_at || e.event_date || '2000-01-01');\n return (Date.now() - d.getTime()) < 30 * 24 * 3600000 && e.event_type && e.event_type.includes('CJIS');\n}).length;\nconst activePsaps = parseInt(metrics.active_psaps || 0);\nconst ng911Uptime = parseFloat(metrics.ng911_uptime_pct || 99.9).toFixed(3);\nconst certRenewals30d = parseInt(metrics.certs_expiring_30d || 0);\nconst stateRampStatus = metrics.stateramp_ato_status || 'ACTIVE';\nconst flag = pct => parseFloat(pct) < 0 ? '\ud83d\udd34' : parseFloat(pct) > 5 ? '\ud83d\udfe2' : '\ud83d\udfe1';\nconst html = '<h2>FlowKit Public Safety Platform \u2014 Weekly KPI Report</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>MRR</td><td>$' + mrrCurrent.toLocaleString() + '</td><td>' + flag(mrrWoW) + ' ' + mrrWoW + '%</td></tr>' +\n '<tr><td>Active PSAPs</td><td>' + activePsaps + '</td><td>\u2014</td></tr>' +\n '<tr><td>NG911/ESInet Uptime</td><td>' + ng911Uptime + '%</td><td>' + (parseFloat(ng911Uptime) >= 99.999 ? '\ud83d\udfe2' : parseFloat(ng911Uptime) >= 99.9 ? '\ud83d\udfe1' : '\ud83d\udd34') + '</td></tr>' +\n '<tr><td>CJIS Incidents (30d)</td><td>' + cjisIncidents30d + '</td><td>' + (cjisIncidents30d > 0 ? '\ud83d\udd34' : '\ud83d\udfe2') + '</td></tr>' +\n '<tr><td>Cert Renewals \u226430d</td><td>' + certRenewals30d + '</td><td>' + (certRenewals30d > 0 ? '\ud83d\udfe1' : '\ud83d\udfe2') + '</td></tr>' +\n '<tr><td>StateRAMP ATO Status</td><td>' + stateRampStatus + '</td><td>' + (stateRampStatus === 'ACTIVE' ? '\ud83d\udfe2' : '\ud83d\udd34') + '</td></tr>' +\n '</table>';\nreturn [{ json: { html, mrrCurrent, mrrWoW, activePsaps, ng911Uptime, cjisIncidents30d, certRenewals30d, stateRampStatus } }];"
}
},
{
"id": "6",
"name": "Email Executive Team",
"type": "n8n-nodes-base.gmail",
"typeVersion": 2.1,
"position": [
1120,
300
],
"parameters": {
"operation": "send",
"toList": "ceo@yourcompany.com",
"ccList": "ciso@yourcompany.com, stateramp-ato@yourcompany.com",
"subject": "=[Weekly KPI] Public Safety Platform \u2014 MRR {{$json.mrrCurrent}} | NG911 Uptime {{$json.ng911Uptime}}% | CJIS Incidents {{$json.cjisIncidents30d}}",
"message": "={{$json.html}}",
"options": {
"appendAttribution": false
}
}
}
],
"connections": {
"Monday 8AM": {
"main": [
[
{
"node": "Load Platform Metrics",
"type": "main",
"index": 0
},
{
"node": "Load Compliance Events",
"type": "main",
"index": 0
}
]
]
},
"Load Platform Metrics": {
"main": [
[
{
"node": "Merge Datasets",
"type": "main",
"index": 0
}
]
]
},
"Load Compliance Events": {
"main": [
[
{
"node": "Merge Datasets",
"type": "main",
"index": 1
}
]
]
},
"Merge Datasets": {
"main": [
[
{
"node": "Compute KPIs",
"type": "main",
"index": 0
}
]
]
},
"Compute KPIs": {
"main": [
[
{
"node": "Email Executive Team",
"type": "main",
"index": 0
}
]
]
}
}
}
Implementation Notes
CJIS cloud deployment: CJIS Policy v5.9 §5.13 requires your cloud service provider to be listed on the CJIS Cloud Provider List or have a completed Cloud Provider Audit. Self-hosted n8n in AWS GovCloud (US), Azure Government, or your own FedRAMP/StateRAMP-authorized infrastructure satisfies this requirement without a separate CSP audit.
NG911 data sensitivity: ESInet call records, CAD incident data, and location routing tables are CJI in most state implementations. Any workflow touching this data must run within the CJIS-authorized boundary — not in a multi-tenant SaaS automation platform.
StateRAMP vs FedRAMP: StateRAMP uses FedRAMP baselines but adds state-specific controls and a separate PMO. If you have FedRAMP Moderate ATO, you can pursue StateRAMP Authorized status via the equivalency pathway — but your automation workflows touching state agency data must still stay within the StateRAMP-authorized boundary.
n8n vs Zapier/Make for GovTech
| Requirement | n8n Self-Hosted | Zapier/Make |
|---|---|---|
| CJIS CSP compliance | Run in your authorized boundary | Requires separate CJIS CSP audit |
| StateRAMP scope | Stays in-scope by design | External CSP = expanded ATO scope |
| CALEA audit trail | Git-versioned workflow JSON = evidence | No audit trail for workflow logic |
| NG911 data sovereignty | ESInet data never leaves enclave | Passes through cloud iPaaS |
| FISMA/NIST evidence | Git history = evidence artifact | No NIST 800-53 controls documentation |
All five workflow JSONs above are import-ready — paste them into Settings → Import workflow in your n8n instance. Replace the Sheets document IDs, Slack channel names, and email addresses with your configuration.
For the full FlowKit n8n template library (including pre-built variants for each tier), visit stripeai.gumroad.com.
Top comments (0)