DEV Community

Alex Kane
Alex Kane

Posted on

n8n for ConstructionTech SaaS Vendors: 5 Automations for OSHA Part 1926, Davis-Bacon, AIA A201, and EPA NPDES Compliance

If your ConstructionTech SaaS handles OSHA incident records, Davis-Bacon payroll data, or EPA stormwater monitoring, you already have a compliance boundary problem you may not know about.

The core issue: Every time your workflow automation routes OSHA §1904.39 injury investigation records, Davis-Bacon WH-347 certified payroll data, or EPA NPDES discharge monitoring through a cloud iPaaS like Zapier or Make, that data touches an external server outside your legal privilege boundary. DOL Wage and Hour Division subpoenas don't just go to your customer — they go to every vendor in the data chain.

Self-hosted n8n keeps your ConstructionTech data in one place. Here are 5 automations built for the regulations that govern construction SaaS vendors.


The compliance landscape for ConstructionTech SaaS vendors

Your customers operate under a dense stack of overlapping federal and state requirements:

Regulation Jurisdiction Who it hits Fastest clock
OSHA 29 CFR Part 1926 Federal All construction employers §1904.39: 8h fatality report
Davis-Bacon Act 40 USC §3142 Federal Federal construction contracts WH-347 weekly payroll
EPA NPDES §402 CGP Federal Sites >1 acre disturbed SWPPP discharge monitoring
AIA A201-2017 Contract Commercial construction §15.1.3: 21-day claim notice
OSHA Form 300 Federal All construction employers Feb 1 annual posting
State prevailing wage State State-funded contracts Varies by state (quarterly typical)

Seven customer tiers shape how these requirements land in your platform:

  • ENTERPRISE_CONSTRUCTION_MGMT_PLATFORM — OSHA §1926 incident record system of record + AIA A201 document retention
  • PROJECT_MANAGEMENT_SAAS_VENDOR — AIA A201 §3.2 RFI/submittal/change order chain of custody
  • SAFETY_COMPLIANCE_SAAS_VENDOR — OSHA §1904.39 8h/24h incident reporting clocks are your core product
  • BIM_DESIGN_COLLABORATION_SAAS — AIA A201 design document retention + federal BIM mandate data sovereignty
  • SUBCONTRACTOR_MANAGEMENT_SAAS — Davis-Bacon WH-347 weekly submission + craft classification records
  • FIELD_WORKFORCE_MGMT_SAAS — OSHA Form 300 + Davis-Bacon field data = DOL audit boundary
  • CONSTRUCTIONTECH_STARTUP — Start with OSHA Form 300 and Davis-Bacon basics for enterprise procurement

Workflow 1: Tier-segmented customer onboarding drip

Injects the right compliance context at Day 0 based on customer tier and compliance flags (DAVIS_BACON_FEDERAL_CONTRACTOR, EPA_NPDES_STORMWATER_PERMIT, OSHA_1926_CONSTRUCTION_SUBJECT). Day 3 reinforces the 8-hour OSHA fatality clock. Day 8 sends pre-built workflow links for Davis-Bacon, OSHA Form 300, and EPA NPDES.

