DEV Community

Alex Kane
Alex Kane

Posted on

n8n for HealthIT SaaS Vendors: 5 Automations for FHIR, 21st Century Cures Act, and Information Blocking Compliance

If your company builds software that touches health data — EHR integrations, FHIR APIs, patient-facing apps, HIE connectors — you have compliance obligations that most automation guides ignore.

This isn't about HIPAA checklists. It's about the 21st Century Cures Act, ONC Health IT Certification, information blocking prohibition (45 CFR Part 171), and the CMS Interoperability Rule — the regulatory stack that governs HealthIT vendors selling to hospitals, health systems, payers, and government health programs.

These five n8n workflows cover the full HealthIT SaaS compliance loop: customer onboarding by institution type, FHIR API health monitoring, ONC/CMS deadline tracking, information blocking incident pipeline, and weekly platform KPI dashboard.

All workflow JSON is import-ready. Self-hosted n8n keeps PHI and USCDI data in your HIPAA-covered environment — routing HL7/FHIR payloads through Zapier or Make creates data egress liability and potential information blocking exposure.


1. HealthIT Customer Onboarding Drip by Institution Type

Different health system customers have radically different compliance contexts. An academic medical center has ONC certification surveillance obligations; a payer has CMS API mandates under 45 CFR §156.221; a TEFCA QHIN participant has exchange agreement obligations. Your Day 0 email should reflect that.

