DEV Community

Alex Kane
Alex Kane

Posted on

n8n for EnergyTech/UtilityTech SaaS Vendors: 5 Automations for NERC CIP, FERC Order 2222, EPA GHG, NRC, and OSHA PSM Compliance

If your SaaS platform serves grid operators, utilities, nuclear facilities, refineries, or renewable energy companies — your workflows touch some of the most consequential compliance obligations in existence.

NERC CIP violations run up to $1 million per day per violation. NRC cyber incidents require a 1-hour phone call to the Operations Center. OSHA PSM accidents require oral notification within 8 hours. And EPA GHG reporters face audit trail requirements that can't be outsourced to a cloud iPaaS vendor you don't fully control.

The data sovereignty angle is severe: NERC CIP-011 designates Bulk Electric System (BES) Cyber System Information as protected information. Routing it through Zapier or Make creates an unauthorized access exposure — a Level 1 violation at $1M/day under FERC Order 216. Nuclear Cyber Security Plan data under 10 CFR §73.54 is safeguards information; unauthorized disclosure is a criminal offense under 10 CFR §73.21.

Here are 5 n8n workflows every EnergyTech and UtilityTech SaaS vendor should build — all import-ready JSON at the bottom.


Who this is for

7 customer tiers EnergyTech SaaS vendors serve:

Tier Description Key compliance
GRID_MANAGEMENT_SAAS BES operators, transmission system operators NERC CIP-002 through CIP-014
UTILITY_ANALYTICS_SAAS Distribution utilities, load forecasting platforms FERC, EPA GHG 40 CFR Part 98
DERM_SAAS Distributed Energy Resource Management FERC Order 2222, CEII data
RENEWABLE_ENERGY_SAAS Wind/solar/storage asset management EPA GHG Part 98, NERC CIP (if interconnected)
NUCLEAR_OPERATIONS_SAAS Nuclear facility operations software NRC 10 CFR Part 73, §73.54 Cyber Security Plan
INDUSTRIAL_PROCESS_SAAS Refinery, chemical plant, LNG terminal ops OSHA PSM 29 CFR §1910.119, EPA RMP §68
ENERGYTECH_STARTUP Early-stage, initial compliance posture SOC2, FedRAMP path

Workflow 1: EnergyTech Customer Onboarding Drip (Tier-Segmented)

A generic welcome email won't cut it when one customer needs a NERC CIP-004 personnel access audit explanation and another needs a FERC Order 2222 DER enrollment timeline.

What it does: Webhook fires on new customer signup → Code node extracts tier and compliance flags → Sheets log → Day 0 welcome (compliance-note personalized by tier) → Wait 3d → Day 3 integration tips → Wait 4d → Day 7 check-in → Wait 7d → Day 14 QBR invite.

Compliance flags injected: NERC_CIP_APPLICABLE, NRC_10_CFR_73_APPLICABLE, OSHA_PSM_29_CFR_1910_119, FERC_ORDER_2222_SUBJECT, EPA_GHG_REPORTING_REQUIRED, SOC2_REQUIRED.