{
  "name": "ConstructionTech Customer Onboarding Drip",
  "nodes": [
    {
      "id": "n1",
      "name": "New Customer Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "parameters": {
        "sheetId": "YOUR_SHEET_ID",
        "range": "Customers!A:M",
        "event": "rowAdded"
      }
    },
    {
      "id": "n2",
      "name": "Extract & Segment Customer",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst row = $input.first().json;\nconst tier = row.customer_tier || 'CONSTRUCTIONTECH_STARTUP';\nconst flags = (row.compliance_flags || '').split(',').map(f => f.trim());\n\nconst tierNotes = {\n  ENTERPRISE_CONSTRUCTION_MGMT_PLATFORM: 'OSHA 29 CFR Part 1926 incident investigation records in your system are DOL subpoena targets \u2014 architecture brief in Day 0 email.',\n  PROJECT_MANAGEMENT_SAAS_VENDOR: 'AIA A201 \u00a73.2 RFI/submittal/change order retention: 10-year litigation hold window for project closeout claims.',\n  SAFETY_COMPLIANCE_SAAS_VENDOR: 'OSHA \u00a71904.39 severe injury 24h / fatality 8h notification clocks \u2014 your incident pipeline is the compliance boundary.',\n  BIM_DESIGN_COLLABORATION_SAAS: 'AIA A201 \u00a73.2 design document retention + NIST BIM IFC data sovereignty for federal BIM mandate projects.',\n  SUBCONTRACTOR_MANAGEMENT_SAAS: 'Davis-Bacon 40 USC \u00a73142 certified payroll (WH-347 weekly) \u2014 prevailing wage data in cloud iPaaS = DOL WHD audit exposure.',\n  FIELD_WORKFORCE_MGMT_SAAS: 'OSHA Form 300 injury log + Davis-Bacon craft classification records \u2014 field data sovereignty for DOL compliance boundary.',\n  CONSTRUCTIONTECH_STARTUP: 'Start with OSHA Form 300 and Davis-Bacon basics \u2014 fastest path to enterprise procurement.'\n};\n\nconst flagNotes = [];\nif (flags.includes('DAVIS_BACON_FEDERAL_CONTRACTOR')) flagNotes.push('Davis-Bacon WH-347 certified payroll weekly submission active for this account.');\nif (flags.includes('EPA_NPDES_STORMWATER_PERMIT')) flagNotes.push('EPA NPDES \u00a7402 stormwater SWPPP discharge monitoring data must stay in environmental compliance perimeter.');\nif (flags.includes('OSHA_1926_CONSTRUCTION_SUBJECT')) flagNotes.push('OSHA 29 CFR Part 1926 construction safety standards apply \u2014 incident investigation records in scope.');\n\nreturn [{\n  json: {\n    customer_id: row.customer_id,\n    company_name: row.company_name,\n    contact_email: row.contact_email,\n    tier,\n    flags,\n    tier_note: tierNotes[tier] || tierNotes.CONSTRUCTIONTECH_STARTUP,\n    flag_notes: flagNotes.join(' '),\n    onboarding_started: new Date().toISOString(),\n    crm_url: `https://crm.example.com/accounts/${row.customer_id}`\n  }\n}];\n"
      }
    },
    {
      "id": "n3",
      "name": "Day 0 Welcome Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.contact_email }}",
        "subject": "Welcome to FlowKit \u2014 your n8n automation is ready",
        "message": "={{ 'Hi ' + $json.company_name + ',\\n\\nYour n8n automation environment is live.\\n\\n' + $json.tier_note + '\\n\\n' + ($json.flag_notes ? $json.flag_notes + '\\n\\n' : '') + 'Book your onboarding call: https://cal.example.com/onboarding\\n\\nThe FlowKit Team' }}"
      }
    },
    {
      "id": "n4",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "sheetId": "YOUR_SHEET_ID",
        "range": "OnboardingLog!A:G",
        "values": [
          [
            "={{ $json.customer_id }}",
            "={{ $json.company_name }}",
            "={{ $json.tier }}",
            "={{ $json.flags.join(',') }}",
            "Day0",
            "={{ new Date().toISOString() }}",
            "sent"
          ]
        ]
      }
    },
    {
      "id": "n5",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "n6",
      "name": "Day 3 Check-In Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.contact_email }}",
        "subject": "Day 3: Your first workflow \u2014 any questions?",
        "message": "={{ 'Hi ' + $json.company_name + ',\\n\\nChecking in \u2014 have you triggered your first automation?\\n\\nTop tip for ' + $json.tier + ': configure your OSHA incident pipeline early \u2014 the 8-hour fatality reporting clock starts at discovery.\\n\\nReply to this email or book a call.\\n\\nThe FlowKit Team' }}"
      }
    },
    {
      "id": "n7",
      "name": "Wait 5 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 5,
        "unit": "days"
      }
    },
    {
      "id": "n8",
      "name": "Day 8 Feature Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.contact_email }}",
        "subject": "Day 8: 3 workflows your competitors are already running",
        "message": "={{ 'Hi ' + $json.company_name + ',\\n\\n3 workflows live customers run on Day 8:\\n\\n1. Davis-Bacon WH-347 certified payroll weekly deadline tracker (DOL WHD audit compliance)\\n2. OSHA Form 300 annual posting reminder (Feb 1 deadline \u2014 OSHA \u00a71904.32)\\n3. EPA NPDES stormwater SWPPP review scheduler (permit renewal compliance)\\n\\nAll 3 are pre-built in your template library.\\n\\nThe FlowKit Team' }}"
      }
    },
    {
      "id": "n9",
      "name": "Wait 6 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 6,
        "unit": "days"
      }
    },
    {
      "id": "n10",
      "name": "Day 14 QBR / Upgrade Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.contact_email }}",
        "subject": "Day 14: Book your 30-min QBR",
        "message": "={{ 'Hi ' + $json.company_name + ',\\n\\nTwo weeks in \u2014 time for a quick review of your compliance automation coverage.\\n\\nBook your 30-min QBR: https://cal.example.com/qbr\\n\\nAgenda: OSHA/Davis-Bacon/EPA workflow gaps, upgrade path to Enterprise tier.\\n\\nThe FlowKit Team' }}"
      }
    }
  ],
  "connections": {
    "n1": {
      "main": [
        [
          "n2"
        ]
      ]
    },
    "n2": {
      "main": [
        [
          "n3"
        ]
      ]
    },
    "n3": {
      "main": [
        [
          "n4"
        ]
      ]
    },
    "n4": {
      "main": [
        [
          "n5"
        ]
      ]
    },
    "n5": {
      "main": [
        [
          "n6"
        ]
      ]
    },
    "n6": {
      "main": [
        [
          "n7"
        ]
      ]
    },
    "n7": {
      "main": [
        [
          "n8"
        ]
      ]
    },
    "n8": {
      "main": [
        [
          "n9"
        ]
      ]
    },
    "n9": {
      "main": [
        [
          "n10"
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: OSHA/Davis-Bacon/AIA/EPA deadline tracker

Runs daily at 7AM against a Google Sheet of compliance deadlines. 12 deadline types with regulation-specific urgency notes. Routes OVERDUE/IMMEDIATE/CRITICAL items to Slack #compliance-critical and emails the compliance owner directly.

Deadline types covered:

  • OSHA_FATALITY_REPORT — OSHA §1904.39: 8h oral report after fatality discovery (fastest clock)
  • OSHA_SEVERE_INJURY_REPORT — §1904.39: 24h from employer learning of hospitalization/amputation/eye loss
  • OSHA_FORM_300_ANNUAL_SUMMARY_POSTING — Form 300A posted Feb 1 through Apr 30
  • DAVIS_BACON_CERTIFIED_PAYROLL_WEEKLY — WH-347 weekly for federal construction contracts
  • DAVIS_BACON_ANNUAL_WAGE_DETERMINATION_REVIEW — SAM.gov prevailing wage rate review by craft
  • EPA_NPDES_STORMWATER_SWPPP_ANNUAL_REVIEW — CGP permit holder annual SWPPP update
  • EPA_NPDES_CONSTRUCTION_PERMIT_RENEWAL — EPA §402 CGP permit renewal authorization
  • AIA_A201_PROJECT_CLOSEOUT_RETENTION — 10-year record retention post-substantial completion
  • OSHA_1926_SAFETY_TRAINING_ANNUAL — Part 1926 Subpart E training records 3-year minimum
  • PREVAILING_WAGE_QUARTERLY_REPORT — State prevailing wage (CA DIR/NY DOL/IL IDOL varies)
  • SOC2_TYPE2_RENEWAL — Annual audit cycle for enterprise ConstructionTech procurement
  • ANNUAL_PENTEST — SOC 2 CC7.1 penetration testing requirement
{
  "name": "ConstructionTech OSHA/Davis-Bacon/AIA/EPA Deadline Tracker",
  "nodes": [
    {
      "id": "d1",
      "name": "Daily 7AM Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "d2",
      "name": "Read Compliance Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "read",
        "sheetId": "YOUR_SHEET_ID",
        "range": "ComplianceDeadlines!A:G"
      }
    },
    {
      "id": "d3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst today = new Date();\nconst deadlines = $input.all().map(item => {\n  const d = item.json;\n  const dueDate = new Date(d.due_date);\n  const daysUntil = Math.ceil((dueDate - today) / (1000 * 60 * 60 * 24));\n\n  const typeClocks = {\n    OSHA_FATALITY_REPORT: { urgencyNote: 'OSHA \u00a71904.39 \u2014 8h oral report to OSHA Area Office after fatality discovery', fastest: true },\n    OSHA_SEVERE_INJURY_REPORT: { urgencyNote: 'OSHA \u00a71904.39 \u2014 24h report for inpatient hospitalization, amputation, or eye loss', fastest: true },\n    OSHA_FORM_300_ANNUAL_SUMMARY_POSTING: { urgencyNote: 'OSHA Form 300A must be posted Feb 1 through Apr 30 \u2014 \u00a71904.32', fastest: false },\n    DAVIS_BACON_CERTIFIED_PAYROLL_WEEKLY: { urgencyNote: 'Davis-Bacon 40 USC \u00a73142 \u2014 WH-347 certified payroll due weekly for federal construction contracts', fastest: false },\n    DAVIS_BACON_ANNUAL_WAGE_DETERMINATION_REVIEW: { urgencyNote: 'DOL wage determination review \u2014 check SAM.gov for current prevailing wage rates by craft classification', fastest: false },\n    EPA_NPDES_STORMWATER_SWPPP_ANNUAL_REVIEW: { urgencyNote: 'EPA NPDES \u00a7402 \u2014 SWPPP annual review and update required for CGP permit holders', fastest: false },\n    EPA_NPDES_CONSTRUCTION_PERMIT_RENEWAL: { urgencyNote: 'EPA Construction General Permit (CGP) renewal \u2014 EPA \u00a7402 stormwater discharge authorization', fastest: false },\n    AIA_A201_PROJECT_CLOSEOUT_RETENTION: { urgencyNote: 'AIA A201 \u00a73.5 + \u00a715.1.2 \u2014 project record retention 10 years post-substantial completion', fastest: false },\n    OSHA_1926_SAFETY_TRAINING_ANNUAL: { urgencyNote: 'OSHA 29 CFR Part 1926 Subpart E \u2014 safety training records maintained 3 years minimum', fastest: false },\n    PREVAILING_WAGE_QUARTERLY_REPORT: { urgencyNote: 'State prevailing wage quarterly payroll report \u2014 varies by state (CA DIR/NY DOL/IL IDOL)', fastest: false },\n    SOC2_TYPE2_RENEWAL: { urgencyNote: 'SOC 2 Type II audit renewal \u2014 CC9.2 vendor management scope includes n8n as infrastructure', fastest: false },\n    ANNUAL_PENTEST: { urgencyNote: 'Annual penetration test \u2014 required for enterprise ConstructionTech procurement and SOC 2 CC7.1', fastest: false }\n  };\n\n  const info = typeClocks[d.deadline_type] || { urgencyNote: 'Compliance deadline', fastest: false };\n\n  let urgency;\n  if (daysUntil < 0) urgency = 'OVERDUE';\n  else if (daysUntil <= 1 && info.fastest) urgency = 'IMMEDIATE';\n  else if (daysUntil <= 7) urgency = 'CRITICAL';\n  else if (daysUntil <= 21) urgency = 'URGENT';\n  else if (daysUntil <= 45) urgency = 'WARNING';\n  else urgency = 'NOTICE';\n\n  return { ...d, daysUntil, urgency, urgencyNote: info.urgencyNote };\n}).filter(d => d.urgency !== 'NOTICE');\n\nreturn deadlines.map(d => ({ json: d }));\n"
      }
    },
    {
      "id": "d4",
      "name": "Route by Urgency",
      "type": "n8n-nodes-base.switch",
      "parameters": {
        "rules": {
          "rules": [
            {
              "value1": "={{ $json.urgency }}",
              "operation": "equals",
              "value2": "OVERDUE"
            },
            {
              "value1": "={{ $json.urgency }}",
              "operation": "equals",
              "value2": "IMMEDIATE"
            },
            {
              "value1": "={{ $json.urgency }}",
              "operation": "equals",
              "value2": "CRITICAL"
            }
          ]
        }
      }
    },
    {
      "id": "d5",
      "name": "Slack #compliance-critical",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-critical",
        "text": "={{ '\ud83d\udea8 ' + $json.urgency + ': ' + $json.deadline_type + ' \u2014 ' + $json.daysUntil + 'd. ' + $json.urgencyNote + ' \u2014 Owner: ' + $json.owner }}"
      }
    },
    {
      "id": "d6",
      "name": "Email Compliance Owner",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "={{ $json.owner_email }}",
        "subject": "={{ '[' + $json.urgency + '] ' + $json.deadline_type + ' \u2014 ' + $json.daysUntil + ' days' }}",
        "message": "={{ $json.deadline_type + ' deadline in ' + $json.daysUntil + ' days.\\n\\n' + $json.urgencyNote + '\\n\\nCustomer: ' + $json.customer_name + '\\nDue: ' + $json.due_date + '\\nOwner: ' + $json.owner }}"
      }
    }
  ],
  "connections": {
    "d1": {
      "main": [
        [
          "d2"
        ]
      ]
    },
    "d2": {
      "main": [
        [
          "d3"
        ]
      ]
    },
    "d3": {
      "main": [
        [
          "d4"
        ]
      ]
    },
    "d4": {
      "main": [
        [
          "d5"
        ],
        [
          "d5"
        ],
        [
          "d5"
        ]
      ]
    },
    "d5": {
      "main": [
        [
          "d6"
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: ConstructionTech API health monitor

Polls five critical endpoints every 15 minutes with compliance annotations. The 15-minute interval is tighter than the OSHA §1904.39 8h fatality clock, ensuring you detect a safety incident API failure before a reporting window slips.

Endpoints and compliance mapping:

  • project_mgmt_api — OSHA 29 CFR Part 1926 incident investigation record integrity
  • bim_collaboration_api — AIA A201 §3.2 design document chain of custody
  • payroll_api — Davis-Bacon 40 USC §3142 certified payroll weekly clock; downtime = WH-347 delay risk
  • safety_incident_api — OSHA §1904.39 severe injury 24h / fatality 8h clock
  • stormwater_api — EPA NPDES §402 SWPPP discharge monitoring
{
  "name": "ConstructionTech BIM/Safety API Health Monitor",
  "nodes": [
    {
      "id": "m1",
      "name": "Every 15 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/15 * * * *"
            }
          ]
        }
      }
    },
    {
      "id": "m2",
      "name": "Read API Endpoints",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "read",
        "sheetId": "YOUR_SHEET_ID",
        "range": "ApiEndpoints!A:D"
      }
    },
    {
      "id": "m3",
      "name": "Ping Each Endpoint",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "={{ $json.endpoint_url }}",
        "method": "GET",
        "timeout": 5000
      }
    },
    {
      "id": "m4",
      "name": "Classify Health Status",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst endpoints = $input.all().map(item => {\n  const d = item.json;\n  const complianceAnnotations = {\n    project_mgmt_api: 'OSHA 29 CFR Part 1926 \u2014 incident investigation records integrity; downtime = OSHA \u00a71904.39 reporting gap risk',\n    bim_collaboration_api: 'AIA A201 \u00a73.2 design document retention \u2014 RFI/submittal/change order data chain of custody',\n    payroll_api: 'Davis-Bacon 40 USC \u00a73142 \u2014 certified payroll weekly clock; downtime = WH-347 submission delay risk (DOL WHD)',\n    safety_incident_api: 'OSHA \u00a71904.39 \u2014 severe injury 24h clock / fatality 8h clock; downtime = reporting window risk',\n    stormwater_api: 'EPA NPDES \u00a7402 \u2014 SWPPP discharge monitoring data; downtime = CGP permit deviation'\n  };\n\n  const latency = d.responseTime || 9999;\n  let status;\n  if (d.statusCode >= 200 && d.statusCode < 300 && latency < 2000) status = 'HEALTHY';\n  else if (d.statusCode >= 200 && d.statusCode < 300 && latency >= 2000) status = 'DEGRADED';\n  else status = 'DOWN';\n\n  return {\n    endpoint_name: d.endpoint_name,\n    status,\n    statusCode: d.statusCode,\n    latency,\n    compliance_note: complianceAnnotations[d.endpoint_name] || 'ConstructionTech compliance boundary',\n    checked_at: new Date().toISOString()\n  };\n}).filter(e => e.status !== 'HEALTHY');\n\nreturn endpoints.length > 0 ? endpoints.map(e => ({ json: e })) : [{ json: { all_healthy: true } }];\n"
      }
    },
    {
      "id": "m5",
      "name": "Alert Slack #construction-ops",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#construction-ops",
        "text": "={{ '\u26a0\ufe0f API ' + $json.status + ': ' + $json.endpoint_name + ' (' + $json.latency + 'ms). ' + $json.compliance_note }}"
      }
    }
  ],
  "connections": {
    "m1": {
      "main": [
        [
          "m2"
        ]
      ]
    },
    "m2": {
      "main": [
        [
          "m3"
        ]
      ]
    },
    "m3": {
      "main": [
        [
          "m4"
        ]
      ]
    },
    "m4": {
      "main": [
        [
          "m5"
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: OSHA/Davis-Bacon/EPA incident pipeline

Webhook endpoint that immediately ACKs with HTTP 200, then routes through a compliance matrix. Eight incident types with regulation-specific clocks, escalation lists, and authority contacts.

Incident types and clocks:

Incident Type Clock Authority Regulation
OSHA_FATALITY_REPORT 8 hours OSHA Area Office (oral) §1904.39
OSHA_SEVERE_INJURY_REPORT 24 hours OSHA Area Office §1904.39
OSHA_CITATION_RECEIVED IMMEDIATE — 15 working days to contest OSHA Area Office §659
EPA_STORMWATER_DISCHARGE_VIOLATION 14 days EPA Region / State agency NPDES §402
DAVIS_BACON_WAGE_VIOLATION_COMPLAINT IMMEDIATE — DOL WHD audit DOL Wage and Hour Division 40 USC §3142
AIA_A201_CLAIM_FILED 21 days Architect/Owner AIA A201 §15.1.3
SITE_SAFETY_EMERGENCY IMMEDIATE Emergency Services + OSHA 29 CFR Part 1926
DATA_BREACH_CONSTRUCTION_PII 72 hours State AG / CCPA / GDPR §1798.150 / Art.33

Fastest clocks: OSHA_FATALITY_REPORT (8h) and OSHA_SEVERE_INJURY_REPORT (24h)

{
  "name": "ConstructionTech OSHA/Davis-Bacon/EPA Incident Pipeline",
  "nodes": [
    {
      "id": "i1",
      "name": "Incident Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "path": "construction-incident",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "i2",
      "name": "Immediate 200 ACK",
      "type": "n8n-nodes-base.respondToWebhook",
      "parameters": {
        "responseCode": 200,
        "responseBody": "{\"status\":\"received\"}"
      }
    },
    {
      "id": "i3",
      "name": "Classify & Route Incident",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst p = $input.first().json.body || $input.first().json;\nconst type = p.incident_type;\n\nconst incidentMatrix = {\n  OSHA_FATALITY_REPORT: {\n    urgency: 'P0',\n    clock: '8 hours',\n    authority: 'OSHA Area Office (oral report required)',\n    regulation: 'OSHA \u00a71904.39 \u2014 must report within 8 hours of learning of a work-related fatality',\n    slackChannel: '#safety-critical',\n    escalateTo: ['CEO', 'CCO', 'Legal', 'EHS Director'],\n    note: 'FASTEST CLOCK \u2014 8h from discovery. Written follow-up within 8h. OSHA investigation may follow.'\n  },\n  OSHA_SEVERE_INJURY_REPORT: {\n    urgency: 'P0',\n    clock: '24 hours',\n    authority: 'OSHA Area Office',\n    regulation: 'OSHA \u00a71904.39 \u2014 inpatient hospitalization, amputation, or loss of an eye within 24h of employer learning',\n    slackChannel: '#safety-critical',\n    escalateTo: ['CCO', 'EHS Director', 'Legal'],\n    note: '24h from employer learning of injury \u2014 not from date of injury'\n  },\n  OSHA_CITATION_RECEIVED: {\n    urgency: 'P1',\n    clock: 'IMMEDIATE \u2014 15 working days to contest',\n    authority: 'OSHA Area Office',\n    regulation: 'OSHA \u00a7659 \u2014 15 working-day contest window starts on receipt date',\n    slackChannel: '#compliance-critical',\n    escalateTo: ['Legal', 'EHS Director', 'COO'],\n    note: 'Miss 15-day window = citation becomes final order; penalties not contestable'\n  },\n  EPA_STORMWATER_DISCHARGE_VIOLATION: {\n    urgency: 'P1',\n    clock: '14 days',\n    authority: 'EPA Region / State environmental agency',\n    regulation: 'EPA NPDES \u00a7402 CGP \u2014 discharge outside permit conditions must be reported',\n    slackChannel: '#compliance-critical',\n    escalateTo: ['Environmental Compliance', 'COO'],\n    note: 'SWPPP best management practices failure \u2014 document corrective actions immediately'\n  },\n  DAVIS_BACON_WAGE_VIOLATION_COMPLAINT: {\n    urgency: 'P1',\n    clock: 'IMMEDIATE \u2014 DOL WHD investigation',\n    authority: 'DOL Wage and Hour Division',\n    regulation: 'Davis-Bacon 40 USC \u00a73142 \u2014 prevailing wage underpayment complaint triggers WHD audit',\n    slackChannel: '#compliance-critical',\n    escalateTo: ['Legal', 'Payroll Compliance', 'CFO'],\n    note: 'Preserve all WH-347 certified payroll records \u2014 WHD subpoena typically covers 2-3 year lookback'\n  },\n  AIA_A201_CLAIM_FILED: {\n    urgency: 'P2',\n    clock: '21 days \u2014 AIA A201 \u00a715.1.3 notice requirement',\n    authority: 'Architect/Owner per contract',\n    regulation: 'AIA A201 \u00a715.1.3 \u2014 initial decision request must be filed within 21 days of claim notice',\n    slackChannel: '#legal-compliance',\n    escalateTo: ['Legal', 'Project Manager'],\n    note: 'Preserve all RFI/submittal/change order records for dispute \u2014 AIA A201 \u00a73.2 document retention'\n  },\n  SITE_SAFETY_EMERGENCY: {\n    urgency: 'P0',\n    clock: 'IMMEDIATE',\n    authority: 'Emergency Services + OSHA Area Office',\n    regulation: 'OSHA 29 CFR Part 1926 Subpart C \u2014 safety emergency protocols',\n    slackChannel: '#safety-critical',\n    escalateTo: ['EHS Director', 'Site Superintendent', 'COO'],\n    note: 'Follow emergency response plan \u2014 document all response actions for OSHA investigation'\n  },\n  DATA_BREACH_CONSTRUCTION_PII: {\n    urgency: 'P2',\n    clock: '72 hours',\n    authority: 'State AG / CCPA / GDPR supervisory authority',\n    regulation: 'CCPA \u00a71798.150 / GDPR Art.33 \u2014 notify within 72h of breach discovery',\n    slackChannel: '#security-incident',\n    escalateTo: ['CISO', 'Legal', 'DPO'],\n    note: 'Worker PII (SSN, payroll data) in construction records \u2014 Davis-Bacon records subject to CCPA'\n  }\n};\n\nconst info = incidentMatrix[type] || incidentMatrix.SITE_SAFETY_EMERGENCY;\n\nreturn [{\n  json: {\n    incident_id: p.incident_id || `INC-${Date.now()}`,\n    incident_type: type,\n    customer_id: p.customer_id,\n    customer_name: p.customer_name,\n    project_name: p.project_name,\n    description: p.description,\n    reported_by: p.reported_by,\n    reported_at: new Date().toISOString(),\n    ...info\n  }\n}];\n"
      }
    },
    {
      "id": "i4",
      "name": "Alert Slack",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "={{ $json.slackChannel }}",
        "text": "={{ '\ud83d\udea8 ' + $json.urgency + ' | ' + $json.incident_type + ' | ' + $json.customer_name + ' | Project: ' + $json.project_name + '\\nClock: ' + $json.clock + ' \u2014 Authority: ' + $json.authority + '\\nReg: ' + $json.regulation + '\\nNote: ' + $json.note }}"
      }
    },
    {
      "id": "i5",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "sheetId": "YOUR_SHEET_ID",
        "range": "IncidentLog!A:K",
        "values": [
          [
            "={{ $json.incident_id }}",
            "={{ $json.incident_type }}",
            "={{ $json.customer_id }}",
            "={{ $json.project_name }}",
            "={{ $json.urgency }}",
            "={{ $json.clock }}",
            "={{ $json.authority }}",
            "={{ $json.regulation }}",
            "={{ $json.reported_by }}",
            "={{ $json.reported_at }}",
            "OPEN"
          ]
        ]
      }
    }
  ],
  "connections": {
    "i1": {
      "main": [
        [
          "i2",
          "i3"
        ]
      ]
    },
    "i3": {
      "main": [
        [
          "i4"
        ]
      ]
    },
    "i4": {
      "main": [
        [
          "i5"
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Weekly ConstructionTech KPI dashboard

Monday 8AM executive briefing tracking MRR, active customers, and open compliance counts: OSHA incidents, Davis-Bacon violations, EPA NPDES pending, AIA A201 claims. Uses $getWorkflowStaticData for WoW MRR comparison. Emails CEO with BCC to CISO and CCO.

{
  "name": "Weekly ConstructionTech KPI Dashboard",
  "nodes": [
    {
      "id": "k1",
      "name": "Monday 8AM Schedule",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "k2",
      "name": "Read Platform Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "read",
        "sheetId": "YOUR_SHEET_ID",
        "range": "PlatformMetrics!A:Z"
      }
    },
    {
      "id": "k3",
      "name": "Read Compliance Events",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "read",
        "sheetId": "YOUR_SHEET_ID",
        "range": "ComplianceEvents!A:J"
      }
    },
    {
      "id": "k4",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst metrics = $input.all()[0]?.json || {};\nconst prevMetrics = $getWorkflowStaticData('global');\n\nconst mrr = parseFloat(metrics.mrr_usd || 0);\nconst prevMrr = parseFloat(prevMetrics.mrr_usd || 0);\nconst mrrWoW = prevMrr > 0 ? (((mrr - prevMrr) / prevMrr) * 100).toFixed(1) : 'N/A';\n\nconst active = parseInt(metrics.active_customers || 0);\nconst osha_open = parseInt(metrics.osha_incidents_open || 0);\nconst davis_bacon_open = parseInt(metrics.davis_bacon_violations_open || 0);\nconst epa_npdes_open = parseInt(metrics.epa_npdes_pending || 0);\nconst aia_claims_open = parseInt(metrics.aia_claims_open || 0);\nconst safety_trainings = parseInt(metrics.safety_training_completions_7d || 0);\nconst api_calls = parseInt(metrics.api_calls_7d || 0);\n\n$setWorkflowStaticData('global', { mrr_usd: mrr });\n\nconst html = `<h2>ConstructionTech Weekly KPI \u2014 ${new Date().toLocaleDateString()}</h2>\n<table border=\"1\" cellpadding=\"6\" style=\"border-collapse:collapse\">\n<tr><th>Metric</th><th>Value</th><th>WoW</th></tr>\n<tr><td>MRR</td><td>$${mrr.toLocaleString()}</td><td>${mrrWoW}%</td></tr>\n<tr><td>Active Customers</td><td>${active}</td><td>\u2014</td></tr>\n<tr><td>API Calls (7d)</td><td>${api_calls.toLocaleString()}</td><td>\u2014</td></tr>\n<tr><td style=\"color:${osha_open>0?'red':'green'}\">OSHA Incidents Open</td><td>${osha_open}</td><td>\u2014</td></tr>\n<tr><td style=\"color:${davis_bacon_open>0?'red':'green'}\">Davis-Bacon Violations Open</td><td>${davis_bacon_open}</td><td>\u2014</td></tr>\n<tr><td style=\"color:${epa_npdes_open>0?'orange':'green'}\">EPA NPDES Pending</td><td>${epa_npdes_open}</td><td>\u2014</td></tr>\n<tr><td style=\"color:${aia_claims_open>0?'orange':'green'}\">AIA A201 Claims Open</td><td>${aia_claims_open}</td><td>\u2014</td></tr>\n<tr><td>Safety Trainings (7d)</td><td>${safety_trainings}</td><td>\u2014</td></tr>\n</table>`;\n\nreturn [{ json: { html, mrr, active, osha_open, davis_bacon_open, epa_npdes_open, aia_claims_open } }];\n"
      }
    },
    {
      "id": "k5",
      "name": "Email CEO + BCC CISO",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "to": "ceo@example.com",
        "bcc": "ciso@example.com,cco@example.com",
        "subject": "ConstructionTech Weekly KPI",
        "message": "={{ $json.html }}",
        "messageType": "html"
      }
    },
    {
      "id": "k6",
      "name": "Slack #management One-Liner",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#management",
        "text": "={{ 'Weekly KPI: MRR $' + $json.mrr.toLocaleString() + ' | Customers ' + $json.active + ' | OSHA Open ' + $json.osha_open + ' | Davis-Bacon Open ' + $json.davis_bacon_open + ' | EPA Pending ' + $json.epa_npdes_open + ' | AIA Claims ' + $json.aia_claims_open }}"
      }
    }
  ],
  "connections": {
    "k1": {
      "main": [
        [
          "k2"
        ]
      ]
    },
    "k2": {
      "main": [
        [
          "k3"
        ]
      ]
    },
    "k3": {
      "main": [
        [
          "k4"
        ]
      ]
    },
    "k4": {
      "main": [
        [
          "k5"
        ]
      ]
    },
    "k5": {
      "main": [
        [
          "k6"
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why self-hosted n8n for ConstructionTech SaaS

Compliance requirement Cloud iPaaS risk Self-hosted n8n solution
OSHA §1904.39 incident records DOL subpoena outside privilege boundary — cloud vendor receives subpoena Records stay in your VPC; legal privilege intact
Davis-Bacon WH-347 certified payroll Prevailing wage data in Zapier = DOL WHD audit data outside perimeter Payroll data in compliance boundary; WHD audit scope controlled
EPA NPDES SWPPP discharge monitoring Environmental monitoring data in cloud = EPA audit chain gap Discharge data in environmental compliance perimeter
AIA A201 project closeout records RFI/change order history in cloud = litigation discovery risk 10-year retention in your document management perimeter
OSHA Form 300 injury log Worker injury data in cloud iPaaS = EEOC discovery exposure Injury records in OSHA-compliant boundary

Quick setup

  1. Import each workflow JSON into your n8n instance
  2. Set your Google Sheets IDs in all nodes
  3. Configure Slack webhook URLs for #compliance-critical, #safety-critical, #construction-ops
  4. Populate ComplianceDeadlines sheet with your customers' deadline data
  5. Set POST /construction-incident webhook URL in your safety incident system

All 5 workflows are included in the FlowKit n8n Template Library at stripeai.gumroad.com. The complete bundle includes 15 production-ready templates for $97.


Compare: n8n vs Zapier/Make for ConstructionTech SaaS

Capability n8n (self-hosted) Zapier / Make
OSHA record privacy In your VPC Data transits cloud
Davis-Bacon payroll data In compliance boundary Routes through cloud
EPA SWPPP monitoring data In environmental perimeter External server
AIA A201 retention control Your document system Cloud vendor data
8h OSHA fatality clock Sub-1-min trigger Cloud latency
Cost at scale Fixed hosting Per-task pricing

Questions? Drop them in the comments — I respond to all OSHA/Davis-Bacon/EPA questions.

Top comments (0)