EnergyTech and CleanTech SaaS vendors sit at the intersection of three converging regulatory stacks: grid reliability (NERC CIP), emissions compliance (EPA Clean Air Act), and climate financial disclosure (SEC Rule 33-11275). Each fires on different clocks, with different enforcement bodies, and different evidence standards.
A NERC CIP cyber incident requires E-ISAC notification within 1 hour of detection (CIP-008-6 R4.1). An EPA §111(d) emission exceedance is immediate — the compliance manager must act before the agency audit. A SEC material climate event triggers a 4 business-day 8-K under 17 CFR §229.1502. These are not the same clock, and they cannot be managed from the same incident queue.
This article shows 5 n8n workflows for EnergyTech and CleanTech SaaS vendors — grid management platforms, renewable energy SaaS, utility billing tools, energy trading platforms, demand response SaaS, building energy management vendors, and CleanTech startups.
The regulatory stack
NERC CIP v7 — BES Cyber System protection
The North American Electric Reliability Corporation Critical Infrastructure Protection standards (CIP-001 through CIP-014) govern cybersecurity for Bulk Electric System (BES) assets. Key clocks: CIP-004-6 annual personnel training (R4.2), CIP-007-6 35-day patch window (R2), CIP-008-6 15-month incident response exercise (R3), and CIP-009-6 annual recovery plan review (R3). CIP-008-6 R4.1 requires reporting a BES Cyber Security Incident to E-ISAC and NERC Compliance within 1 hour of confirmation. SaaS vendors whose platforms touch BES cyber assets must document whether their automation tooling is within the CIP compliance boundary. A cloud iPaaS vendor routing CJI through a shared multi-tenant pipeline is not within that boundary.
FERC Order 2222 — Distributed Energy Resource aggregation
FERC Order 2222 (2020) requires all RTOs and ISOs to revise their tariffs to allow DER aggregators to participate in all wholesale electric markets. EnergyTech platforms that aggregate distributed solar, batteries, demand response, or EV charging into market-participating resources must track interconnection queue deadlines, capacity market eligibility windows, and the FERC Order 2023 interconnection reform milestones.
EPA Clean Air Act §111(d) — Carbon Pollution Standard
EPA's 2024 Carbon Pollution Standard for existing power plants sets emission limits for coal and gas generators under CAA §111(d). CleanTech SaaS platforms serving power generators must track annual compliance reporting windows, Title V permit renewal deadlines, and emission monitoring system (CEMS) calibration certificates.
SEC Climate Rule — 17 CFR Parts 210, 229, 232, 239
SEC's climate disclosure rule (adopted March 2024, currently subject to litigation stay) requires large accelerated filers to disclose Scope 1 and 2 GHG emissions, material physical and transition climate risks, and climate-related financial impacts in annual 10-K filings. Material climate events — grid disruptions, flood damage to a major facility, regulatory action on a major emission source — may trigger a 4 business-day 8-K under §229.1502.
CA SB 100 — 100% clean electricity by 2045
California SB 100 (§454.53 of the Public Utilities Code) requires 100% of California retail electricity to come from renewable and zero-carbon sources by 2045, with a 60% renewable portfolio standard by 2030. CleanTech platforms serving California utilities must track annual procurement compliance reports to the CPUC.
The 7 customer tiers
| Tier | Example platforms |
|---|---|
GRID_MGMT_SAAS |
BES monitoring, SCADA integration, EMS platforms |
RENEWABLE_ENERGY_SAAS |
Solar/wind asset management, BESS optimization |
UTILITY_BILLING_SAAS |
AMI analytics, rate design, NEM billing |
ENERGY_TRADING_SAAS |
ISO/RTO bidding, capacity market, power purchase agreements |
DEMAND_RESPONSE_SAAS |
DER aggregation, FERC Order 2222, virtual power plants |
BUILDING_ENERGY_SAAS |
ENERGY STAR benchmarking, BMS integration, ASHRAE 90.1 |
CLEANTECH_STARTUP_SAAS |
Carbon accounting, green hydrogen, EV fleet management |
Workflow 1: NERC CIP v7 BES Cyber Asset Compliance Monitor
This daily workflow queries your BES cyber asset registry for CIP compliance gaps — training expiry (CIP-004-6), open patch windows (CIP-007-6), overdue incident response exercises (CIP-008-6), recovery plan expiry (CIP-009-6), and physical security review overdue (CIP-014-3).
{
"name": "NERC CIP v7 BES Cyber Asset Compliance Monitor",
"nodes": [
{
"id": "1",
"name": "Daily 7AM Cron",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1-5"
}
]
}
}
},
{
"id": "2",
"name": "Query BES Cyber Assets",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT asset_id, asset_name, cip_impact_level, cip004_training_expiry, cip007_patch_window_open, cip008_plan_last_tested, cip009_recovery_plan_expiry, cip014_physical_review_due, asset_owner_email FROM bes_cyber_assets WHERE asset_status = 'ACTIVE' AND (cip004_training_expiry <= NOW() + INTERVAL '30 days' OR cip007_patch_window_open = TRUE OR cip008_plan_last_tested <= NOW() - INTERVAL '15 months' OR cip009_recovery_plan_expiry <= NOW() + INTERVAL '60 days' OR cip014_physical_review_due <= NOW() + INTERVAL '30 days')"
}
},
{
"id": "3",
"name": "Classify CIP Urgency",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const items = $input.all();\nconst results = [];\nconst now = new Date();\nfor (const item of items) {\n const d = item.json;\n const alerts = [];\n const training_exp = new Date(d.cip004_training_expiry);\n const days_training = Math.ceil((training_exp - now) / 86400000);\n if (days_training <= 0) alerts.push({type: 'CIP004_TRAINING_EXPIRED', severity: 'CRITICAL', days: days_training, cite: 'CIP-004-6 R4.2 annual training requirement'});\n else if (days_training <= 14) alerts.push({type: 'CIP004_TRAINING_EXPIRING', severity: 'URGENT', days: days_training, cite: 'CIP-004-6 R4.2'});\n if (d.cip007_patch_window_open) alerts.push({type: 'CIP007_PATCH_WINDOW_OPEN', severity: 'HIGH', cite: 'CIP-007-6 R2 35-day patch window'});\n const plan_tested = new Date(d.cip008_plan_last_tested);\n const months_since_test = (now - plan_tested) / (30 * 86400000);\n if (months_since_test >= 15) alerts.push({type: 'CIP008_PLAN_OVERDUE', severity: 'CRITICAL', cite: 'CIP-008-6 R3 15-month test requirement'});\n const recovery_exp = new Date(d.cip009_recovery_plan_expiry);\n const days_recovery = Math.ceil((recovery_exp - now) / 86400000);\n if (days_recovery <= 30) alerts.push({type: 'CIP009_RECOVERY_PLAN_EXPIRING', severity: 'HIGH', days: days_recovery, cite: 'CIP-009-6 R3 annual review'});\n const phys_due = new Date(d.cip014_physical_review_due);\n const days_phys = Math.ceil((phys_due - now) / 86400000);\n if (days_phys <= 0) alerts.push({type: 'CIP014_PHYSICAL_REVIEW_OVERDUE', severity: 'CRITICAL', days: days_phys, cite: 'CIP-014-3 R6 annual threat review'});\n if (alerts.length > 0) results.push({json: {...d, alerts, highest_severity: alerts.some(a => a.severity==='CRITICAL') ? 'CRITICAL' : 'HIGH'}});\n}\nreturn results;"
}
},
{
"id": "4",
"name": "Route by Severity",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"options": {
"caseSensitive": false,
"leftValue": "",
"typeValidation": "strict"
},
"conditions": [
{
"id": "1",
"leftValue": "={{ $json.highest_severity }}",
"rightValue": "CRITICAL",
"operator": {
"type": "string",
"operation": "equals"
}
}
]
}
}
},
{
"id": "5",
"name": "Slack CRITICAL #nerc-compliance-critical",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#nerc-compliance-critical",
"text": "=\ud83d\udea8 NERC CIP CRITICAL \u2014 {{ $json.asset_name }} ({{ $json.cip_impact_level }})\n{{ $json.alerts.map(a => `\u2022 ${a.type}: ${a.cite}`).join('\\n') }}\nOwner: {{ $json.asset_owner_email }}"
}
},
{
"id": "6",
"name": "Gmail Asset Owner",
"type": "n8n-nodes-base.gmail",
"parameters": {
"toList": "={{ $json.asset_owner_email }}",
"subject": "=NERC CIP CRITICAL: {{ $json.asset_name }} compliance gap",
"message": "=NERC CIP compliance gap detected for BES Cyber Asset {{ $json.asset_name }} (Impact Level: {{ $json.cip_impact_level }}).\n\nAlerts:\n{{ $json.alerts.map(a => `${a.type}: ${a.cite}`).join('\\n') }}\n\nRemediate within 35 days (CIP-007 patch) or before certification deadline."
}
},
{
"id": "7",
"name": "Slack HIGH #nerc-compliance",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#nerc-compliance",
"text": "=\u26a0\ufe0f NERC CIP HIGH \u2014 {{ $json.asset_name }}: {{ $json.alerts.map(a=>a.type).join(', ') }}"
}
}
],
"connections": {
"Daily 7AM Cron": {
"main": [
[
{
"node": "Query BES Cyber Assets",
"type": "main",
"index": 0
}
]
]
},
"Query BES Cyber Assets": {
"main": [
[
{
"node": "Classify CIP Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify CIP Urgency": {
"main": [
[
{
"node": "Route by Severity",
"type": "main",
"index": 0
}
]
]
},
"Route by Severity": {
"main": [
[
{
"node": "Slack CRITICAL #nerc-compliance-critical",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack HIGH #nerc-compliance",
"type": "main",
"index": 0
}
]
]
},
"Slack CRITICAL #nerc-compliance-critical": {
"main": [
[
{
"node": "Gmail Asset Owner",
"type": "main",
"index": 0
}
]
]
}
}
}
Why this matters: CIP-008-6 R4.1 requires E-ISAC notification within 1 hour of confirming a BES Cyber Security Incident. If your platform is the system of record for BES cyber events and the incident response plan has not been exercised in 15 months, you are in violation before the clock even starts. This workflow surfaces the 15-month gap before an incident, not after.
Workflow 2: FERC / EPA / SEC CleanTech Compliance Deadline Tracker
Daily tracker for 14 regulatory deadline types: NERC CIP annual recertification, quarterly log review, CIP-008 exercise, FERC Order 2222 compliance filing, FERC Order 2023 interconnection milestones, EPA §111(d) annual report, Title V permit renewal, SEC 10-K climate disclosure, CA SB 100 annual progress, EU Taxonomy KPI reporting, ERCOT interconnection queue, REC retirement, DOE appliance standard review.
{
"name": "FERC / EPA / SEC CleanTech Compliance Deadline Tracker",
"nodes": [
{
"id": "1",
"name": "Weekdays 8AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1-5"
}
]
}
}
},
{
"id": "2",
"name": "Fetch Compliance Deadlines",
"type": "n8n-nodes-base.googleSheets",
"parameters": {
"operation": "read",
"sheetId": "YOUR_SHEET_ID",
"range": "Deadlines!A:H"
}
},
{
"id": "3",
"name": "Classify Urgency",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const items = $input.all();\nconst now = new Date();\nconst alerts = [];\nconst DEADLINE_TYPES = {\n 'NERC_CIP_ANNUAL_RECERTIFICATION': 'CIP-001 through CIP-014 annual evidence package',\n 'NERC_CIP_QUARTERLY_LOG_REVIEW': 'CIP-007-6 R5.6 quarterly review',\n 'NERC_CIP_008_INCIDENT_EXERCISE': 'CIP-008-6 R3 15-month incident response exercise',\n 'FERC_ORDER_2222_COMPLIANCE_FILING': 'FERC Order 2222 RTO/ISO DER participation rule',\n 'FERC_ORDER_2023_INTERCONNECT': 'FERC Order 2023 generator interconnection queue reform',\n 'EPA_111D_ANNUAL_EMISSION_REPORT': 'CAA \u00a7111(d) Carbon Pollution Standard annual compliance',\n 'EPA_TITLE_V_PERMIT_RENEWAL': 'CAA Title V operating permit renewal',\n 'SEC_CLIMATE_RULE_ANNUAL_10K': 'SEC 17 CFR \u00a7229.1500 climate risk 10-K disclosure',\n 'SEC_CLIMATE_RULE_8K_MATERIAL': '4-business-day 8-K material climate event',\n 'CA_SB100_ANNUAL_PROGRESS_REPORT': 'CA SB 100 \u00a7454.53 100% clean electricity 2045 progress',\n 'EU_TAXONOMY_CLIMATE_MITIGATION_REPORT': 'EU Taxonomy Regulation Art.8 KPI reporting',\n 'ERCOT_INTERCONNECT_QUEUE_UPDATE': 'ERCOT Nodal Protocol \u00a75.2.1 interconnection milestone',\n 'REC_RETIREMENT_ANNUAL': 'Renewable Energy Certificate retirement for RPS compliance',\n 'DOE_EERE_APPLIANCE_STANDARD_REVIEW': 'DOE 10 CFR Part 430/431 appliance efficiency standard'\n};\nfor (const item of items) {\n const row = item.json;\n const deadline = new Date(row['deadline_date']);\n const days = Math.ceil((deadline - now) / 86400000);\n const dtype = row['deadline_type'];\n let bucket = days <= 0 ? 'OVERDUE' : days <= 14 ? 'CRITICAL' : days <= 45 ? 'URGENT' : days <= 90 ? 'WARNING' : 'NOTICE';\n if (bucket !== 'NOTICE') alerts.push({json: {...row, days_remaining: days, urgency: bucket, regulatory_cite: DEADLINE_TYPES[dtype] || dtype}});\n}\nreturn alerts;"
}
},
{
"id": "4",
"name": "Route CRITICAL/OVERDUE",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.urgency }}",
"rightValue": "CRITICAL",
"operator": {
"type": "string",
"operation": "equals"
}
},
{
"leftValue": "={{ $json.urgency }}",
"rightValue": "OVERDUE",
"operator": {
"type": "string",
"operation": "equals"
}
}
],
"combinator": "or"
}
}
},
{
"id": "5",
"name": "Slack + Gmail Critical",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#regulatory-compliance-critical",
"text": "=\ud83d\udea8 {{ $json.urgency }}: {{ $json.deadline_type }} \u2014 {{ $json.days_remaining }} days\nCite: {{ $json.regulatory_cite }}\nOwner: {{ $json.owner_name }}"
}
},
{
"id": "6",
"name": "Slack Warning/Urgent",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#regulatory-compliance",
"text": "=\u26a0\ufe0f {{ $json.urgency }}: {{ $json.deadline_type }} \u2014 {{ $json.days_remaining }} days remaining. Cite: {{ $json.regulatory_cite }}"
}
}
],
"connections": {
"Weekdays 8AM": {
"main": [
[
{
"node": "Fetch Compliance Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Fetch Compliance Deadlines": {
"main": [
[
{
"node": "Classify Urgency",
"type": "main",
"index": 0
}
]
]
},
"Classify Urgency": {
"main": [
[
{
"node": "Route CRITICAL/OVERDUE",
"type": "main",
"index": 0
}
]
]
},
"Route CRITICAL/OVERDUE": {
"main": [
[
{
"node": "Slack + Gmail Critical",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack Warning/Urgent",
"type": "main",
"index": 0
}
]
]
}
}
}
The self-hosting argument: SEC 17 CFR §229.1502 climate disclosure workflows generate evidence that becomes part of the annual report attestation chain. Routing Scope 1/2 emission data through a cloud iPaaS creates a gap between the data as processed and the data as filed — the same gap that triggered restatements in early CSRD pilots. Self-hosted n8n with Postgres audit logs keeps the evidence chain inside your SOX disclosure controls boundary.
Workflow 3: Grid & Clean Energy API Health Monitor
Five-minute polling across grid operations, FERC OASIS, NERC CIP portal, EPA CDX e-reporting, and energy trading APIs. Uses $getWorkflowStaticData for state-change deduplication — only fires an alert when status changes, not on every poll cycle.
{
"name": "Grid & Clean Energy API Health Monitor",
"nodes": [
{
"id": "1",
"name": "Every 5 Minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 5
}
]
}
}
},
{
"id": "2",
"name": "Fetch Previous State",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const state = $getWorkflowStaticData('global');\nreturn [{json: {prev: state.endpoint_status || {}}}];"
}
},
{
"id": "3",
"name": "Check Grid Operations API",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://api.your-grid-platform.com/health",
"method": "GET",
"timeout": 10000
}
},
{
"id": "4",
"name": "Check FERC OASIS API",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://oasis.your-platform.com/health",
"method": "GET",
"timeout": 10000
}
},
{
"id": "5",
"name": "Check NERC CIP Portal",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://cip.your-platform.com/health",
"method": "GET",
"timeout": 10000
}
},
{
"id": "6",
"name": "Check EPA e-Reporting API",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://cdx.epa.gov/health",
"method": "GET",
"timeout": 10000
}
},
{
"id": "7",
"name": "Check Energy Trading API",
"type": "n8n-nodes-base.httpRequest",
"parameters": {
"url": "https://trading.your-platform.com/health",
"method": "GET",
"timeout": 10000
}
},
{
"id": "8",
"name": "Evaluate & Dedup Alerts",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const prev = $node['Fetch Previous State'].json.prev;\nconst checks = [\n {id: 'grid_operations_api', node: 'Check Grid Operations API', label: 'Grid Operations API', cite: 'NERC CIP-007-6 R6 system monitoring'},\n {id: 'ferc_oasis_api', node: 'Check FERC OASIS API', label: 'FERC OASIS Transmission API', cite: 'FERC Order 2222 wholesale market access'},\n {id: 'nerc_cip_portal', node: 'Check NERC CIP Portal', label: 'NERC CIP Compliance Portal', cite: 'CIP-001 through CIP-014 evidence submission'},\n {id: 'epa_ereporting_api', node: 'Check EPA e-Reporting API', label: 'EPA e-Reporting CDX API', cite: 'CAA \u00a7111(d) emission compliance submission'},\n {id: 'energy_trading_api', node: 'Check Energy Trading API', label: 'Energy Trading API', cite: 'FERC Order 2222 DER market participation'}\n];\nconst now = Date.now();\nconst state = $getWorkflowStaticData('global');\nstate.endpoint_status = state.endpoint_status || {};\nconst alerts = [];\nfor (const c of checks) {\n let status = 'DOWN';\n try { const r = $node[c.node].json; status = (r.status >= 200 && r.status < 300) ? 'UP' : 'DOWN'; } catch(e) { status = 'DOWN'; }\n const was = prev[c.id] || 'UP';\n if (status !== was) {\n state.endpoint_status[c.id] = status;\n alerts.push({json: {endpoint: c.id, label: c.label, status, previous: was, cite: c.cite, ts: new Date().toISOString(), changed: true}});\n } else {\n state.endpoint_status[c.id] = status;\n }\n}\nreturn alerts.length > 0 ? alerts : [{json: {no_changes: true}}];"
}
},
{
"id": "9",
"name": "Alert on Change",
"type": "n8n-nodes-base.if",
"parameters": {
"conditions": {
"conditions": [
{
"leftValue": "={{ $json.changed }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "equals"
}
}
]
}
}
},
{
"id": "10",
"name": "Slack #grid-ops-alerts",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "#grid-ops-alerts",
"text": "={{ $json.status === 'DOWN' ? '\ud83d\udd34' : '\ud83d\udfe2' }} {{ $json.label }} \u2192 {{ $json.status }}\nCompliance note: {{ $json.cite }}\n{{ $json.ts }}"
}
}
],
"connections": {
"Every 5 Minutes": {
"main": [
[
{
"node": "Fetch Previous State",
"type": "main",
"index": 0
}
]
]
},
"Fetch Previous State": {
"main": [
[
{
"node": "Check Grid Operations API",
"type": "main",
"index": 0
},
{
"node": "Check FERC OASIS API",
"type": "main",
"index": 0
},
{
"node": "Check NERC CIP Portal",
"type": "main",
"index": 0
},
{
"node": "Check EPA e-Reporting API",
"type": "main",
"index": 0
},
{
"node": "Check Energy Trading API",
"type": "main",
"index": 0
}
]
]
},
"Check Grid Operations API": {
"main": [
[
{
"node": "Evaluate & Dedup Alerts",
"type": "main",
"index": 0
}
]
]
},
"Evaluate & Dedup Alerts": {
"main": [
[
{
"node": "Alert on Change",
"type": "main",
"index": 0
}
]
]
},
"Alert on Change": {
"main": [
[
{
"node": "Slack #grid-ops-alerts",
"type": "main",
"index": 0
}
],
[]
]
}
}
}
The compliance annotation: When NERC CIP Portal goes DOWN, it is not just a UX issue — CIP-007-6 R5.6 requires logging all security events, and an inability to submit compliance evidence during an audit window can trigger a finding. The annotation on each endpoint makes the regulatory consequence visible to on-call engineers who may not know the CIP citation.
Workflow 4: NERC CIP / EPA / FERC Incident Pipeline
Webhook-driven incident router for 8 event types: NERC CIP cyber incident (1h E-ISAC), BES physical security breach (1h), EPA emission exceedance (immediate), FERC market manipulation flag (24h self-report evaluation), SEC material climate event (4 business-day 8-K), grid outage reportable event (1h NERC GCWR), DER aggregation compliance gap (48h), and CA SB 100 renewable shortfall (72h CPUC).
{
"name": "NERC CIP / EPA / FERC CleanTech Incident Pipeline",
"nodes": [
{
"id": "1",
"name": "Webhook Incident Intake",
"type": "n8n-nodes-base.webhook",
"parameters": {
"path": "cleantech-incident",
"responseMode": "lastNode"
}
},
{
"id": "2",
"name": "Classify & Route Incident",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const inc = $input.first().json;\nconst INCIDENTS = {\n 'NERC_CIP_CYBER_INCIDENT': {severity: 'CRITICAL', deadline_hours: 1, notify: 'E-ISAC + NERC CIP-008-6 R4.1 one-hour notification', channel: '#nerc-cip-critical'},\n 'BES_PHYSICAL_SECURITY_BREACH': {severity: 'CRITICAL', deadline_hours: 1, notify: 'NERC CIP-006 physical security event log + E-ISAC notification', channel: '#nerc-cip-critical'},\n 'EPA_EMISSION_EXCEEDANCE_EVENT': {severity: 'HIGH', deadline_hours: 0, notify: 'IMMEDIATE CAA \u00a7111(d) compliance manager notification + state air agency', channel: '#epa-compliance'},\n 'FERC_MARKET_MANIPULATION_FLAG': {severity: 'CRITICAL', deadline_hours: 24, notify: 'FERC Order 1000 \u00a735.41 + FERC Enforcement 24h self-report evaluation', channel: '#ferc-compliance-critical'},\n 'SEC_MATERIAL_CLIMATE_EVENT': {severity: 'CRITICAL', deadline_hours: 96, notify: 'SEC 17 CFR \u00a7229.1502 4 business-day 8-K filing + investor relations', channel: '#sec-disclosure'},\n 'GRID_OUTAGE_REPORTABLE_EVENT': {severity: 'HIGH', deadline_hours: 1, notify: 'NERC GCWR + state PUC notification + CIP-008 incident response activation', channel: '#grid-ops-alerts'},\n 'DER_AGGREGATION_COMPLIANCE_GAP': {severity: 'HIGH', deadline_hours: 48, notify: 'FERC Order 2222 RTO/ISO compliance gap \u2014 DER participation rule violation risk', channel: '#ferc-compliance'},\n 'CA_SB100_RENEWABLE_SHORTFALL': {severity: 'HIGH', deadline_hours: 72, notify: 'CA SB 100 \u00a7454.53 procurement shortfall \u2014 CPUC compliance filing required', channel: '#regulatory-compliance'}\n};\nconst config = INCIDENTS[inc.incident_type] || {severity: 'MEDIUM', deadline_hours: 24, notify: 'Unknown incident type \u2014 review manually', channel: '#cleantech-incidents'};\nconst deadline_ts = new Date(Date.now() + config.deadline_hours * 3600000).toISOString();\nreturn [{json: {...inc, ...config, deadline_ts, logged_at: new Date().toISOString()}}];"
}
},
{
"id": "3",
"name": "Log to Postgres",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "insert",
"table": "cleantech_incident_log",
"columns": "incident_type,severity,deadline_ts,notify_guidance,logged_at,raw_payload",
"columnData": "={{ $json.incident_type }},={{ $json.severity }},={{ $json.deadline_ts }},={{ $json.notify }},={{ $json.logged_at }},={{ JSON.stringify($json) }}"
}
},
{
"id": "4",
"name": "Route by Severity",
"type": "n8n-nodes-base.switch",
"parameters": {
"mode": "expression",
"output": "={{ $json.severity === 'CRITICAL' ? 0 : $json.severity === 'HIGH' ? 1 : 2 }}"
}
},
{
"id": "5",
"name": "Slack CRITICAL + PagerDuty",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "={{ $json.channel }}",
"text": "=\ud83d\udea8 CRITICAL INCIDENT: {{ $json.incident_type }}\nDeadline: {{ $json.deadline_ts }}\nGuidance: {{ $json.notify }}"
}
},
{
"id": "6",
"name": "Slack HIGH",
"type": "n8n-nodes-base.slack",
"parameters": {
"channel": "={{ $json.channel }}",
"text": "=\u26a0\ufe0f HIGH INCIDENT: {{ $json.incident_type }}\nDeadline: {{ $json.deadline_ts }}\nGuidance: {{ $json.notify }}"
}
},
{
"id": "7",
"name": "Respond 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"parameters": {
"respondWith": "json",
"responseBody": "={\"received\": true, \"incident_type\": \"{{ $json.incident_type }}\", \"deadline_ts\": \"{{ $json.deadline_ts }}\"}"
}
}
],
"connections": {
"Webhook Incident Intake": {
"main": [
[
{
"node": "Classify & Route Incident",
"type": "main",
"index": 0
}
]
]
},
"Classify & Route Incident": {
"main": [
[
{
"node": "Log to Postgres",
"type": "main",
"index": 0
}
]
]
},
"Log to Postgres": {
"main": [
[
{
"node": "Route by Severity",
"type": "main",
"index": 0
}
]
]
},
"Route by Severity": {
"main": [
[
{
"node": "Slack CRITICAL + PagerDuty",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack HIGH",
"type": "main",
"index": 0
}
],
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Slack CRITICAL + PagerDuty": {
"main": [
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Slack HIGH": {
"main": [
[
{
"node": "Respond 200 OK",
"type": "main",
"index": 0
}
]
]
}
}
}
The enforcement evidence problem: NERC CIP-008-6 requires that the incident response plan document the time of detection, the time of notification to E-ISAC, and the actions taken. If the incident enters your system through a cloud automation vendor's shared pipeline, the timestamp on the Zapier/Make execution log is the detection timestamp for audit purposes — and it is not under your control. Self-hosted n8n with logged_at in Postgres gives you the timestamp you need to show the 1-hour window was met.
Workflow 5: Weekly EnergyTech Platform KPI Dashboard
Monday morning dashboard combining MRR, customer tier breakdown (grid management / renewable / trading), and compliance events from the incident log: NERC CIP incidents, EPA emission events, FERC incidents, SEC climate disclosures. Flags auto-raise if SEC events are open (4 business-day 8-K window), if EPA incidents are unresolved, or if overdue compliance deadlines exceed zero.
{
"name": "Weekly EnergyTech Platform KPI Dashboard",
"nodes": [
{
"id": "1",
"name": "Monday 7AM",
"type": "n8n-nodes-base.scheduleTrigger",
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 7 * * 1"
}
]
}
}
},
{
"id": "2",
"name": "Query This Week Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) FILTER(WHERE created_at >= NOW()-'7 days'::interval) AS new_customers_7d, COUNT(*) FILTER(WHERE status='ACTIVE') AS active_customers, SUM(mrr_usd) FILTER(WHERE status='ACTIVE') AS total_mrr, COUNT(*) FILTER(WHERE customer_tier='GRID_MGMT_SAAS' AND status='ACTIVE') AS grid_mgmt_count, COUNT(*) FILTER(WHERE customer_tier='RENEWABLE_ENERGY_SAAS' AND status='ACTIVE') AS renewable_count, COUNT(*) FILTER(WHERE customer_tier='ENERGY_TRADING_SAAS' AND status='ACTIVE') AS trading_count FROM customers"
}
},
{
"id": "3",
"name": "Query Last Week Metrics",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT SUM(mrr_usd) FILTER(WHERE status='ACTIVE') AS last_mrr FROM customers WHERE created_at < NOW()-'7 days'::interval"
}
},
{
"id": "4",
"name": "Query Compliance Events",
"type": "n8n-nodes-base.postgres",
"parameters": {
"operation": "executeQuery",
"query": "SELECT COUNT(*) FILTER(WHERE incident_type LIKE 'NERC_CIP%' AND logged_at >= NOW()-'7 days'::interval) AS nerc_cip_incidents_7d, COUNT(*) FILTER(WHERE incident_type LIKE 'EPA%' AND logged_at >= NOW()-'7 days'::interval) AS epa_incidents_7d, COUNT(*) FILTER(WHERE incident_type LIKE 'SEC%' AND logged_at >= NOW()-'7 days'::interval) AS sec_incidents_7d, COUNT(*) FILTER(WHERE incident_type LIKE 'FERC%' AND logged_at >= NOW()-'7 days'::interval) AS ferc_incidents_7d, COUNT(*) FILTER(WHERE severity='CRITICAL' AND deadline_ts < NOW() AND logged_at >= NOW()-'30 days'::interval) AS overdue_compliance_30d FROM cleantech_incident_log"
}
},
{
"id": "5",
"name": "Merge KPI Streams",
"type": "n8n-nodes-base.merge",
"parameters": {
"mode": "combine",
"combinationMode": "multiplex"
}
},
{
"id": "6",
"name": "Build HTML Dashboard",
"type": "n8n-nodes-base.code",
"parameters": {
"jsCode": "const m = $input.first().json;\nconst mrr = parseFloat(m.total_mrr||0);\nconst last = parseFloat(m.last_mrr||0);\nconst wow = last > 0 ? (((mrr-last)/last)*100).toFixed(1) : 'N/A';\nconst flags = [];\nif ((m.nerc_cip_incidents_7d||0) > 0) flags.push(`\u26a0\ufe0f ${m.nerc_cip_incidents_7d} NERC CIP incidents this week \u2014 verify E-ISAC notifications`);\nif ((m.epa_incidents_7d||0) > 0) flags.push(`\u26a0\ufe0f ${m.epa_incidents_7d} EPA emission events \u2014 CAA \u00a7111(d) compliance review required`);\nif ((m.sec_incidents_7d||0) > 0) flags.push(`\ud83d\udea8 ${m.sec_incidents_7d} SEC climate events \u2014 4 business-day 8-K window check`);\nif ((m.overdue_compliance_30d||0) > 0) flags.push(`\ud83d\udea8 ${m.overdue_compliance_30d} overdue compliance deadlines in 30 days`);\nconst html = `<h2>EnergyTech Platform \u2014 Weekly KPI</h2><table border='1' cellpadding='6'><tr><th>Metric</th><th>Value</th></tr><tr><td>Total MRR</td><td>$${mrr.toLocaleString()}</td></tr><tr><td>MRR WoW</td><td>${wow}%</td></tr><tr><td>Active Customers</td><td>${m.active_customers||0}</td></tr><tr><td>New (7d)</td><td>${m.new_customers_7d||0}</td></tr><tr><td>Grid Mgmt SaaS</td><td>${m.grid_mgmt_count||0}</td></tr><tr><td>Renewable Energy SaaS</td><td>${m.renewable_count||0}</td></tr><tr><td>Energy Trading SaaS</td><td>${m.trading_count||0}</td></tr><tr><td>NERC CIP Incidents (7d)</td><td>${m.nerc_cip_incidents_7d||0}</td></tr><tr><td>EPA Emission Events (7d)</td><td>${m.epa_incidents_7d||0}</td></tr><tr><td>FERC Incidents (7d)</td><td>${m.ferc_incidents_7d||0}</td></tr><tr><td>SEC Climate Events (7d)</td><td>${m.sec_incidents_7d||0}</td></tr><tr><td>Overdue Compliance (30d)</td><td>${m.overdue_compliance_30d||0}</td></tr></table>${flags.length > 0 ? '<h3>Flags</h3><ul>' + flags.map(f=>`<li>${f}</li>`).join('') + '</ul>' : ''}<p><small>EnergyTech Platform | NERC CIP audit trail preserved in Postgres \u2014 git-versioned workflow JSON constitutes compliance control evidence</small></p>`;\nreturn [{json: {html, subject: `EnergyTech Weekly KPI \u2014 MRR $${mrr.toLocaleString()} | WoW ${wow}%`}}];"
}
},
{
"id": "7",
"name": "Gmail CEO + BCC NERC Compliance",
"type": "n8n-nodes-base.gmail",
"parameters": {
"toList": "ceo@your-company.com",
"ccList": "nerc-compliance@your-company.com",
"subject": "={{ $json.subject }}",
"message": "={{ $json.html }}",
"options": {
"isHtml": true
}
}
}
],
"connections": {
"Monday 7AM": {
"main": [
[
{
"node": "Query This Week Metrics",
"type": "main",
"index": 0
},
{
"node": "Query Last Week Metrics",
"type": "main",
"index": 0
},
{
"node": "Query Compliance Events",
"type": "main",
"index": 0
}
]
]
},
"Query This Week Metrics": {
"main": [
[
{
"node": "Merge KPI Streams",
"type": "main",
"index": 0
}
]
]
},
"Query Last Week Metrics": {
"main": [
[
{
"node": "Merge KPI Streams",
"type": "main",
"index": 1
}
]
]
},
"Query Compliance Events": {
"main": [
[
{
"node": "Merge KPI Streams",
"type": "main",
"index": 2
}
]
]
},
"Merge KPI Streams": {
"main": [
[
{
"node": "Build HTML Dashboard",
"type": "main",
"index": 0
}
]
]
},
"Build HTML Dashboard": {
"main": [
[
{
"node": "Gmail CEO + BCC NERC Compliance",
"type": "main",
"index": 0
}
]
]
}
}
}
The BCC pattern: NERC Compliance Officer is BCC'd, not CC'd. This preserves attorney-client privilege for any compliance communication that flows into litigation. Energy companies facing NERC enforcement proceedings have argued that internal compliance dashboards are attorney work-product — BCC creates a clean separation between the management reporting chain and the legal oversight chain.
The self-hosting argument for EnergyTech/CleanTech SaaS
NERC CIP is the clearest self-hosting requirement in any regulated industry. CIP-005-6 defines the Electronic Security Perimeter (ESP) — systems that store, process, or transmit BES Cyber System Information (BCSI) must be within the ESP. A cloud iPaaS vendor operating in a shared multi-tenant environment is not within the ESP. If your compliance automation workflows route BCSI through Zapier or Make, you have an ESP boundary violation that will appear on your next NERC audit.
Beyond CIP: the SEC climate disclosure rule creates a new chain-of-custody obligation for Scope 1/2 emission data. The FERC market manipulation rules (Order 1000 §35.41) require you to be able to reconstruct trading decisions from audit logs. EPA CEMS data must be preserved in tamper-evident format (40 CFR §75.59). None of these evidence chains survive a cloud vendor's data retention policy or a standard SaaS terms-of-service clause.
Self-hosted n8n in an AWS GovCloud or Azure Government environment: workflows are git-versioned JSON (audit evidence), execution logs are append-only Postgres (tamper detection), and the data never leaves the regulatory boundary.
The FlowKit n8n template store
These 5 workflows and 14 additional EnergyTech compliance templates are available at stripeai.gumroad.com — import-ready JSON, documented for NERC CIP, FERC, EPA, and SEC compliance.
All regulatory citations current as of May 2026. NERC CIP versions: CIP-004-6 through CIP-014-3. SEC Climate Rule subject to ongoing litigation stay — verify current effective date before 10-K implementation.
Top comments (0)