{
  "name": "HealthIT Customer Onboarding Drip",
  "nodes": [
    {
      "id": "1",
      "name": "Customer Signed",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "parameters": {
        "event": "rowAdded",
        "sheetId": "HEALTHIT_CUSTOMERS_SHEET_ID",
        "operation": "getAllRowChanges"
      },
      "position": [
        200,
        300
      ]
    },
    {
      "id": "2",
      "name": "Extract & Classify",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst row = $input.first().json;\nconst institutionType = row.institution_type || 'INTEGRATED_HEALTH_SYSTEM';\nconst flags = {\n  hl7FhirR4Certified: row.hl7_fhir_r4_certified === 'true',\n  twentyOneCuresScope: row.twenty_one_cures_scope === 'true',\n  oncCertified: row.onc_certified === 'true',\n  informationBlockingScope: row.information_blocking_scope === 'true',\n  cmsInteropRequired: row.cms_interop_required === 'true',\n  hipaaCoveredEntity: row.hipaa_covered_entity === 'true',\n  tefcaParticipant: row.tefca_participant === 'true'\n};\nconst typeMap = {\n  ACADEMIC_MEDICAL_CENTER: 'Academic Medical Center',\n  INTEGRATED_HEALTH_SYSTEM: 'Integrated Health System',\n  REGIONAL_HOSPITAL: 'Regional Hospital',\n  COMMUNITY_HOSPITAL: 'Community Hospital',\n  AMBULATORY_PHYSICIAN_GROUP: 'Ambulatory / Physician Group',\n  PAYER_HEALTH_PLAN: 'Payer / Health Plan',\n  GOVERNMENT_PUBLIC_HEALTH: 'Government / Public Health Agency'\n};\nreturn [{ json: {\n  name: row.contact_name, email: row.contact_email,\n  orgName: row.org_name, institutionType,\n  institutionLabel: typeMap[institutionType] || institutionType,\n  flags, signedAt: new Date().toISOString()\n}}];\n"
      },
      "position": [
        420,
        300
      ]
    },
    {
      "id": "3",
      "name": "Day 0 Welcome",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toEmail": "={{$json.email}}",
        "subject": "={{$json.orgName}} \u2014 Welcome to [Your Platform]",
        "emailType": "html",
        "message": "=<p>Hi {{$json.name}},</p><p>Welcome! As a {{$json.institutionLabel}}, your integration path includes:</p><ul>{{$json.flags.oncCertified ? '<li>ONC Health IT Certification surveillance (\u00a7170.315 annual reporting)</li>' : ''}}<br>{{$json.flags.twentyOneCuresScope ? '<li>21st Century Cures Act conditions of certification (\u00a7170.315(g)(10) patient API access)</li>' : ''}}<br>{{$json.flags.cmsInteropRequired ? '<li>CMS Interoperability Rule patient access API (45 CFR \u00a7156.221-225)</li>' : ''}}<br>{{$json.flags.informationBlockingScope ? '<li>Information blocking prohibition review (45 CFR Part 171 \u2014 actor obligations)</li>' : ''}}<br>{{$json.flags.tefcaParticipant ? '<li>TEFCA QHIN exchange agreement onboarding</li>' : ''}}</ul><p>Your implementation team will reach out within 24 hours. Reply to this email with any questions.</p>"
      },
      "position": [
        640,
        300
      ]
    },
    {
      "id": "4",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "HEALTHIT_ONBOARDING_LOG_SHEET_ID",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "position": [
        860,
        300
      ]
    },
    {
      "id": "5",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 3,
        "unit": "days"
      },
      "position": [
        1080,
        300
      ]
    },
    {
      "id": "6",
      "name": "Day 4 FHIR Onboarding Tips",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toEmail": "={{$json.email}}",
        "subject": "FHIR R4 endpoint setup \u2014 3 things to verify before go-live",
        "emailType": "html",
        "message": "=<p>Hi {{$json.name}},</p><p>Before your FHIR R4 endpoint goes live, verify:</p><ol><li><strong>SMART on FHIR scopes</strong> \u2014 \u00a7170.315(g)(10) requires launch/user/*.read minimum. Missing scopes = ONC condition of certification finding.</li><li><strong>USCDI v3 data classes</strong> \u2014 Map your resource coverage against the 28 USCDI v3 data classes (ONC Final Rule 2023). Gaps can trigger information blocking complaints under 45 CFR \u00a7171.103.</li><li><strong>Bulk FHIR export</strong> ($export endpoint) \u2014 Required for CMS patient access API compliance (45 CFR \u00a7156.221). Payer customers will test this on day 1.</li></ol><p>Reply with your FHIR base URL and we will run a quick conformance check.</p>"
      },
      "position": [
        1300,
        300
      ]
    },
    {
      "id": "7",
      "name": "Wait 4 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 4,
        "unit": "days"
      },
      "position": [
        1520,
        300
      ]
    },
    {
      "id": "8",
      "name": "Day 8 USCDI Mapping Guide",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toEmail": "={{$json.email}}",
        "subject": "USCDI data class mapping \u2014 your 30-day milestone",
        "emailType": "html",
        "message": "=<p>Hi {{$json.name}},</p><p>Your 30-day milestone: complete USCDI v3 data class coverage mapping. This protects you from information blocking complaints and accelerates ONC certification surveillance.</p><p>We have attached the USCDI v3 \u2192 FHIR R4 resource mapping template. Book a 30-min mapping session: [CALENDAR LINK]</p>"
      },
      "position": [
        1740,
        300
      ]
    }
  ],
  "connections": {
    "Customer Signed": {
      "main": [
        [
          {
            "node": "Extract & Classify",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract & Classify": {
      "main": [
        [
          {
            "node": "Day 0 Welcome",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 0 Welcome": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Sheets": {
      "main": [
        [
          {
            "node": "Wait 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Days": {
      "main": [
        [
          {
            "node": "Day 4 FHIR Onboarding Tips",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 4 FHIR Onboarding Tips": {
      "main": [
        [
          {
            "node": "Wait 4 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 Days": {
      "main": [
        [
          {
            "node": "Day 8 USCDI Mapping Guide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Institution types handled: ACADEMIC_MEDICAL_CENTER / INTEGRATED_HEALTH_SYSTEM / REGIONAL_HOSPITAL / COMMUNITY_HOSPITAL / AMBULATORY_PHYSICIAN_GROUP / PAYER_HEALTH_PLAN / GOVERNMENT_PUBLIC_HEALTH

Compliance flags: HL7_FHIR_R4_CERTIFIED / TWENTY_ONE_CURES_ACT_SCOPE / ONC_CERTIFIED / INFORMATION_BLOCKING_SCOPE / CMS_INTEROP_REQUIRED / HIPAA_COVERED_ENTITY / TEFCA_PARTICIPANT


2. FHIR API & HL7 Integration Health Monitor

HealthIT vendors have a unique liability that general SaaS vendors do not: a FHIR API outage may constitute information blocking under 45 CFR §171.103 if your platform is an "actor" (health IT developer with ONC-certified product). Monitoring with this in mind changes what you alert on.

{
  "name": "FHIR API Health Monitor",
  "nodes": [
    {
      "id": "1",
      "name": "Every 5 Min",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      },
      "position": [
        200,
        300
      ]
    },
    {
      "id": "2",
      "name": "Load Endpoint List",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nreturn [\n  { json: { id: 'fhir_r4_metadata', name: 'FHIR R4 Metadata (Conformance)', url: process.env.FHIR_BASE_URL + '/metadata', type: 'FHIR_R4', complianceNote: '\u00a7170.315(g)(10) ONC certification \u2014 mandatory for actor status' } },\n  { json: { id: 'fhir_patient_read', name: 'FHIR Patient $everything', url: process.env.FHIR_BASE_URL + '/Patient/$everything?_count=1', type: 'FHIR_R4', complianceNote: '45 CFR \u00a7171.103 information blocking actor \u2014 downtime is potential violation' } },\n  { json: { id: 'fhir_bulk_export', name: 'FHIR Bulk Export ($export)', url: process.env.FHIR_BASE_URL + '/$export', type: 'FHIR_R4', complianceNote: 'CMS Interoperability Rule 45 CFR \u00a7156.221 patient access API \u2014 required for payer customers' } },\n  { json: { id: 'hl7_v2_listener', name: 'HL7 v2.x ADT Listener', url: process.env.HL7_V2_HEALTH_URL, type: 'HL7_V2', complianceNote: 'HIPAA EDI standard \u2014 X12 834/835/837 downstream if ADT drops' } },\n  { json: { id: 'uscdi_mapping_api', name: 'USCDI Data Class Mapping API', url: process.env.USCDI_API_URL + '/health', type: 'USCDI', complianceNote: 'ONC USCDI v3 28 data classes \u2014 gap triggers information blocking risk' } }\n];\n"
      },
      "position": [
        420,
        300
      ]
    },
    {
      "id": "3",
      "name": "Poll Each Endpoint",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "={{$json.url}}",
        "method": "GET",
        "timeout": 8000,
        "options": {
          "response": {
            "response": {
              "neverError": true
            }
          }
        }
      },
      "position": [
        640,
        300
      ]
    },
    {
      "id": "4",
      "name": "Evaluate Health",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst endpoint = $('Load Endpoint List').item.json;\nconst statusCode = $input.first().json.statusCode || 0;\nconst responseTime = $input.first().json.responseTime || 9999;\nlet status = 'OK';\nlet severity = null;\nif (statusCode === 0 || statusCode >= 500) {\n  status = 'DOWN';\n  severity = 'CRITICAL';\n} else if (responseTime > 5000) {\n  status = 'DEGRADED';\n  severity = 'WARNING';\n} else if (statusCode === 429) {\n  status = 'THROTTLED';\n  severity = 'WARNING';\n}\nconst infoBlockingRisk = (status === 'DOWN' && endpoint.type === 'FHIR_R4') ?\n  '\u26a0\ufe0f INFORMATION BLOCKING RISK \u2014 45 CFR \u00a7171.103 actor obligation. Log outage start time immediately.' : '';\nconst cmsRisk = (endpoint.id === 'fhir_bulk_export' && status === 'DOWN') ?\n  '\u26a0\ufe0f CMS INTEROP RULE \u2014 Patient access API (45 CFR \u00a7156.221) down. Payer customers in scope.' : '';\nreturn [{ json: { ...endpoint, status, severity, statusCode, responseTime, infoBlockingRisk, cmsRisk, ts: new Date().toISOString() } }];\n"
      },
      "position": [
        860,
        300
      ]
    },
    {
      "id": "5",
      "name": "Filter Degraded",
      "type": "n8n-nodes-base.filter",
      "parameters": {
        "conditions": {
          "options": {
            "combinator": "or"
          },
          "conditions": [
            {
              "leftValue": "={{$json.status}}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "DOWN"
            },
            {
              "leftValue": "={{$json.status}}",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "rightValue": "DEGRADED"
            }
          ]
        }
      },
      "position": [
        1080,
        300
      ]
    },
    {
      "id": "6",
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": "#healthit-platform-ops",
        "text": "=\ud83d\udea8 *{{$json.status}}* \u2014 {{$json.name}} ({{$json.statusCode}}, {{$json.responseTime}}ms)\n\ud83d\udccb {{$json.complianceNote}}\n{{$json.infoBlockingRisk}}\n{{$json.cmsRisk}}\n\ud83d\udd50 {{$json.ts}}"
      },
      "position": [
        1300,
        300
      ]
    },
    {
      "id": "7",
      "name": "Log Incident",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "FHIR_INCIDENT_LOG_SHEET_ID",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      },
      "position": [
        1520,
        300
      ]
    }
  ],
  "connections": {
    "Every 5 Min": {
      "main": [
        [
          {
            "node": "Load Endpoint List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Endpoint List": {
      "main": [
        [
          {
            "node": "Poll Each Endpoint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Poll Each Endpoint": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Health": {
      "main": [
        [
          {
            "node": "Filter Degraded",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Degraded": {
      "main": [
        [
          {
            "node": "Slack Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack Alert": {
      "main": [
        [
          {
            "node": "Log Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why FHIR monitoring is different from standard SaaS monitoring: A downed FHIR patient-access endpoint can expose you to an OIG information blocking complaint (45 CFR §171.400 — disincentive up to $1M/violation for health IT developers). Log outage start timestamps. If downtime exceeds a material threshold, your legal team needs the data.


3. ONC / CMS / Information Blocking Compliance Deadline Tracker

HealthIT vendors live under overlapping ONC, CMS, HHS, and state-level deadline regimes. Missing ONC certification surveillance deadlines or CMS API compliance reviews can trigger certification withdrawal — a business-ending event for EHR vendors and health app developers.

{
  "name": "HealthIT Compliance Deadline Tracker",
  "nodes": [
    {
      "id": "1",
      "name": "Weekdays 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "position": [
        200,
        300
      ]
    },
    {
      "id": "2",
      "name": "Load Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "sheetId": "HEALTHIT_DEADLINES_SHEET_ID",
        "options": {
          "returnAllMatches": true
        }
      },
      "position": [
        420,
        300
      ]
    },
    {
      "id": "3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst today = new Date();\nconst results = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  const due = new Date(d.due_date);\n  const daysLeft = Math.ceil((due - today) / 86400000);\n  let urgency = null;\n  const deadlineLabels = {\n    ONC_HEALTH_IT_CERTIFICATION_SURVEILLANCE_ANNUAL: 'ONC Certification Surveillance (\u00a7170.556 annual developer report)',\n    TWENTY_ONE_CURES_CONDITION_OF_CERTIFICATION_ANNUAL: '21st Century Cures Act Condition of Certification (\u00a7170.315(g)(10))',\n    CMS_INTEROPERABILITY_RULE_API_COMPLIANCE_REVIEW: 'CMS Interoperability Rule API Compliance Review (45 CFR \u00a7156.221-225)',\n    INFORMATION_BLOCKING_PROHIBITION_ANNUAL_REVIEW: 'Information Blocking Prohibition Review (45 CFR Part 171 \u00a7171.103)',\n    TEFCA_QHIN_ANNUAL_PARTICIPATION_REVIEW: 'TEFCA QHIN Annual Participation & Exchange Agreement Review',\n    HIPAA_PRIVACY_SECURITY_RULE_ANNUAL_REVIEW: 'HIPAA Privacy & Security Rule Annual Risk Analysis (45 CFR \u00a7164.308(a)(1))',\n    SOC2_TYPE2_RENEWAL: 'SOC 2 Type II Audit Renewal',\n    ONC_USCDI_VERSION_UPGRADE: 'ONC USCDI Version Upgrade (USCDI v3\u2192v4 ONC roadmap)',\n    HL7_FHIR_VERSION_UPGRADE: 'HL7 FHIR Version Upgrade (R4\u2192R5 migration planning)',\n    STATE_HIN_PARTICIPATION_RENEWAL: 'State Health Information Network Participation Renewal',\n    CCPA_PATIENT_DATA_ANNUAL_AUDIT: 'CCPA Patient Data Annual Audit (Cal. Health & Safety Code \u00a7123148)',\n    ANNUAL_PENETRATION_TEST: 'Annual Penetration Test & Vulnerability Assessment'\n  };\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  if (urgency) {\n    results.push({ json: { ...d, daysLeft, urgency, deadlineLabel: deadlineLabels[d.deadline_type] || d.deadline_type } });\n  }\n}\nreturn results;\n"
      },
      "position": [
        640,
        300
      ]
    },
    {
      "id": "4",
      "name": "Route by Urgency",
      "type": "n8n-nodes-base.switch",
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{$json.urgency}}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "OVERDUE"
                  }
                ]
              },
              "outputKey": "overdue"
            },
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{$json.urgency}}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "CRITICAL"
                  }
                ]
              },
              "outputKey": "critical"
            },
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{$json.urgency}}",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    },
                    "rightValue": "URGENT"
                  }
                ]
              },
              "outputKey": "urgent"
            },
            {
              "conditions": {
                "conditions": [
                  {
                    "leftValue": "={{$json.urgency}}",
                    "operator": {
                      "type": "string",
                      "operation": "in"
                    },
                    "rightValue": "WARNING,NOTICE"
                  }
                ]
              },
              "outputKey": "watch"
            }
          ]
        }
      },
      "position": [
        860,
        300
      ]
    },
    {
      "id": "5",
      "name": "Critical Slack",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": "#healthit-compliance",
        "text": "=\ud83d\udea8 [{{$json.urgency}}] {{$json.deadlineLabel}}\nDue: {{$json.due_date}} ({{$json.daysLeft}} days)\nOwner: {{$json.owner}}\n{{$json.urgency === 'OVERDUE' ? '\u26a0\ufe0f OVERDUE \u2014 ONC/CMS escalation risk. Notify legal immediately.' : ''}}"
      },
      "position": [
        1080,
        200
      ]
    },
    {
      "id": "6",
      "name": "Owner Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toEmail": "={{$json.owner_email}}",
        "subject": "=[{{$json.urgency}}] Compliance deadline: {{$json.deadlineLabel}} \u2014 {{$json.daysLeft}} days",
        "emailType": "html",
        "message": "=<p>This is a compliance deadline reminder.</p><p><strong>{{$json.deadlineLabel}}</strong><br>Due: {{$json.due_date}} ({{$json.daysLeft}} days remaining)<br>Status: <strong>{{$json.urgency}}</strong></p><p>{{$json.urgency === 'OVERDUE' ? '<strong>\u26a0\ufe0f OVERDUE \u2014 contact your compliance officer immediately. ONC certification surveillance and CMS API compliance reviews have no grace period.</strong>' : 'Please ensure all required documentation and evidence are prepared by the due date.'}}</p>"
      },
      "position": [
        1080,
        400
      ]
    }
  ],
  "connections": {
    "Weekdays 8 AM": {
      "main": [
        [
          {
            "node": "Load Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Deadlines": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "Route by Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Urgency": {
      "overdue": [
        [
          {
            "node": "Critical Slack",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "critical": [
        [
          {
            "node": "Critical Slack",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "urgent": [
        [
          {
            "node": "Owner Email",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "watch": [
        [
          {
            "node": "Owner Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

12 deadline types tracked: ONC_HEALTH_IT_CERTIFICATION_SURVEILLANCE_ANNUAL / TWENTY_ONE_CURES_CONDITION_OF_CERTIFICATION_ANNUAL / CMS_INTEROPERABILITY_RULE_API_COMPLIANCE_REVIEW / INFORMATION_BLOCKING_PROHIBITION_ANNUAL_REVIEW / TEFCA_QHIN_ANNUAL_PARTICIPATION_REVIEW / HIPAA_PRIVACY_SECURITY_RULE_ANNUAL_REVIEW / SOC2_TYPE2_RENEWAL / ONC_USCDI_VERSION_UPGRADE / HL7_FHIR_VERSION_UPGRADE / STATE_HIN_PARTICIPATION_RENEWAL / CCPA_PATIENT_DATA_ANNUAL_AUDIT / ANNUAL_PENETRATION_TEST


4. Information Blocking & FHIR Incident Pipeline

HealthIT vendors face a regulatory reality that other SaaS companies do not: a security incident may simultaneously trigger HIPAA breach notification (60 days, 45 CFR §164.408), an information blocking complaint to the OIG (45 CFR §171.400), and an ONC condition-of-certification breach finding. Track all three clocks from one webhook.

{
  "name": "Information Blocking & FHIR Incident Pipeline",
  "nodes": [
    {
      "id": "1",
      "name": "Incident Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "path": "healthit-incident",
        "httpMethod": "POST",
        "authentication": "headerAuth"
      },
      "position": [
        200,
        300
      ]
    },
    {
      "id": "2",
      "name": "Classify Incident",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst body = $input.first().json.body || $input.first().json;\nconst incidentType = body.incident_type;\nconst detectedAt = body.detected_at || new Date().toISOString();\nconst now = new Date(detectedAt);\nconst typeConfig = {\n  INFORMATION_BLOCKING_COMPLAINT: {\n    label: 'Information Blocking Complaint Filed (OIG)',\n    regulation: '45 CFR \u00a7171.400 \u2014 Disincentive up to $1M/violation for health IT developers',\n    clock: '45 CFR \u00a7171.400 \u2014 No fixed statutory response window; OIG investigation timeline varies',\n    severity: 'CRITICAL', notifyLegal: true,\n    note: 'Log complaint receipt timestamp. Contact health law counsel immediately. ONC may trigger certification review.'\n  },\n  FHIR_API_UNAUTHORIZED_ACCESS: {\n    label: 'FHIR API Unauthorized Access',\n    regulation: 'HIPAA Security Rule 45 CFR \u00a7164.312(a)(1) + information blocking 45 CFR \u00a7171.103 actor breach',\n    clock: 'HIPAA breach: 60 calendar days from discovery to HHS notification (45 CFR \u00a7164.408)',\n    hipaaBreachClock: true,\n    hipaaDueDateDays: 60,\n    severity: 'CRITICAL', notifyLegal: true\n  },\n  USCDI_DATA_INTEGRITY_ERROR: {\n    label: 'USCDI Data Integrity Error \u2014 ONC Certification Finding',\n    regulation: '21st Century Cures Act \u00a7170.315(g)(10) condition of certification \u2014 data accuracy obligation',\n    clock: 'No fixed clock; ONC may open certification deficiency review. Document remediation start.',\n    severity: 'HIGH', notifyLegal: false\n  },\n  CMS_INTEROPERABILITY_RULE_VIOLATION: {\n    label: 'CMS Interoperability Rule Violation \u2014 Patient Access API Down',\n    regulation: '45 CFR \u00a7156.221 \u2014 Patient access API mandatory for payers with >1,000 enrollees',\n    clock: 'CMS complaint review \u2014 document incident duration for CMS prior authorization API audit trail',\n    severity: 'HIGH', notifyLegal: true\n  },\n  TEFCA_QHIN_DATA_EXCHANGE_FAILURE: {\n    label: 'TEFCA QHIN Data Exchange Failure',\n    regulation: 'TEFCA Exchange Agreement obligations \u2014 QHIN participation requirements',\n    clock: 'TEFCA incident reporting \u2014 contact your QHIN participant agreement terms',\n    severity: 'HIGH', notifyLegal: false\n  },\n  HL7_PHI_EXFILTRATION: {\n    label: 'HL7 PHI Data Exfiltration',\n    regulation: 'HIPAA HITECH Breach Notification Rule 45 CFR \u00a7\u00a7164.400-414',\n    clock: 'HIPAA breach: 60 calendar days from discovery (45 CFR \u00a7164.408)',\n    hipaaBreachClock: true,\n    hipaaDueDateDays: 60,\n    severity: 'CRITICAL', notifyLegal: true\n  },\n  ONC_CERTIFICATION_CONDITION_BREACH: {\n    label: 'ONC Certification Condition Breach \u2014 \u00a7170.315(g)(10) App Access Violation',\n    regulation: 'ONC Health IT Certification Program \u2014 condition of certification \u00a7170.315(g)(10)',\n    clock: 'ONC certification surveillance finding \u2014 document immediately, remediation required before next surveillance cycle',\n    severity: 'CRITICAL', notifyLegal: true\n  },\n  STATE_HIN_DATA_BREACH: {\n    label: 'State Health Information Network Data Breach',\n    regulation: 'State HIE participation agreement + state breach notification law',\n    clock: 'Varies by state \u2014 typically 30-60 days. Cross-reference HIPAA 60-day clock.',\n    hipaaBreachClock: true,\n    hipaaDueDateDays: 60,\n    severity: 'HIGH', notifyLegal: true\n  }\n};\nconst config = typeConfig[incidentType] || { label: incidentType, severity: 'MEDIUM', regulation: 'Review applicable regulations', clock: 'TBD', notifyLegal: false };\nlet hipaaClockDue = null;\nif (config.hipaaBreachClock) {\n  const due = new Date(now);\n  due.setDate(due.getDate() + (config.hipaaDueDateDays || 60));\n  hipaaClockDue = due.toISOString().split('T')[0];\n}\nreturn [{ json: { ...body, incidentType, detectedAt, ...config, hipaaClockDue, incidentId: 'INC-' + Date.now() } }];\n"
      },
      "position": [
        420,
        300
      ]
    },
    {
      "id": "3",
      "name": "Slack CISO Alert",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "resource": "message",
        "operation": "post",
        "channel": "#healthit-security-incidents",
        "text": "=\ud83d\udea8 *{{$json.severity}}* \u2014 {{$json.label}}\n\ud83d\udccb {{$json.regulation}}\n\u23f0 Clock: {{$json.clock}}\n{{$json.hipaaClockDue ? '\ud83c\udfe5 HIPAA breach notification due: ' + $json.hipaaClockDue + ' (60 days from discovery)' : ''}}\n{{$json.notifyLegal ? '\u2696\ufe0f LEGAL NOTIFICATION REQUIRED' : ''}}\nIncident ID: {{$json.incidentId}}\nDetected: {{$json.detectedAt}}"
      },
      "position": [
        640,
        300
      ]
    },
    {
      "id": "4",
      "name": "Log to Postgres",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO healthit_incidents (incident_id, incident_type, severity, detected_at, hipaa_clock_due, notify_legal, regulation, created_at) VALUES ('{{$json.incidentId}}', '{{$json.incidentType}}', '{{$json.severity}}', '{{$json.detectedAt}}', {{$json.hipaaClockDue ? \"'\" + $json.hipaaClockDue + \"'\" : 'NULL'}}, {{$json.notifyLegal}}, '{{$json.regulation}}', NOW()) ON CONFLICT DO NOTHING"
      },
      "position": [
        860,
        300
      ]
    }
  ],
  "connections": {
    "Incident Webhook": {
      "main": [
        [
          {
            "node": "Classify Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident": {
      "main": [
        [
          {
            "node": "Slack CISO Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack CISO Alert": {
      "main": [
        [
          {
            "node": "Log to Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

8 incident types: INFORMATION_BLOCKING_COMPLAINT / FHIR_API_UNAUTHORIZED_ACCESS / USCDI_DATA_INTEGRITY_ERROR / CMS_INTEROPERABILITY_RULE_VIOLATION / TEFCA_QHIN_DATA_EXCHANGE_FAILURE / HL7_PHI_EXFILTRATION / ONC_CERTIFICATION_CONDITION_BREACH / STATE_HIN_DATA_BREACH

Regulatory clocks tracked: HIPAA breach notification 60-day clock (45 CFR §164.408) / ONC certification condition deficiency / Information blocking OIG complaint receipt timestamp


5. Weekly HealthIT SaaS KPI Dashboard

Your CISO and Compliance Officer need visibility on FHIR API health, ONC certification status, and information blocking risk alongside MRR and activation metrics — in one weekly briefing.

{
  "name": "Weekly HealthIT SaaS KPI Dashboard",
  "nodes": [
    {
      "id": "1",
      "name": "Monday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "position": [
        200,
        300
      ]
    },
    {
      "id": "2",
      "name": "Query Platform Metrics",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT\n  COUNT(DISTINCT customer_id) FILTER (WHERE status='active') as active_customers,\n  COUNT(DISTINCT customer_id) FILTER (WHERE status='trial') as trial_customers,\n  SUM(mrr_usd) as total_mrr,\n  LAG(SUM(mrr_usd), 1) OVER (ORDER BY week_start) as prev_mrr,\n  AVG(fhir_api_uptime_pct) as avg_fhir_uptime,\n  COUNT(*) FILTER (WHERE fhir_api_uptime_pct < 99.9) as fhir_sla_breach_customers,\n  COUNT(*) FILTER (WHERE uscdi_coverage_pct < 100) as uscdi_gap_customers,\n  COUNT(*) FILTER (WHERE onboarding_complete = false AND signup_date < NOW() - INTERVAL '14 days') as stalled_onboardings,\n  SUM(fhir_api_calls_weekly) as total_fhir_calls\nFROM healthit_platform_metrics\nWHERE week_start >= NOW() - INTERVAL '7 days'"
      },
      "position": [
        420,
        300
      ]
    },
    {
      "id": "3",
      "name": "Query Compliance Events",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT\n  COUNT(*) FILTER (WHERE incident_type = 'INFORMATION_BLOCKING_COMPLAINT' AND created_at >= NOW() - INTERVAL '7 days') as info_blocking_complaints_week,\n  COUNT(*) FILTER (WHERE severity = 'CRITICAL' AND created_at >= NOW() - INTERVAL '7 days') as critical_incidents_week,\n  COUNT(*) FILTER (WHERE notify_legal = true AND created_at >= NOW() - INTERVAL '7 days') as legal_notification_events,\n  COUNT(*) FILTER (WHERE hipaa_clock_due IS NOT NULL AND hipaa_clock_due <= NOW()::date + 14) as hipaa_clocks_due_14d\nFROM healthit_incidents"
      },
      "position": [
        420,
        500
      ]
    },
    {
      "id": "4",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst platform = $('Query Platform Metrics').first().json;\nconst compliance = $('Query Compliance Events').first().json;\nconst mrrWoW = platform.prev_mrr ? (((platform.total_mrr - platform.prev_mrr) / platform.prev_mrr) * 100).toFixed(1) : 'N/A';\nconst flags = [];\nif (compliance.info_blocking_complaints_week > 0) flags.push('[ONC INFO BLOCKING COMPLAINT THIS WEEK]');\nif (platform.fhir_sla_breach_customers > 0) flags.push('[FHIR SLA BREACH \u2014 ' + platform.fhir_sla_breach_customers + ' CUSTOMERS]');\nif (compliance.hipaa_clocks_due_14d > 0) flags.push('[HIPAA BREACH CLOCK DUE <14 DAYS]');\nif (platform.uscdi_gap_customers > 0) flags.push('[USCDI COVERAGE GAPS \u2014 ' + platform.uscdi_gap_customers + ' CUSTOMERS]');\nconst html = '<h2>Weekly HealthIT SaaS KPI \u2014 ' + new Date().toISOString().split('T')[0] + '</h2>' +\n  (flags.length ? '<p style=\"color:red\"><strong>' + flags.join('<br>') + '</strong></p>' : '') +\n  '<h3>Platform Metrics</h3><table border=\"1\" cellpadding=\"4\"><tr><th>Metric</th><th>Value</th></tr>' +\n  '<tr><td>Active Customers</td><td>' + platform.active_customers + '</td></tr>' +\n  '<tr><td>Trial Customers</td><td>' + platform.trial_customers + '</td></tr>' +\n  '<tr><td>Total MRR</td><td>$' + Number(platform.total_mrr).toLocaleString() + ' (' + (mrrWoW >= 0 ? '+' : '') + mrrWoW + '% WoW)</td></tr>' +\n  '<tr><td>FHIR API Calls (7d)</td><td>' + Number(platform.total_fhir_calls).toLocaleString() + '</td></tr>' +\n  '<tr><td>Avg FHIR Uptime</td><td>' + Number(platform.avg_fhir_uptime).toFixed(2) + '%</td></tr>' +\n  '<tr><td>USCDI Gap Customers</td><td>' + platform.uscdi_gap_customers + '</td></tr>' +\n  '<tr><td>Stalled Onboardings (>14d)</td><td>' + platform.stalled_onboardings + '</td></tr>' +\n  '</table><h3>Compliance Events (7d)</h3><table border=\"1\" cellpadding=\"4\">' +\n  '<tr><td>Info Blocking Complaints</td><td>' + compliance.info_blocking_complaints_week + '</td></tr>' +\n  '<tr><td>Critical Incidents</td><td>' + compliance.critical_incidents_week + '</td></tr>' +\n  '<tr><td>Legal Notification Events</td><td>' + compliance.legal_notification_events + '</td></tr>' +\n  '<tr><td>HIPAA Clocks Due <14 Days</td><td>' + compliance.hipaa_clocks_due_14d + '</td></tr>' +\n  '</table>';\nreturn [{ json: { html, flags, mrrWoW, platform, compliance } }];\n"
      },
      "position": [
        640,
        400
      ]
    },
    {
      "id": "5",
      "name": "Send KPI Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toEmail": "={{$env.CEO_EMAIL}}",
        "ccEmail": "={{$env.CISO_EMAIL}},={{$env.COMPLIANCE_OFFICER_EMAIL}}",
        "subject": "=Weekly HealthIT KPI \u2014 {{$json.flags.length > 0 ? $json.flags[0] : 'All Clear'}}",
        "emailType": "html",
        "message": "={{$json.html}}"
      },
      "position": [
        860,
        400
      ]
    }
  ],
  "connections": {
    "Monday 8 AM": {
      "main": [
        [
          {
            "node": "Query Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Query Compliance Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Platform Metrics": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Compliance Events": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Send KPI Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Subject line flags: [ONC INFO BLOCKING COMPLAINT THIS WEEK] / [FHIR SLA BREACH] / [HIPAA BREACH CLOCK DUE <14 DAYS] / [USCDI COVERAGE GAPS]

CISO BCC is intentional: HealthIT SaaS CISO needs visibility on FHIR uptime, information blocking complaints, and HIPAA clocks alongside MRR. When the CISO sees both metrics in one email, governance gaps close themselves.


Why self-hosted n8n — the HealthIT SaaS case

Concern Zapier / Make Self-hosted n8n
FHIR payload with PHI in automation Data egress to cloud iPaaS — HIPAA BAA required, data leaves your VPC Stays in your HIPAA-covered environment
USCDI data class mappings Third-party cloud processes controlled health data In-VPC processing, no external data egress
Information blocking audit trail Automation logs in vendor cloud Git-versioned JSON + on-prem logs
ONC certification surveillance evidence Workflow runs in external SaaS — CC9.2 vendor risk Self-hosted = internal infrastructure
SOC 2 CC9.2 vendor inventory Zapier/Make added to vendor list Eliminated from external vendor list

The information blocking prohibition (45 CFR Part 171) makes this concrete: if your FHIR API goes down and you are an ONC-certified health IT developer, you are an "actor" under §171.103. Routing your monitoring alerts through Zapier means your incident logs sit in a third-party cloud environment — not your HIPAA-covered, SOC2-audited infrastructure.


Get the templates

All five workflows are available at stripeai.gumroad.com — individual templates from $12, full automation bundle at $97.

If you are building HealthIT infrastructure and want to discuss compliance automation architecture, reach out at openstripeai@gmail.com.

Top comments (0)