DEV Community

Alex Kane
Alex Kane

Posted on

n8n for UtilityTech SaaS Vendors: 5 Automations for NERC CIP v7, FERC Order 2222, PHMSA Part 192, and State PUC Compliance

Utility-scale SaaS vendors face a compliance reality their counterparts in other verticals don't: NERC CIP-003-8 R2 defines 'external routable connectivity' as any communication path between a BES Cyber System and an external network. When your team routes grid operational data through Zapier or Make.com, you've created that connectivity — and the cloud iPaaS vendor is now part of your CIP compliance scope.

This isn't a theoretical risk. It's a literal violation of the standard, and FERC enforcement actions against utilities for CIP gaps run into six-figure penalties per violation per day.

This article gives you five production-ready n8n workflow templates built for utility, grid, pipeline, and nuclear SaaS vendors. Each includes complete, importable JSON — copy it into your n8n instance and adapt the Sheets schema to your data.


Who These Workflows Are For

These automations target SaaS companies selling into regulated utility markets:

Tier Example Customers Primary Regulations
INVESTOR_OWNED_UTILITY Duke Energy, Dominion, Xcel scale NERC CIP v7, FERC Form 1, state PUC
MUNICIPAL_UTILITY City-owned electric/gas utilities NERC CIP, state PUC, EPA CAA §111(d)
RURAL_ELECTRIC_COOPERATIVE NRECA member co-ops NERC CIP, USDA RUS, state PUC
GAS_DISTRIBUTION_SAAS_VENDOR Pipeline operators PHMSA 49 CFR §192/§195
DER_AGGREGATION_PLATFORM Virtual power plant operators FERC Order 2222, ISO/RTO rules
NUCLEAR_COMPLIANCE_SAAS NRC licensees NRC 10 CFR §73.54, §50.72
STATE_PUC_REGULATORY_TECH Rate case filing tools State PUC, IRP filings

Workflow 1: UtilityTech Customer Onboarding Drip

What it does: Segments new customers by tier and compliance flag, injects the relevant regulatory context into each email, then sends a 14-day onboarding sequence that drives time-to-value.

The compliance injection: Customers flagged NERC_CIP_HIGH_IMPACT_BES get a Day 0 note explaining why BES Cyber System data must stay within their Electronic Security Perimeter — and why their self-hosted n8n instance satisfies that requirement. Same logic for NRC, PHMSA, and FERC Order 2222 customers.