{
  "name": "EnergyTech Customer Onboarding Drip \u2014 Tier Segmented",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "energytech-onboarding",
        "responseMode": "responseNode"
      },
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "id": "a1b2c3d4-0001-0001-0001-000000000001",
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const tier = $json.customer_tier || 'ENERGYTECH_STARTUP';\nconst flags = $json.compliance_flags || [];\nconst isNercCip = flags.includes('NERC_CIP_APPLICABLE');\nconst isNrc = flags.includes('NRC_10_CFR_73_APPLICABLE');\nconst isOsha = flags.includes('OSHA_PSM_29_CFR_1910_119');\nconst isFercDer = flags.includes('FERC_ORDER_2222_SUBJECT');\nconst isEpaGhg = flags.includes('EPA_GHG_REPORTING_REQUIRED');\nlet note = '';\nif (tier === 'GRID_MANAGEMENT_SAAS') note = 'NERC CIP-004 personnel access auditable; CIP-011 BES Cyber System Information stays in your enclave \u2014 not Zapier.';\nelse if (tier === 'NUCLEAR_OPERATIONS_SAAS') note = 'NRC 10 CFR \u00a773.54 CSP data stays on-prem; safeguards information unauthorized disclosure = criminal offense.';\nelse if (tier === 'INDUSTRIAL_PROCESS_SAAS') note = 'OSHA PSM 29 CFR \u00a71910.119 Process Safety Information and PHA docs remain in your perimeter \u2014 discoverable if cloud-stored.';\nelse if (tier === 'DERM_SAAS') note = 'FERC Order 2222 DER enrollment data is CEII \u2014 must stay within authorized enclosure, not a cloud iPaaS.';\nelse if (tier === 'RENEWABLE_ENERGY_SAAS') note = 'EPA GHG 40 CFR Part 98 annual report calculation audit trail owned by you, not a SaaS vendor.';\nelse note = 'Self-hosted n8n keeps all energy operational data within your perimeter.';\nreturn [{ json: { ...$json, tier, flags, note, isNercCip, isNrc, isOsha, isFercDer, isEpaGhg } }];"
      },
      "name": "Code \u2014 Tier + Flags",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "id": "a1b2c3d4-0001-0001-0001-000000000002",
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "customers",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "name": "Sheets \u2014 Log Customer",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0001-0001-0001-000000000003",
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Welcome to [Platform] \u2014 Your {{ $json.tier }} onboarding",
        "emailType": "html",
        "message": "=<p>Hi {{ $json.name }},</p><p>{{ $json.note }}</p><p>Your onboarding specialist will contact you within 2 business hours.</p>"
      },
      "name": "Gmail \u2014 Day 0 Welcome",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0001-0001-0001-000000000004",
      "position": [
        910,
        300
      ]
    },
    {
      "parameters": {
        "amount": 3,
        "unit": "days"
      },
      "name": "Wait \u2014 3 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "id": "a1b2c3d4-0001-0001-0001-000000000005",
      "position": [
        1130,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Webhook').item.json.email }}",
        "subject": "Your first automation in [Platform]",
        "emailType": "html",
        "message": "=<p>Hi {{ $('Webhook').item.json.name }},</p><p>Day 3 tip: here are the 3 workflows our {{ $('Code \u2014 Tier + Flags').item.json.tier }} customers activate first.</p>"
      },
      "name": "Gmail \u2014 Day 3 Tips",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0001-0001-0001-000000000006",
      "position": [
        1350,
        300
      ]
    },
    {
      "parameters": {
        "amount": 4,
        "unit": "days"
      },
      "name": "Wait \u2014 4 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "id": "a1b2c3d4-0001-0001-0001-000000000007",
      "position": [
        1570,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Webhook').item.json.email }}",
        "subject": "Book your power-user session",
        "emailType": "html",
        "message": "<p>Hi,</p><p>Ready to go deeper? Book your 30-min power-user session: [CALENDAR_LINK]</p>"
      },
      "name": "Gmail \u2014 Day 7 Check-In",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0001-0001-0001-000000000008",
      "position": [
        1790,
        300
      ]
    },
    {
      "parameters": {
        "amount": 7,
        "unit": "days"
      },
      "name": "Wait \u2014 7 Days",
      "type": "n8n-nodes-base.wait",
      "typeVersion": 1,
      "id": "a1b2c3d4-0001-0001-0001-000000000009",
      "position": [
        2010,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $('Webhook').item.json.email }}",
        "subject": "QBR invite \u2014 2 weeks in",
        "emailType": "html",
        "message": "<p>Two weeks in \u2014 let's review ROI and map your next compliance automation layer. Book: [CALENDAR_LINK]</p>"
      },
      "name": "Gmail \u2014 Day 14 QBR",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0001-0001-0001-000000000010",
      "position": [
        2230,
        300
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\"status\": \"ok\"}"
      },
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "id": "a1b2c3d4-0001-0001-0001-000000000011",
      "position": [
        470,
        500
      ]
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Code \u2014 Tier + Flags",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Tier + Flags": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Log Customer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Log Customer": {
      "main": [
        [
          {
            "node": "Gmail \u2014 Day 0 Welcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail \u2014 Day 0 Welcome": {
      "main": [
        [
          {
            "node": "Wait \u2014 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait \u2014 3 Days": {
      "main": [
        [
          {
            "node": "Gmail \u2014 Day 3 Tips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail \u2014 Day 3 Tips": {
      "main": [
        [
          {
            "node": "Wait \u2014 4 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait \u2014 4 Days": {
      "main": [
        [
          {
            "node": "Gmail \u2014 Day 7 Check-In",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail \u2014 Day 7 Check-In": {
      "main": [
        [
          {
            "node": "Wait \u2014 7 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait \u2014 7 Days": {
      "main": [
        [
          {
            "node": "Gmail \u2014 Day 14 QBR",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: NERC CIP / NRC / OSHA Compliance Deadline Tracker

Your customers manage a dense calendar of overlapping compliance deadlines. Missing a NERC CIP-007 35-day patch assessment window or the annual EPA GHG March 31 filing creates direct liability — and reflects on your platform's reliability.

12 deadline types tracked:

Deadline Type Authority Window
NERC_CIP_002_SYSTEM_CATEGORIZATION_ANNUAL NERC CIP-002-5.1a Annual
NERC_CIP_005_ESP_REVIEW_QUARTERLY NERC CIP-005-6 Quarterly
NERC_CIP_007_PATCH_MGMT_35_DAY CIP-007-6 R2 35 calendar days from release
NERC_CIP_010_BASELINE_CONFIG_ANNUAL NERC CIP-010-3 Annual
NERC_CIP_013_SUPPLY_CHAIN_PLAN_18_MONTH CIP-013-1 R1 18 months
NERC_CIP_014_PHYSICAL_SECURITY_PLAN_ANNUAL NERC CIP-014-2 Annual
FERC_ORDER_2222_DER_ENROLLMENT_DEADLINE FERC Order 2222 RTO-specific
EPA_GHG_40_CFR_98_ANNUAL_REPORT 40 CFR Part 98 March 31
NRC_10_CFR_73_CYBER_SECURITY_PLAN_ANNUAL 10 CFR §73.54 Annual
OSHA_PSM_PHA_REVALIDATION_5_YEAR 29 CFR §1910.119(e)(5) Every 5 years
EPA_RMP_HAZARD_ASSESSMENT_5_YEAR 40 CFR §68.67 Every 5 years
SOC2_TYPE2_ANNUAL_RENEWAL AICPA TSC Annual
{
  "name": "NERC CIP / NRC / OSHA Compliance Deadline Tracker",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 24
            }
          ]
        }
      },
      "name": "Schedule \u2014 Daily 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "id": "a1b2c3d4-0002-0002-0002-000000000001",
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "compliance_deadlines",
        "returnAllMatches": true
      },
      "name": "Sheets \u2014 Read Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0002-0002-0002-000000000002",
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const today = new Date();\nconst items = $input.all();\nconst results = [];\nfor (const item of items) {\n  const due = new Date(item.json.due_date);\n  const daysUntil = Math.ceil((due - today) / 86400000);\n  let urgency = null;\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  if (urgency) results.push({ json: { ...item.json, urgency, daysUntil } });\n}\nreturn results;"
      },
      "name": "Code \u2014 Classify Urgency",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "id": "a1b2c3d4-0002-0002-0002-000000000003",
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true
          },
          "conditions": [
            {
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "OVERDUE",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "CRITICAL",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        }
      },
      "name": "IF \u2014 Critical or Overdue",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "id": "a1b2c3d4-0002-0002-0002-000000000004",
      "position": [
        910,
        300
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#nerc-cip-compliance",
          "mode": "name"
        },
        "text": "={{ $json.urgency }}: {{ $json.deadline_type }} for {{ $json.org_name }} \u2014 due {{ $json.due_date }} ({{ $json.daysUntil }} days). Owner: {{ $json.owner_email }}"
      },
      "name": "Slack \u2014 NERC/NRC Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "id": "a1b2c3d4-0002-0002-0002-000000000005",
      "position": [
        1130,
        200
      ]
    },
    {
      "parameters": {
        "sendTo": "={{ $json.owner_email }}",
        "subject": "={{ $json.urgency }}: {{ $json.deadline_type }} due {{ $json.due_date }}",
        "emailType": "html",
        "message": "=<p>{{ $json.deadline_type }} for {{ $json.org_name }} is {{ $json.urgency }} ({{ $json.daysUntil }} days until {{ $json.due_date }}).</p><p>Please update compliance_deadlines sheet upon completion.</p>"
      },
      "name": "Gmail \u2014 Owner Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0002-0002-0002-000000000006",
      "position": [
        1130,
        400
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#regulatory-watch",
          "mode": "name"
        },
        "text": "={{ $json.urgency }}: {{ $json.deadline_type }} for {{ $json.org_name }} in {{ $json.daysUntil }} days."
      },
      "name": "Slack \u2014 Warning Notice",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "id": "a1b2c3d4-0002-0002-0002-000000000007",
      "position": [
        1130,
        600
      ]
    }
  ],
  "connections": {
    "Schedule \u2014 Daily 8AM": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Read Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Read Deadlines": {
      "main": [
        [
          {
            "node": "Code \u2014 Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Classify Urgency": {
      "main": [
        [
          {
            "node": "IF \u2014 Critical or Overdue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF \u2014 Critical or Overdue": {
      "main": [
        [
          {
            "node": "Slack \u2014 NERC/NRC Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail \u2014 Owner Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack \u2014 Warning Notice",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: BES Cyber System API Health Monitor (NERC CIP-007)

NERC CIP-008-6 R4 requires notification to E-ISAC and CISA within 1 hour of a reportable cyber incident. Your monitoring must run tighter than that clock — 15-minute polling gives you 3 detection cycles before the 1-hour window closes.

5 endpoints monitored with CIP annotations:

Endpoint Annotation
grid_management_api BES Cyber System data — NERC CIP-011 R1.1 encryption required
scada_integration_api Electronic Security Perimeter — CIP-005 R1 ingress/egress monitoring
der_forecast_api FERC Order 2222 market data — CPUC/PJM/MISO API dependency
ghg_reporting_api EPA 40 CFR §98.3 mandatory reporting — annual March 31 deadline
nuclear_safety_api NRC 10 CFR §73.54 — 1h notification clock if cyber compromise
{
  "name": "BES Cyber System API Health Monitor (NERC CIP-007)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "name": "Schedule \u2014 Every 15 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "id": "a1b2c3d4-0003-0003-0003-000000000001",
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "url": "={{ $json.endpoint_url }}",
        "options": {
          "timeout": 10000
        }
      },
      "name": "HTTP \u2014 Poll API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4,
      "id": "a1b2c3d4-0003-0003-0003-000000000002",
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const endpointMap = {\n  'grid_management_api': 'BES Cyber System data \u2014 NERC CIP-011 R1.1 encryption required; CIP-005 ESP boundary',\n  'scada_integration_api': 'Electronic Security Perimeter \u2014 CIP-005 R1 ingress/egress monitoring',\n  'der_forecast_api': 'FERC Order 2222 DER market data \u2014 CPUC/PJM/MISO dependency',\n  'ghg_reporting_api': 'EPA 40 CFR \u00a798.3 mandatory reporting \u2014 annual March 31 deadline',\n  'nuclear_safety_api': 'NRC 10 CFR \u00a773.54 \u2014 1h notification clock if cyber compromise'\n};\nconst items = $input.all();\nconst down = items.filter(i => i.json.statusCode !== 200);\nreturn down.map(i => ({\n  json: {\n    ...i.json,\n    annotation: endpointMap[i.json.endpoint_name] || 'Unknown endpoint',\n    status: 'DOWN'\n  }\n}));"
      },
      "name": "Code \u2014 Flag Down Endpoints",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "id": "a1b2c3d4-0003-0003-0003-000000000003",
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#nerc-cip-critical",
          "mode": "name"
        },
        "text": "=CRITICAL: {{ $json.endpoint_name }} is DOWN.\n{{ $json.annotation }}\nCIP-007-6 R1 patch window clock may be running. Investigate immediately."
      },
      "name": "Slack \u2014 CIP Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "id": "a1b2c3d4-0003-0003-0003-000000000004",
      "position": [
        910,
        200
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "grid_incident_log",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "name": "Sheets \u2014 Log Incident",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0003-0003-0003-000000000005",
      "position": [
        910,
        400
      ]
    }
  ],
  "connections": {
    "Schedule \u2014 Every 15 Min": {
      "main": [
        [
          {
            "node": "HTTP \u2014 Poll API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP \u2014 Poll API": {
      "main": [
        [
          {
            "node": "Code \u2014 Flag Down Endpoints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Flag Down Endpoints": {
      "main": [
        [
          {
            "node": "Slack \u2014 CIP Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sheets \u2014 Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: NERC CIP-008 / NRC / OSHA Incident Pipeline

The fastest clocks in energy compliance are brutal: 1 hour for NERC CIP-008 reportable incidents (E-ISAC + CISA), 1 hour for NRC cyber incidents (Operations Center 301-816-5100), 8 hours oral for OSHA PSM process accidents. An intake webhook that auto-routes each incident type to the right channel and email with the SLA printed prominently is the difference between a fine and compliance.

8 incident types with SLA routing:

Incident Type SLA Clock Authority
NERC_CIP_008_REPORTABLE_INCIDENT 1h — E-ISAC + CISA CIP-008-6 R4
NERC_CIP_008_MAJOR_INCIDENT 1h — E-ISAC + CISA CIP-008-6 R4
NRC_10_CFR_73_CYBER_INCIDENT 1h — NRC Ops Center 10 CFR §73.77
OSHA_PSM_PROCESS_ACCIDENT 8h oral / 24h written §1910.119(m)
EPA_GHG_MONITORING_FAILURE 10 days rectification 40 CFR §98.35
EPA_RMP_TOXIC_RELEASE 4h — NRC Hotline 40 CFR Part 68
FERC_ORDER_2222_CURTAILMENT RTO immediate FERC Order 2222
NERC_CIP_SUPPLY_CHAIN_COMPROMISE 35 days patch window CIP-013-1 R1
{
  "name": "NERC CIP-008 / NRC / OSHA Incident Pipeline",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "energy-incident",
        "responseMode": "responseNode"
      },
      "name": "Webhook \u2014 Incident Intake",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 1,
      "id": "a1b2c3d4-0004-0004-0004-000000000001",
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const type = $json.incident_type;\nconst slaMap = {\n  'NERC_CIP_008_REPORTABLE_INCIDENT': '1h \u2014 E-ISAC + CISA notification (CIP-008-6 R4)',\n  'NERC_CIP_008_MAJOR_INCIDENT': '1h \u2014 E-ISAC + CISA (CIP-008-6 R4 \u2014 affects BES reliability)',\n  'NRC_10_CFR_73_CYBER_INCIDENT': '1h \u2014 NRC Operations Center 301-816-5100 (10 CFR \u00a773.77)',\n  'OSHA_PSM_PROCESS_ACCIDENT': '8h oral / 24h written \u2014 OSHA Area Office (\u00a71910.119(m))',\n  'EPA_GHG_MONITORING_FAILURE': '10 days \u2014 parametric monitor rectification (40 CFR \u00a798.35)',\n  'EPA_RMP_TOXIC_RELEASE': '4h \u2014 NRC Hotline 800-424-8802 (40 CFR Part 68)',\n  'FERC_ORDER_2222_CURTAILMENT': 'Market penalty \u2014 DER dispatch failure; notify RTO immediately',\n  'NERC_CIP_SUPPLY_CHAIN_COMPROMISE': '35 days \u2014 patch window (CIP-013-1 R1)'\n};\nconst channelMap = {\n  'NERC_CIP_008_REPORTABLE_INCIDENT': '#nerc-cip-critical',\n  'NERC_CIP_008_MAJOR_INCIDENT': '#nerc-cip-critical',\n  'NRC_10_CFR_73_CYBER_INCIDENT': '#nuclear-security',\n  'OSHA_PSM_PROCESS_ACCIDENT': '#process-safety',\n  'EPA_GHG_MONITORING_FAILURE': '#regulatory-team',\n  'EPA_RMP_TOXIC_RELEASE': '#regulatory-team',\n  'FERC_ORDER_2222_CURTAILMENT': '#grid-ops',\n  'NERC_CIP_SUPPLY_CHAIN_COMPROMISE': '#nerc-cip-critical'\n};\nreturn [{ json: { ...$json,\n  sla: slaMap[type] || 'Consult compliance team',\n  slack_channel: channelMap[type] || '#compliance-general',\n  ts: new Date().toISOString()\n} }];"
      },
      "name": "Code \u2014 SLA + Channel Route",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "id": "a1b2c3d4-0004-0004-0004-000000000002",
      "position": [
        470,
        300
      ]
    },
    {
      "parameters": {
        "operation": "append",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "incident_log",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "name": "Sheets \u2014 Log Incident",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0004-0004-0004-000000000003",
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "={{ $json.slack_channel }}",
          "mode": "name"
        },
        "text": "=ENERGY INCIDENT: {{ $json.incident_type }}\nSite: {{ $json.site_name }}\nSLA: {{ $json.sla }}\nDetails: {{ $json.description }}\nTimestamp: {{ $json.ts }}"
      },
      "name": "Slack \u2014 Incident Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "id": "a1b2c3d4-0004-0004-0004-000000000004",
      "position": [
        910,
        200
      ]
    },
    {
      "parameters": {
        "sendTo": "compliance@yourcompany.com",
        "subject": "={{ 'INCIDENT: ' + $json.incident_type + ' at ' + $json.site_name }}",
        "emailType": "html",
        "message": "=<p><strong>Incident Type:</strong> {{ $json.incident_type }}</p><p><strong>SLA Clock:</strong> {{ $json.sla }}</p><p><strong>Site:</strong> {{ $json.site_name }}</p><p><strong>Description:</strong> {{ $json.description }}</p><p>Log in to incident_log sheet for full audit trail.</p>"
      },
      "name": "Gmail \u2014 Compliance Team",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0004-0004-0004-000000000005",
      "position": [
        910,
        400
      ]
    },
    {
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\"status\": \"logged\"}"
      },
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "typeVersion": 1,
      "id": "a1b2c3d4-0004-0004-0004-000000000006",
      "position": [
        470,
        500
      ]
    }
  ],
  "connections": {
    "Webhook \u2014 Incident Intake": {
      "main": [
        [
          {
            "node": "Code \u2014 SLA + Channel Route",
            "type": "main",
            "index": 0
          },
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 SLA + Channel Route": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Log Incident": {
      "main": [
        [
          {
            "node": "Slack \u2014 Incident Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Gmail \u2014 Compliance Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Weekly EnergyTech Platform KPI Dashboard

Grid operators, utility analytics teams, and nuclear facility operations managers have completely different metrics. This workflow unifies them into one Monday morning CEO briefing that also surfaces compliance event counts alongside revenue.

KPIs tracked:

  • Active Utilities (customer count)
  • MRR with WoW % change
  • BES Systems Monitored
  • NERC CIP Open Tickets
  • CIP-008 Incidents YTD
  • EPA GHG Reporters Monitored
  • OSHA PSM Sites Tracked
  • NRC Facilities Served
  • API Calls (7d)
{
  "name": "Weekly EnergyTech Platform KPI Dashboard",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "weeks",
              "weeksInterval": 1,
              "triggerAtDay": [
                1
              ],
              "triggerAtHour": 8
            }
          ]
        }
      },
      "name": "Schedule \u2014 Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1,
      "id": "a1b2c3d4-0005-0005-0005-000000000001",
      "position": [
        250,
        300
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "platform_metrics",
        "returnAllMatches": true
      },
      "name": "Sheets \u2014 Platform Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0005-0005-0005-000000000002",
      "position": [
        470,
        200
      ]
    },
    {
      "parameters": {
        "operation": "read",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "compliance_events",
        "returnAllMatches": true
      },
      "name": "Sheets \u2014 Compliance Events",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "id": "a1b2c3d4-0005-0005-0005-000000000003",
      "position": [
        470,
        400
      ]
    },
    {
      "parameters": {
        "mode": "combine",
        "combinationMode": "mergeByIndex"
      },
      "name": "Merge",
      "type": "n8n-nodes-base.merge",
      "typeVersion": 2,
      "id": "a1b2c3d4-0005-0005-0005-000000000004",
      "position": [
        690,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "const m = $('Sheets \u2014 Platform Metrics').first().json;\nconst c = $('Sheets \u2014 Compliance Events').first().json;\nconst prev = m.prev_mrr || m.mrr;\nconst mrrWow = prev ? (((m.mrr - prev) / prev) * 100).toFixed(1) : '0.0';\nconst html = `<h2>EnergyTech Platform KPI \u2014 Week of ${new Date().toISOString().slice(0,10)}</h2>\n<table border='1' cellpadding='6'>\n<tr><th>Metric</th><th>Value</th><th>WoW</th></tr>\n<tr><td>Active Utilities</td><td>${m.active_utilities}</td><td>\u2014</td></tr>\n<tr><td>MRR</td><td>$${Number(m.mrr).toLocaleString()}</td><td>${mrrWow}%</td></tr>\n<tr><td>BES Systems Monitored</td><td>${m.bes_systems_monitored}</td><td>\u2014</td></tr>\n<tr><td>NERC CIP Open Tickets</td><td>${c.nerc_cip_open || 0}</td><td>\u2014</td></tr>\n<tr><td>CIP-008 Incidents YTD</td><td>${c.cip_008_incidents_ytd || 0}</td><td>\u2014</td></tr>\n<tr><td>EPA GHG Reporters</td><td>${m.epa_ghg_reporters || 0}</td><td>\u2014</td></tr>\n<tr><td>OSHA PSM Sites</td><td>${m.osha_psm_sites || 0}</td><td>\u2014</td></tr>\n<tr><td>NRC Facilities</td><td>${m.nrc_facilities || 0}</td><td>\u2014</td></tr>\n<tr><td>API Calls (7d)</td><td>${Number(m.api_calls_7d).toLocaleString()}</td><td>\u2014</td></tr>\n</table>`;\nreturn [{ json: { html, mrrWow } }];"
      },
      "name": "Code \u2014 Build KPI HTML",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "id": "a1b2c3d4-0005-0005-0005-000000000005",
      "position": [
        910,
        300
      ]
    },
    {
      "parameters": {
        "sendTo": "ceo@yourcompany.com",
        "subject": "=Weekly EnergyTech KPI \u2014 MRR {{ $json.mrrWow }}% WoW",
        "emailType": "html",
        "message": "={{ $json.html }}",
        "options": {
          "bccList": "ciso@yourcompany.com, compliance@yourcompany.com"
        }
      },
      "name": "Gmail \u2014 CEO + BCC CISO",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "id": "a1b2c3d4-0005-0005-0005-000000000006",
      "position": [
        1130,
        300
      ]
    },
    {
      "parameters": {
        "select": "channel",
        "channelId": {
          "__rl": true,
          "value": "#management",
          "mode": "name"
        },
        "text": "=Weekly KPI: MRR {{ $json.mrrWow }}% WoW. Full report in email."
      },
      "name": "Slack \u2014 Summary",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2,
      "id": "a1b2c3d4-0005-0005-0005-000000000007",
      "position": [
        1130,
        500
      ]
    }
  ],
  "connections": {
    "Schedule \u2014 Monday 8AM": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Sheets \u2014 Compliance Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Platform Metrics": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Compliance Events": {
      "main": [
        [
          {
            "node": "Merge",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge": {
      "main": [
        [
          {
            "node": "Code \u2014 Build KPI HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code \u2014 Build KPI HTML": {
      "main": [
        [
          {
            "node": "Gmail \u2014 CEO + BCC CISO",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack \u2014 Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why self-host n8n for EnergyTech

Compliance requirement Cloud iPaaS risk Self-hosted n8n fix
NERC CIP-011 BES Cyber System Information — "protected information" Routing through Zapier = unauthorized access exposure → Level 1 violation → $1M/day FERC Order 216 n8n in your CIP boundary keeps BCS data in authorized enclave
FERC Order 2222 DER enrollment data (site coordinates, generation capacity, interconnection agreements) — CEII designation CEII data must stay within authorized enclosure — cloud iPaaS = unauthorized disclosure n8n on-prem or in FedRAMP-authorized cloud keeps CEII perimeter
NRC 10 CFR §73.54 nuclear Cyber Security Plan — safeguards information Unauthorized disclosure = criminal offense under 10 CFR §73.21 n8n self-hosted in nuclear facility perimeter; safeguards data never transits vendor cloud
OSHA PSM 29 CFR §1910.119 Process Safety Information and PHA documents OSHA has subpoena power — cloud-stored PSI is discoverable in litigation PSI and PHA records in your perimeter under access controls
EPA Chemical-Terrorism Vulnerability Information (CVI) — RMP §68 worst-case scenario data Routing CVI through cloud = 6 USC §621 violation reportable to CISA CVI stays within your CVI-authorized system

Key buyer questions

"Can NERC CIP auditors access data in your cloud iPaaS?"
If you self-host n8n, BES Cyber System Information stays inside your CIP boundary. Zapier and Make are external CSPs — routing BCS data through them expands your CIP assessment boundary to include their infrastructure.

"Our FERC Order 2222 DER enrollment data is CEII — who can see it?"
CEII (Critical Energy Infrastructure Information) must be protected under 18 CFR §388.113. DER site coordinates, generation capacity, and interconnection details flowing through a cloud iPaaS create unauthorized disclosure exposure. Self-hosted n8n keeps CEII within your authorized enclosure.

"NRC inspection found our SaaS vendor processes safeguards information — what's the fix?"
Self-hosted n8n deployed in your facility's perimeter keeps 10 CFR §73.54 Cyber Security Plan data on-premises. The workflow engine is in your environment — no safeguards data transits a vendor cloud.

"OSHA came to our refinery — can they get our PSI from a cloud vendor?"
OSHA has broad subpoena authority. Process Safety Information and Process Hazard Analysis documents stored with a cloud vendor are discoverable. Self-hosted n8n with proper access controls keeps PSI in your documented management-of-change system.

"We're an EPA GHG mandatory reporter — can we run the calculation audit trail ourselves?"
Yes. 40 CFR §98 requires reporters to retain monitoring data and calculations. Running n8n self-hosted means your GHG calculation chain — source data, aggregation logic, annual totals — stays in your records system, not a vendor's infrastructure.


Get all 15 FlowKit n8n templates

These 5 workflows are part of the FlowKit n8n template collection — pre-built, import-ready workflows for the verticals that need automation most.

Browse FlowKit templates → stripeai.gumroad.com

Individual templates: $12–$29. Full bundle (15 templates): $97.

Top comments (0)