{
  "name": "UtilityTech Customer Onboarding Drip",
  "nodes": [
    {
      "id": "w1n1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {
        "path": "utility-customer-signup",
        "responseMode": "responseNode",
        "responseData": "firstEntryJson"
      }
    },
    {
      "id": "w1n2",
      "name": "Segment Tier",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "jsCode": "const inp = $input.first().json;\nconst tier = inp.customer_tier || 'MUNICIPAL_UTILITY';\nconst flags = inp.compliance_flags || [];\nconst name = inp.contact_name || 'Team';\nconst company = inp.company_name || 'Your Company';\nconst subjectMap = {\n  INVESTOR_OWNED_UTILITY: 'Welcome to FlowKit \u2014 NERC CIP & FERC Compliance Automation',\n  MUNICIPAL_UTILITY: 'Welcome to FlowKit \u2014 Grid & Utility Compliance Automation',\n  RURAL_ELECTRIC_COOPERATIVE: 'Welcome to FlowKit \u2014 Cooperative Grid Operations Automation',\n  GAS_DISTRIBUTION_SAAS_VENDOR: 'Welcome to FlowKit \u2014 PHMSA Pipeline Integrity Automation',\n  DER_AGGREGATION_PLATFORM: 'Welcome to FlowKit \u2014 FERC Order 2222 & VPP Automation',\n  NUCLEAR_COMPLIANCE_SAAS: 'Welcome to FlowKit \u2014 NRC Cyber Security & Compliance Automation',\n  STATE_PUC_REGULATORY_TECH: 'Welcome to FlowKit \u2014 PUC Rate Case & Regulatory Filing Automation'\n};\nlet complianceNote = 'Your n8n instance runs on your own infrastructure \u2014 utility operational data stays within your control boundary.';\nif (flags.includes('NERC_CIP_HIGH_IMPACT_BES')) {\n  complianceNote = 'NERC CIP Note: Under CIP-003-8 R2, BES Cyber System data routed through cloud iPaaS creates external routable connectivity \u2014 a CIP compliance gap. Self-hosted n8n keeps BES data within your Electronic Security Perimeter.';\n} else if (flags.includes('NRC_NUCLEAR_LICENSEE')) {\n  complianceNote = 'NRC Note: 10 CFR \u00a773.54 requires all digital pathways in your cyber security plan. Self-hosted n8n operates inside your plan perimeter \u2014 no cloud vendor expansion of your attack surface.';\n} else if (flags.includes('PHMSA_GAS_OPERATOR')) {\n  complianceNote = 'PHMSA Note: 49 CFR \u00a7192.911 IMP data requirements apply. Self-hosted n8n keeps pipeline integrity records within your operational boundary.';\n} else if (flags.includes('FERC_ORDER_2222_DER')) {\n  complianceNote = 'FERC Order 2222 Note: DER dispatch data and bid curves are market-sensitive. Self-hosted n8n keeps aggregation data within your ISO/RTO exchange boundary.';\n}\nreturn [{json: {customer_id: inp.customer_id, email: inp.email, name, company, tier, flags,\n  subject: subjectMap[tier] || 'Welcome to FlowKit \u2014 Utility Compliance Automation',\n  compliance_note: complianceNote, enrolled_at: new Date().toISOString()}}];"
      }
    },
    {
      "id": "w1n3",
      "name": "Gmail Day 0 Welcome",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$json.email}}",
        "subject": "={{$json.subject}}",
        "message": "<h2>Welcome to FlowKit, {name}!</h2><p>Your FlowKit workspace is active.</p><p><strong>{note}</strong></p><h3>Quick-Start Checklist</h3><ul><li>Connect your Google Sheets data sources</li><li>Configure Slack channels (#regulatory-compliance, #grid-ops)</li><li>Activate the compliance deadline tracker (Workflow 2)</li><li>Set up the API health monitor (Workflow 3)</li></ul><p>Reply to this email \u2014 we respond within 4 business hours.</p>",
        "options": {
          "replyTo": "support@flowkithq.com"
        }
      }
    },
    {
      "id": "w1n4",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        900,
        300
      ],
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "w1n5",
      "name": "Gmail Day 3 Integration",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$node['Segment Tier'].json.email}}",
        "subject": "Your FlowKit integration checklist",
        "message": "<h2>Day 3 Check-In</h2><p>Top integrations for utility SaaS teams: Google Sheets compliance tracker, Slack #regulatory-compliance, SCADA/EMS API health monitor, state PUC e-filing deadline sync.</p><p>Reply if you want a 15-min setup call.</p>"
      }
    },
    {
      "id": "w1n6",
      "name": "Wait 4 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "amount": 4,
        "unit": "days"
      }
    },
    {
      "id": "w1n7",
      "name": "Gmail Day 7 Use Case",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1560,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$node['Segment Tier'].json.email}}",
        "subject": "The automation that saves utility compliance teams 6 hours/week",
        "message": "<h2>The Compliance Deadline Tracker</h2><p>The #1 FlowKit automation for utility SaaS customers: a daily NERC CIP / FERC / PHMSA / NRC deadline tracker that pings Slack before anything goes overdue. <a href='https://stripeai.gumroad.com'>Get the full workflow JSON \u2192</a></p>"
      }
    },
    {
      "id": "w1n8",
      "name": "Wait 7 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "position": [
        1780,
        300
      ],
      "parameters": {
        "amount": 7,
        "unit": "days"
      }
    },
    {
      "id": "w1n9",
      "name": "Gmail Day 14 Exec",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        2000,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$node['Segment Tier'].json.email}}",
        "subject": "14-day check-in \u2014 how is FlowKit working?",
        "message": "<p>Hi {{$node['Segment Tier'].json.name}}, two weeks in \u2014 which workflows are running, and what's still on your list? Our Complete Bundle has all 15 templates pre-configured for NERC CIP / FERC / PHMSA / NRC at <a href='https://stripeai.gumroad.com'>stripeai.gumroad.com</a>.</p>"
      }
    },
    {
      "id": "w1n10",
      "name": "Log Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        2220,
        300
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "onboarding_log",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Segment Tier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Segment Tier": {
      "main": [
        [
          {
            "node": "Gmail Day 0 Welcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Day 0 Welcome": {
      "main": [
        [
          {
            "node": "Wait 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Days": {
      "main": [
        [
          {
            "node": "Gmail Day 3 Integration",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Day 3 Integration": {
      "main": [
        [
          {
            "node": "Wait 4 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 Days": {
      "main": [
        [
          {
            "node": "Gmail Day 7 Use Case",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Day 7 Use Case": {
      "main": [
        [
          {
            "node": "Wait 7 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 7 Days": {
      "main": [
        [
          {
            "node": "Gmail Day 14 Exec",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Day 14 Exec": {
      "main": [
        [
          {
            "node": "Log Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: NERC CIP v7 / FERC / PHMSA / NRC Compliance Deadline Tracker

What it does: Reads a Google Sheet of upcoming regulatory deadlines, classifies each as OVERDUE / CRITICAL / URGENT / WARNING / NOTICE, and fires Slack alerts plus owner emails for anything within 45 days.

12 deadline types supported:

Deadline Type Regulatory Clock
NERC_CIP_ANNUAL_SELF_CERTIFICATION Feb 15 annually — ERO Enterprise submission
NERC_CIP_PATCH_MANAGEMENT_35_DAY CIP-007-6 R2.2 — 35 calendar days from vendor notification
FERC_FORM_1_ANNUAL March 31 — annual electric utility report
FERC_ORDER_2222_PARTICIPATION_MODEL FERC docket compliance per ISO/RTO tariff
PHMSA_GAS_INTEGRITY_ANNUAL 49 CFR §192.937 — annual IMP reassessment
PHMSA_OPERATOR_QUALIFICATION_RENEWAL 49 CFR §192.805 — OQ plan renewal
NRC_10_CFR_50_ANNUAL_REPORT §50.71(b) — 6 months after fiscal year end
NRC_CYBER_SECURITY_ANNUAL_REVIEW 10 CFR §73.54(e) — annual CS plan review
STATE_PUC_RATE_CASE_ANNUAL Per state PUC schedule
STATE_PUC_OUTAGE_REPORT 24–48h per state commission rules
EPA_CLEAN_POWER_ANNUAL CAA §111(d) — annual compliance report
SOC2_TYPE2_RENEWAL Annual audit renewal
{
  "name": "NERC CIP v7 / FERC / PHMSA / NRC Deadline Tracker",
  "nodes": [
    {
      "id": "w2n1",
      "name": "Daily 7AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "w2n2",
      "name": "Read Deadlines Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        460,
        300
      ],
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "utility_compliance_deadlines",
          "mode": "name"
        },
        "options": {}
      }
    },
    {
      "id": "w2n3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().map(r => r.json);\nconst now = new Date();\nconst results = [];\nfor (const row of rows) {\n  if (!row.deadline_date || !row.regulation_type || !row.owner_email) continue;\n  const dl = new Date(row.deadline_date);\n  const daysLeft = Math.ceil((dl - now) / 86400000);\n  let urgency = 'NOTICE';\n  if (daysLeft < 0) urgency = 'OVERDUE';\n  else if (daysLeft <= 7) urgency = 'CRITICAL';\n  else if (daysLeft <= 21) urgency = 'URGENT';\n  else if (daysLeft <= 45) urgency = 'WARNING';\n  else if (daysLeft <= 90) urgency = 'NOTICE';\n  else continue;\n  const clockMap = {\n    NERC_CIP_ANNUAL_SELF_CERTIFICATION: 'NERC CIP-007-6 R2.2 \u2014 annual Feb 15 submission to ERO Enterprise',\n    NERC_CIP_PATCH_MANAGEMENT_35_DAY: 'NERC CIP-007-6 R2.2 \u2014 35 calendar days from vendor notification',\n    FERC_FORM_1_ANNUAL: 'FERC Form No. 1 \u2014 March 31 annual electric utility report',\n    FERC_ORDER_2222_PARTICIPATION_MODEL: 'FERC Order 2222 \u2014 DER aggregator participation model compliance',\n    PHMSA_GAS_INTEGRITY_ANNUAL: 'PHMSA 49 CFR \u00a7192.937 \u2014 annual integrity management reassessment',\n    PHMSA_OPERATOR_QUALIFICATION_RENEWAL: 'PHMSA 49 CFR \u00a7192.805 \u2014 operator qualification plan renewal',\n    NRC_10_CFR_50_ANNUAL_REPORT: 'NRC 10 CFR \u00a750.71(b) \u2014 annual safety report within 6 months of fiscal year end',\n    NRC_CYBER_SECURITY_ANNUAL_REVIEW: 'NRC 10 CFR \u00a773.54(e) \u2014 annual cyber security plan review',\n    STATE_PUC_RATE_CASE_ANNUAL: 'State PUC \u2014 annual rate case or IRP filing per state schedule',\n    STATE_PUC_OUTAGE_REPORT: 'State PUC \u2014 major outage report 24-48h per state rules',\n    EPA_CLEAN_POWER_ANNUAL: 'EPA CAA \u00a7111(d) \u2014 annual clean power plan compliance report',\n    SOC2_TYPE2_RENEWAL: 'SOC 2 Type II \u2014 annual audit renewal'\n  };\n  results.push({json: {\n    ...row, daysLeft, urgency,\n    regulatory_clock: clockMap[row.regulation_type] || row.regulation_type,\n    alert_message: `[${urgency}] ${row.regulation_type}: ${row.deadline_name} \u2014 ${daysLeft < 0 ? Math.abs(daysLeft) + ' days OVERDUE' : daysLeft + ' days remaining'} (${row.deadline_date}). Owner: ${row.owner_email}. ${clockMap[row.regulation_type] || ''}`\n  }});\n}\nreturn results.length ? results : [{json: {urgency: 'NONE', alert_message: 'No deadlines require attention today.'}}];"
      }
    },
    {
      "id": "w2n4",
      "name": "Skip if None",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{$json.urgency}}",
              "rightValue": "NONE",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "w2n5",
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1120,
        200
      ],
      "parameters": {
        "operation": "post",
        "channel": "#regulatory-compliance",
        "text": "={{$json.alert_message}}",
        "otherOptions": {}
      }
    },
    {
      "id": "w2n6",
      "name": "Gmail to Owner",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1120,
        400
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$json.owner_email}}",
        "subject": "=[{{$json.urgency}}] {{$json.regulation_type}}: Action Required \u2014 {{$json.deadline_name}}",
        "message": "<p>{{$json.alert_message}}</p><p>Regulatory clock: {{$json.regulatory_clock}}</p>"
      }
    },
    {
      "id": "w2n7",
      "name": "Log Alert",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "deadline_alerts",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Daily 7AM": {
      "main": [
        [
          {
            "node": "Read Deadlines Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Deadlines Sheet": {
      "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 Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail to Owner",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Slack Alert": {
      "main": [
        [
          {
            "node": "Log Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail to Owner": {
      "main": [
        [
          {
            "node": "Log Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: Grid & Pipeline API Health Monitor

What it does: Pings five critical infrastructure API endpoints every 5 minutes — SCADA grid, FERC eSubmission, PHMSA pipeline integrity, NRC event reporting, and state PUC portal. Classifies failures as CRITICAL or DEGRADED with the specific regulatory risk annotation, fires Slack alerts to #grid-ops, and logs all incidents to Google Sheets.

Why this matters beyond uptime: NERC CIP-007-6 R6.1 requires logging of all security events on BES Cyber Systems. A health monitor that tracks your SCADA API is also your CIP-007 evidence log. Your n8n job logs are timestamped, structured, and auditable.

{
  "name": "Grid & Pipeline API Health Monitor",
  "nodes": [
    {
      "id": "w3n1",
      "name": "Every 5 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      }
    },
    {
      "id": "w3n2",
      "name": "Check SCADA Grid API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        180
      ],
      "parameters": {
        "method": "GET",
        "url": "https://your-scada-grid-api.internal/health",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 5000
        },
        "sendBody": false
      }
    },
    {
      "id": "w3n3",
      "name": "Check FERC eSubmission API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        280
      ],
      "parameters": {
        "method": "GET",
        "url": "https://esubmit.ferc.gov/api/health",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 5000
        },
        "sendBody": false
      }
    },
    {
      "id": "w3n4",
      "name": "Check PHMSA Pipeline API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        380
      ],
      "parameters": {
        "method": "GET",
        "url": "https://your-phmsa-pipeline-api.internal/health",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 5000
        },
        "sendBody": false
      }
    },
    {
      "id": "w3n5",
      "name": "Check NRC Event API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        480
      ],
      "parameters": {
        "method": "GET",
        "url": "https://your-nrc-event-api.internal/health",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 5000
        },
        "sendBody": false
      }
    },
    {
      "id": "w3n6",
      "name": "Check State PUC Portal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "position": [
        460,
        580
      ],
      "parameters": {
        "method": "GET",
        "url": "https://your-state-puc-portal.gov/api/health",
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          },
          "timeout": 5000
        },
        "sendBody": false
      }
    },
    {
      "id": "w3n7",
      "name": "Merge Health Results",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 3,
      "position": [
        680,
        380
      ],
      "parameters": {
        "mode": "combine",
        "combineBy": "combineAll",
        "options": {}
      }
    },
    {
      "id": "w3n8",
      "name": "Classify Issues",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        900,
        380
      ],
      "parameters": {
        "jsCode": "const checks = $input.all().map(r => r.json);\nconst issues = [];\nfor (const check of checks) {\n  const ok = check.statusCode >= 200 && check.statusCode < 300;\n  const slow = (check.responseTime || 0) > 3000;\n  if (!ok || slow) {\n    const endpointMap = {\n      scada_grid_api: {label: 'SCADA Grid API', reg: 'NERC CIP-007-6 R6.1 \u2014 BES Cyber System asset monitoring'},\n      ferc_esubmission_api: {label: 'FERC eSubmission API', reg: 'FERC Order 2222 \u2014 DER participation model filings'},\n      phmsa_pipeline_api: {label: 'PHMSA Pipeline Integrity API', reg: 'PHMSA 49 CFR \u00a7192.937 \u2014 IMP reassessment data'},\n      nrc_event_api: {label: 'NRC Event Reporting API', reg: 'NRC 10 CFR \u00a750.72 \u2014 8-hour notification window'},\n      state_puc_portal_api: {label: 'State PUC Portal API', reg: 'State PUC \u2014 rate case and outage report filings'}\n    };\n    const info = endpointMap[check.endpoint_key] || {label: check.endpoint_key, reg: 'Unknown regulation'};\n    issues.push({json: {\n      endpoint_key: check.endpoint_key,\n      endpoint_label: info.label,\n      regulatory_note: info.reg,\n      status: ok ? 'DEGRADED' : 'CRITICAL',\n      statusCode: check.statusCode,\n      responseTime: check.responseTime,\n      checked_at: new Date().toISOString(),\n      message: `[${ok ? 'DEGRADED' : 'CRITICAL'}] ${info.label} \u2014 HTTP ${check.statusCode}, ${check.responseTime}ms. ${info.reg}`\n    }});\n  }\n}\nreturn issues.length ? issues : [{json: {status: 'OK', message: 'All grid/pipeline endpoints healthy.'}}];"
      }
    },
    {
      "id": "w3n9",
      "name": "Has Issues?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        1120,
        380
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false
          },
          "conditions": [
            {
              "id": "c1",
              "leftValue": "={{$json.status}}",
              "rightValue": "OK",
              "operator": {
                "type": "string",
                "operation": "notEquals"
              }
            }
          ],
          "combinator": "and"
        }
      }
    },
    {
      "id": "w3n10",
      "name": "Slack #grid-ops",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        1340,
        280
      ],
      "parameters": {
        "operation": "post",
        "channel": "#grid-ops",
        "text": "={{$json.message}}",
        "otherOptions": {}
      }
    },
    {
      "id": "w3n11",
      "name": "Log Incident",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1340,
        480
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "api_health_log",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Every 5 Min": {
      "main": [
        [
          {
            "node": "Check SCADA Grid API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check FERC eSubmission API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check PHMSA Pipeline API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check NRC Event API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check State PUC Portal",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check SCADA Grid API": {
      "main": [
        [
          {
            "node": "Merge Health Results",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check FERC eSubmission API": {
      "main": [
        [
          {
            "node": "Merge Health Results",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Check PHMSA Pipeline API": {
      "main": [
        [
          {
            "node": "Merge Health Results",
            "type": "main",
            "index": 2
          }
        ]
      ]
    },
    "Check NRC Event API": {
      "main": [
        [
          {
            "node": "Merge Health Results",
            "type": "main",
            "index": 3
          }
        ]
      ]
    },
    "Check State PUC Portal": {
      "main": [
        [
          {
            "node": "Merge Health Results",
            "type": "main",
            "index": 4
          }
        ]
      ]
    },
    "Merge Health Results": {
      "main": [
        [
          {
            "node": "Classify Issues",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Issues": {
      "main": [
        [
          {
            "node": "Has Issues?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Has Issues?": {
      "main": [
        [
          {
            "node": "Slack #grid-ops",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log Incident",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: Utility Compliance Incident Pipeline

What it does: Accepts incident events via webhook, classifies each by type and regulatory clock, routes CRITICAL incidents to #incident-command and executive email, and routes WARNING incidents to #compliance-ops — all within seconds of the webhook firing.

Fastest regulatory clocks:

Incident Type Clock Regulator
NERC_CIP_CYBER_INCIDENT_REPORTABLE IMMEDIATE — 1 hour max E-ISAC portal (CIP-008-6 R4)
NRC_10_CFR_50_72_EVENT 8 hours NRC Operations Center
PHMSA_GAS_PIPELINE_INCIDENT 1 hour telephonic PHMSA National Response Center
FERC_ORDER_2222_DER_NONPERFORMANCE 24 hours ISO/RTO per Order 2222
STATE_PUC_MAJOR_OUTAGE 24–48 hours State PUC commission
GRID_BES_RELIABILITY_EVENT 1 hour E-ISAC
EPA_CLEAN_AIR_EXCEEDANCE 72 hours EPA
DATA_BREACH_UTILITY_OPS 72 hours State breach law

The NERC_CIP_CYBER_INCIDENT_REPORTABLE path has zero tolerance: CIP-008-6 R4 requires E-ISAC portal submission within one hour of identifying a reportable cyber security incident. The workflow fires immediately and sets response_deadline: 'IMMEDIATE' — there is no queue, no wait node, no retry window.

{
  "name": "Utility Compliance Incident Pipeline",
  "nodes": [
    {
      "id": "w4n1",
      "name": "Incident Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {
        "path": "utility-incident",
        "responseMode": "responseNode",
        "responseData": "firstEntryJson"
      }
    },
    {
      "id": "w4n2",
      "name": "Classify Incident & Clock",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        300
      ],
      "parameters": {
        "jsCode": "const inp = $input.first().json;\nconst incidentType = inp.incident_type;\nconst responseMap = {\n  NERC_CIP_CYBER_INCIDENT_REPORTABLE: {severity: 'CRITICAL', responseHours: 0, clock: 'IMMEDIATE \u2014 CIP-008-6 R4: report to E-ISAC portal within 1 hour of identification. No grace period.', channel: '#incident-command', escalateTo: 'SVP Engineering + CISO + Legal'},\n  NRC_10_CFR_50_72_EVENT: {severity: 'CRITICAL', responseHours: 8, clock: '8-hour NRC Operations Center notification \u2014 10 CFR \u00a750.72(a). Clock starts at event identification.', channel: '#incident-command', escalateTo: 'VP Engineering + Nuclear Safety Officer'},\n  PHMSA_GAS_PIPELINE_INCIDENT: {severity: 'CRITICAL', responseHours: 1, clock: '1-hour telephonic report to PHMSA NRC \u2014 49 CFR \u00a7191.5. Follow with written report within 30 days.', channel: '#incident-command', escalateTo: 'Pipeline Integrity Manager + Legal'},\n  FERC_ORDER_2222_DER_NONPERFORMANCE: {severity: 'WARNING', responseHours: 24, clock: '24-hour report to ISO/RTO per FERC Order 2222 compliance rules.', channel: '#compliance-ops', escalateTo: 'DER Operations Lead'},\n  STATE_PUC_MAJOR_OUTAGE: {severity: 'WARNING', responseHours: 24, clock: 'State PUC major outage reporting \u2014 24-48h per state commission rules.', channel: '#compliance-ops', escalateTo: 'Regulatory Affairs Manager'},\n  GRID_BES_RELIABILITY_EVENT: {severity: 'CRITICAL', responseHours: 1, clock: '1-hour E-ISAC notification per NERC reliability standard requirements.', channel: '#incident-command', escalateTo: 'Grid Operations Manager + CISO'},\n  EPA_CLEAN_AIR_EXCEEDANCE: {severity: 'WARNING', responseHours: 72, clock: '72-hour EPA notification per CAA \u00a7111(d) compliance requirements.', channel: '#compliance-ops', escalateTo: 'Environmental Compliance Manager'},\n  DATA_BREACH_UTILITY_OPS: {severity: 'WARNING', responseHours: 72, clock: '72-hour state breach notification law. Check state-specific rules for utility customer data.', channel: '#compliance-ops', escalateTo: 'CISO + Legal'}\n};\nconst resp = responseMap[incidentType] || {severity: 'WARNING', responseHours: 24, clock: 'Unknown incident type \u2014 default 24h response.', channel: '#compliance-ops', escalateTo: 'Compliance Officer'};\nconst deadlineTs = new Date(Date.now() + resp.responseHours * 3600000).toISOString();\nreturn [{json: {\n  ...inp, ...resp,\n  response_deadline: resp.responseHours === 0 ? 'IMMEDIATE' : deadlineTs,\n  alert_message: `[${resp.severity}] ${incidentType}: ${inp.description || 'No description'}. Regulatory clock: ${resp.clock}. Response deadline: ${resp.responseHours === 0 ? 'IMMEDIATE' : deadlineTs}. Escalate to: ${resp.escalateTo}`\n}}];"
      }
    },
    {
      "id": "w4n3",
      "name": "CRITICAL or WARNING?",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        680,
        300
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.severity}}",
                    "rightValue": "CRITICAL",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "outputKey": "CRITICAL"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "conditions": [
                  {
                    "leftValue": "={{$json.severity}}",
                    "rightValue": "WARNING",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ],
                "combinator": "and"
              },
              "outputKey": "WARNING"
            }
          ]
        }
      }
    },
    {
      "id": "w4n4",
      "name": "Slack #incident-command",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        900,
        180
      ],
      "parameters": {
        "operation": "post",
        "channel": "#incident-command",
        "text": "\ud83d\udea8 {{$json.alert_message}}",
        "otherOptions": {}
      }
    },
    {
      "id": "w4n5",
      "name": "Gmail Escalation",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "={{$json.escalateTo}}",
        "subject": "[CRITICAL] {{$json.incident_type}} \u2014 Regulatory Clock Active",
        "message": "<p><strong>{{$json.alert_message}}</strong></p><p>Incident ID: {{$json.incident_id}}</p><p>Reported at: {{$json.reported_at}}</p>"
      }
    },
    {
      "id": "w4n6",
      "name": "Slack #compliance-ops",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        900,
        420
      ],
      "parameters": {
        "operation": "post",
        "channel": "#compliance-ops",
        "text": "\u26a0\ufe0f {{$json.alert_message}}",
        "otherOptions": {}
      }
    },
    {
      "id": "w4n7",
      "name": "Log Incident Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1120,
        300
      ],
      "parameters": {
        "operation": "append",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "compliance_incidents",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "autoMapInputData",
          "value": {},
          "matchingColumns": []
        },
        "options": {}
      }
    },
    {
      "id": "w4n8",
      "name": "Webhook Response",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "position": [
        1340,
        300
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "={\"received\": true, \"incident_type\": \"{{$node['Classify Incident & Clock'].json.incident_type}}\", \"severity\": \"{{$node['Classify Incident & Clock'].json.severity}}\", \"response_deadline\": \"{{$node['Classify Incident & Clock'].json.response_deadline}}\"}"
      }
    }
  ],
  "connections": {
    "Incident Webhook": {
      "main": [
        [
          {
            "node": "Classify Incident & Clock",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident & Clock": {
      "main": [
        [
          {
            "node": "CRITICAL or WARNING?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "CRITICAL or WARNING?": {
      "main": [
        [
          {
            "node": "Slack #incident-command",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail Escalation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack #compliance-ops",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack #incident-command": {
      "main": [
        [
          {
            "node": "Log Incident Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail Escalation": {
      "main": [
        [
          {
            "node": "Log Incident Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack #compliance-ops": {
      "main": [
        [
          {
            "node": "Log Incident Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Incident Sheets": {
      "main": [
        [
          {
            "node": "Webhook Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Weekly UtilityTech SaaS KPI Dashboard

What it does: Every Monday at 8AM, reads your platform metrics from Google Sheets, computes week-over-week deltas using $getWorkflowStaticData, builds an HTML table covering active accounts, MRR, grid MWh monitored, pipeline miles covered, and all four compliance open-item counts (NERC CIP / PHMSA / FERC / NRC), then emails the CEO and BCCs the CISO and VP Sales.

{
  "name": "Weekly UtilityTech SaaS KPI Dashboard",
  "nodes": [
    {
      "id": "w5n1",
      "name": "Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "w5n2",
      "name": "Read Platform Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        460,
        300
      ],
      "parameters": {
        "operation": "read",
        "documentId": {
          "__rl": true,
          "value": "YOUR_SPREADSHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "platform_metrics_weekly",
          "mode": "name"
        },
        "options": {}
      }
    },
    {
      "id": "w5n3",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "jsCode": "const rows = $input.all().map(r => r.json);\nconst prevData = $getWorkflowStaticData('global');\nconst prev = prevData.lastWeek || {};\nconst calcDelta = (cur, p) => p ? (((cur - p) / p) * 100).toFixed(1) + '%' : 'N/A';\nconst cur = {\n  active_accounts: rows.filter(r => r.account_status === 'active').length,\n  mrr_usd: rows.reduce((s, r) => s + (parseFloat(r.mrr_usd) || 0), 0),\n  grid_mwh_monitored: rows.reduce((s, r) => s + (parseFloat(r.grid_mwh_monitored) || 0), 0),\n  pipeline_miles: rows.reduce((s, r) => s + (parseFloat(r.pipeline_miles) || 0), 0),\n  nerc_cip_open: rows.filter(r => r.nerc_cip_open === 'true' || r.nerc_cip_open === true).length,\n  phmsa_open: rows.filter(r => r.phmsa_open === 'true' || r.phmsa_open === true).length,\n  ferc_open: rows.filter(r => r.ferc_open === 'true' || r.ferc_open === true).length,\n  nrc_open: rows.filter(r => r.nrc_open === 'true' || r.nrc_open === true).length\n};\nprevData.lastWeek = cur;\n$setWorkflowStaticData('global', prevData);\nconst html = `<h2>UtilityTech SaaS \u2014 Weekly KPI Report</h2><table border='1' cellpadding='6'><tr><th>Metric</th><th>This Week</th><th>WoW Delta</th></tr><tr><td>Active Utility Accounts</td><td>${cur.active_accounts}</td><td>${calcDelta(cur.active_accounts, prev.active_accounts)}</td></tr><tr><td>MRR (USD)</td><td>$${cur.mrr_usd.toLocaleString()}</td><td>${calcDelta(cur.mrr_usd, prev.mrr_usd)}</td></tr><tr><td>Grid MWh Monitored</td><td>${cur.grid_mwh_monitored.toLocaleString()}</td><td>${calcDelta(cur.grid_mwh_monitored, prev.grid_mwh_monitored)}</td></tr><tr><td>Pipeline Miles Covered</td><td>${cur.pipeline_miles.toLocaleString()}</td><td>${calcDelta(cur.pipeline_miles, prev.pipeline_miles)}</td></tr><tr><th colspan='3'>Compliance Open Items</th></tr><tr><td>NERC CIP Open</td><td>${cur.nerc_cip_open}</td><td>\u2014</td></tr><tr><td>PHMSA Integrity Open</td><td>${cur.phmsa_open}</td><td>\u2014</td></tr><tr><td>FERC Order 2222 Open</td><td>${cur.ferc_open}</td><td>\u2014</td></tr><tr><td>NRC Reporting Open</td><td>${cur.nrc_open}</td><td>\u2014</td></tr></table>`;\nreturn [{json: {html, ...cur}}];"
      }
    },
    {
      "id": "w5n4",
      "name": "Gmail to CEO + CISO BCC",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        900,
        300
      ],
      "parameters": {
        "operation": "send",
        "toList": "ceo@yourcompany.com",
        "subject": "=UtilityTech SaaS \u2014 Weekly KPI Report {{new Date().toISOString().split('T')[0]}}",
        "message": "={{$json.html}}",
        "options": {
          "bccList": "ciso@yourcompany.com,vpsales@yourcompany.com"
        }
      }
    },
    {
      "id": "w5n5",
      "name": "Slack #leadership",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "position": [
        900,
        480
      ],
      "parameters": {
        "operation": "post",
        "channel": "#leadership",
        "text": "=\ud83d\udcca Weekly UtilityTech SaaS: {{$json.active_accounts}} accounts | MRR ${{$json.mrr_usd}} | Grid {{$json.grid_mwh_monitored}} MWh | Pipeline {{$json.pipeline_miles}} mi | NERC CIP open: {{$json.nerc_cip_open}} | PHMSA: {{$json.phmsa_open}} | FERC: {{$json.ferc_open}} | NRC: {{$json.nrc_open}}",
        "otherOptions": {}
      }
    }
  ],
  "connections": {
    "Monday 8AM": {
      "main": [
        [
          {
            "node": "Read Platform Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Platform Metrics": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Gmail to CEO + CISO BCC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack #leadership",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why Self-Hosted n8n Solves the NERC CIP-003-8 R2 Problem

Regulation Cloud iPaaS Risk Self-Hosted n8n Solution
NERC CIP-003-8 R2 BES Cyber System data in Zapier/Make = external routable connectivity = CIP violation n8n inside OT network segment — BES data stays within your ESP
NRC 10 CFR §73.54 Each cloud iPaaS vendor expands your cyber security plan attack surface n8n inside your NRC cyber security plan perimeter
PHMSA 49 CFR §191.5 Pipeline incident data with control system locations transits vendor servers Air-gapped or on-prem n8n with direct PHMSA NRC hotline API
FERC Order 2222 DER dispatch data and bid curves are market-sensitive — cloud iPaaS = discovery risk Self-hosted n8n in ISO/RTO data exchange boundary
State PUC rate cases Rate case financial models are commercially sensitive — cloud = discovery risk Self-hosted n8n with state PUC portal direct integration

Get All 15 Workflow Templates

The five workflows in this article are part of the FlowKit n8n Template Library — 15 production-ready workflows covering the full utility SaaS ops stack: onboarding, compliance tracking, health monitoring, incident response, and revenue reporting.

→ Download all 15 at stripeai.gumroad.com

Individual templates: $12–$29. Complete bundle: $97.


FlowKit by Alex Kane — n8n automation templates for regulated industries.

Top comments (0)