DEV Community

Alex Kane
Alex Kane

Posted on

n8n for HRTech/Recruiting AI SaaS Vendors: 5 Automations for EEOC, NYC Local Law 144, IL AI Video Interview Act, and GDPR Art.22 Compliance

n8n for HRTech/Recruiting AI SaaS Vendors: 5 Automations for EEOC, NYC Local Law 144, IL AI Video Interview Act, and GDPR Art.22 Compliance

HRTech and Recruiting AI SaaS vendors are now directly in regulators' crosshairs. The wave of AI hiring laws enacted between 2022 and 2026 creates compliance obligations not just for employers—but for the SaaS vendors whose algorithms make the decisions.

The financial exposure is real: NYC Local Law 144 carries $375,000+ fines per violation. Illinois's AI Video Interview Act imposes liability for every recording not deleted within 30 days. GDPR Art.22 right-to-explanation failures create €20M or 4% global revenue exposure. EEOC adverse impact findings trigger enforcement investigations.

If your platform touches hiring decisions—ATS, AI video interviews, skills assessments, background checks, compensation analytics—you are in scope.

This article provides five production-ready n8n workflows with full import-ready JSON for the seven HRTech AI SaaS tiers:

Tier Primary Obligations
ENTERPRISE_ATS_SAAS EEOC UGESP, OFCCP AAP §60-2, NYC LL144, GDPR Art.22, FLSA §211(c)
SMB_HIRING_PLATFORM NYC LL144, EEOC UGESP, IL AIVIA, MD HB 1202, Cal. FEHA
AI_VIDEO_INTERVIEW_SAAS IL AIVIA 820 ILCS 42, NYC LL144, GDPR Art.22, CCPA
SKILLS_ASSESSMENT_SAAS EEOC UGESP 4/5ths rule, GDPR Art.22, NYC LL144
BACKGROUND_CHECK_SAAS FCRA, EEOC disparate impact, OFCCP, FLSA §211(c)
COMPENSATION_ANALYTICS_SAAS EPA, OFCCP §60-2 AAP, NYC Pay Transparency Law 195-d
HRTECH_AI_STARTUP NYC LL144, EEOC UGESP, IL AIVIA, GDPR Art.22 (if EU data)

Why n8n vs Zapier or Make?

n8n (self-hosted) Zapier Make
Candidate PII stays on-premise
AEDT decision scores auditable ✅ git-versioned
Protected class data never in vendor cloud
EEOC/OFCCP audit trail
IL AIVIA deletion proof Limited Limited
GDPR Art.22 explanation chain
SOC2 CC9.2 vendor risk Closed Open finding Open finding

Routing candidate PII, AEDT scores, and protected class data through Zapier or Make's cloud creates EEOC audit exposure, GDPR Art.46 transfer liability, and SOC2 findings. Self-hosted n8n closes all three in one architectural decision.


Workflow 1: NYC Local Law 144 Annual Bias Audit Scheduler

Who needs it: ENTERPRISE_ATS_SAAS, AI_VIDEO_INTERVIEW_SAAS, SKILLS_ASSESSMENT_SAAS, SMB_HIRING_PLATFORM

NYC Local Law 144 (NYC Admin Code §5-336) requires employers using automated employment decision tools (AEDTs) for NYC-based candidates to commission an independent annual bias audit by an accredited third party—and publish the results. For SaaS vendors, this means your customers need audit support, and increasingly, vendors are being named in audits.

This workflow runs daily on weekdays, loads the audit schedule from a Google Sheet, calculates days-until-audit-due, and routes by urgency (OVERDUE/CRITICAL/URGENT/WARNING). OVERDUE audits trigger immediate Slack alerts to #compliance-critical and email to Legal and CPO with penalty exposure ($375,000+ per violation, $1,500/day continued use after finding).

Key compliance detail: NYC LL144 also requires employers to post notice to candidates at least 10 business days before using an AEDT in their assessment. Your SaaS platform should trigger this automatically.

{
  "name": "NYC LL144 Annual Bias Audit Scheduler",
  "nodes": [
    {
      "id": "a1",
      "name": "Daily Weekdays 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "a2",
      "name": "Load Audit Schedule",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "spreadsheetId": "={{$vars.AUDIT_SHEET_ID}}",
        "range": "BiasAudits!A:I"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "a3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const today=new Date();const items=$input.all();const out=[];for(const item of items){const d=item.json;if(!d.audit_due_date||d.status==='COMPLETE')continue;const due=new Date(d.audit_due_date);const days=Math.ceil((due-today)/86400000);let urgency=null;if(days<0)urgency='OVERDUE';else if(days<=14)urgency='CRITICAL';else if(days<=30)urgency='URGENT';else if(days<=60)urgency='WARNING';if(urgency)out.push({json:{...d,days_until_audit:days,urgency,regulation:'NYC_LL144',requirement:'Annual independent bias audit - NYC Admin Code \u00a75-336(b)'}});}return out;"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "a4",
      "name": "IF OVERDUE",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "leftValue": "={{$json.urgency}}",
              "rightValue": "OVERDUE",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "a5",
      "name": "Slack #compliance-critical",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-critical",
        "text": "=\ud83d\udea8 NYC LL144 {{$json.urgency}}: {{$json.employer_name}} bias audit due {{$json.audit_due_date}} ({{$json.days_until_audit}}d). \u00a75-336(b) annual AEDT audit required. Penalty: $375K+ 1st violation."
      },
      "position": [
        1050,
        200
      ]
    },
    {
      "id": "a6",
      "name": "Email Legal & CPO",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "sendTo": "={{$json.legal_email}},={{$json.cpo_email}}",
        "subject": "=[NYC LL144] {{$json.urgency}} Bias Audit \u2014 {{$json.employer_name}}",
        "message": "=NYC Local Law 144 Audit Status\n\nEmployer: {{$json.employer_name}}\nDue: {{$json.audit_due_date}}\nStatus: {{$json.urgency}} ({{$json.days_until_audit}} days)\nRegulation: NYC Admin Code \u00a75-336(b)\n\nRequired Actions:\n1. Retain accredited third-party auditor (NYC DCWP approved)\n2. Publish audit summary on company website within 30d of completion\n3. Post candidate notice 10 days before each AEDT assessment\n\nPenalty: $375,000/violation + $1,500/day continued use after finding"
      },
      "position": [
        1050,
        400
      ]
    },
    {
      "id": "a7",
      "name": "Log to AuditLog Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "spreadsheetId": "={{$vars.AUDIT_SHEET_ID}}",
        "range": "AuditLog!A:G",
        "data": {
          "mappingMode": "defineBelow",
          "values": [
            {
              "name": "employer_id",
              "value": "={{$json.employer_id}}"
            },
            {
              "name": "check_date",
              "value": "={{new Date().toISOString()}}"
            },
            {
              "name": "urgency",
              "value": "={{$json.urgency}}"
            },
            {
              "name": "days_until_audit",
              "value": "={{$json.days_until_audit}}"
            }
          ]
        }
      },
      "position": [
        1050,
        600
      ]
    }
  ],
  "connections": {
    "Daily Weekdays 8AM": {
      "main": [
        [
          {
            "node": "Load Audit Schedule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Audit Schedule": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "IF OVERDUE",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF OVERDUE": {
      "main": [
        [
          {
            "node": "Slack #compliance-critical",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Legal & CPO",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Legal & CPO": {
      "main": [
        [
          {
            "node": "Log to AuditLog Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: IL AI Video Interview Act 30-Day Candidate Deletion Tracker

Who needs it: AI_VIDEO_INTERVIEW_SAAS, ENTERPRISE_ATS_SAAS, SMB_HIRING_PLATFORM

The Illinois AI Video Interview Act (820 ILCS 42) requires employers using AI to analyze video interviews to: (1) obtain written consent before the interview, (2) provide disclosure of the AI features being assessed, (3) limit sharing of recordings, and (4) delete all video recordings and AI analysis data within 30 days of the final hiring decision—unless the candidate consents to longer retention.

This workflow handles the full AIVIA compliance loop. When a candidate interview is scheduled, it checks if the candidate is an Illinois resident and if it's an AI video interview. If yes, it sends a consent email (with disclosure language) and logs the deletion due date. A separate daily check fires at 7AM, identifies recordings past their deletion deadline, calls your ATS API to delete them, and logs confirmation to Slack #legal-privacy.

Penalty: Illinois AG enforcement, civil suits from candidates, EEOC retaliation risk if deletion requests are ignored.

{
  "name": "IL AI Video Interview Act 30-Day Deletion Tracker",
  "nodes": [
    {
      "id": "b1",
      "name": "Candidate Interview Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "il-video-consent"
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "b2",
      "name": "Check IL Resident + Video",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const d=$input.first().json;const il_resident=d.candidate_state==='IL'||(d.candidate_zip||'').startsWith('6');const video_interview=d.interview_type==='video'||d.interview_type==='ai_video';return[{json:{...d,requires_il_compliance:il_resident&&video_interview,deletion_due:new Date(Date.now()+30*86400000).toISOString().split('T')[0],regulation:'IL_AIVIA',statute:'820 ILCS 42',requirement:'Written consent+disclosure required before recording; delete within 30d of final decision'}}];"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "b3",
      "name": "IF IL Compliance Required",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "leftValue": "={{$json.requires_il_compliance}}",
              "rightValue": "true",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "b4",
      "name": "Email Consent Form",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "sendTo": "={{$json.candidate_email}}",
        "subject": "Your Video Interview \u2014 Required Consent (Illinois AI Video Interview Act)",
        "message": "=Dear {{$json.candidate_name}},\n\nPursuant to the Illinois AI Video Interview Act (820 ILCS 42), we are required to obtain your written consent before conducting an AI-analyzed video interview.\n\nDisclosure:\n- AI analysis will assess your qualifications based on your responses, facial expressions, and tone\n- Your video recording will be shared only with those evaluating your qualifications\n- Your recording and analysis data will be permanently deleted within 30 days of a final hiring decision\n- You may withdraw consent at any time by replying to this email\n\nTo consent, please click: {{$json.consent_url}}\n\nJob: {{$json.job_title}} | Reference: {{$json.candidate_id}}"
      },
      "position": [
        850,
        200
      ]
    },
    {
      "id": "b5",
      "name": "Log Consent Record",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "spreadsheetId": "={{$vars.IL_COMPLIANCE_SHEET_ID}}",
        "range": "ConsentLog!A:H",
        "data": {
          "mappingMode": "defineBelow",
          "values": [
            {
              "name": "candidate_id",
              "value": "={{$json.candidate_id}}"
            },
            {
              "name": "candidate_email",
              "value": "={{$json.candidate_email}}"
            },
            {
              "name": "consent_sent_date",
              "value": "={{new Date().toISOString()}}"
            },
            {
              "name": "deletion_due",
              "value": "={{$json.deletion_due}}"
            },
            {
              "name": "interview_id",
              "value": "={{$json.interview_id}}"
            },
            {
              "name": "status",
              "value": "CONSENT_PENDING"
            }
          ]
        }
      },
      "position": [
        850,
        400
      ]
    },
    {
      "id": "b6",
      "name": "Daily Deletion Check 7AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * *"
            }
          ]
        }
      },
      "position": [
        250,
        600
      ]
    },
    {
      "id": "b7",
      "name": "Load Pending Deletions",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "spreadsheetId": "={{$vars.IL_COMPLIANCE_SHEET_ID}}",
        "range": "ConsentLog!A:H"
      },
      "position": [
        450,
        600
      ]
    },
    {
      "id": "b8",
      "name": "Flag Due for Deletion",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const today=new Date();return $input.all().filter(i=>i.json.status!=='DELETED'&&i.json.final_decision_date).map(i=>{const due=new Date(i.json.deletion_due);const overdue=today>due;return{json:{...i.json,deletion_overdue:overdue,days_overdue:overdue?Math.floor((today-due)/86400000):0}};}).filter(i=>i.json.deletion_overdue);"
      },
      "position": [
        650,
        600
      ]
    },
    {
      "id": "b9",
      "name": "HTTP DELETE Recording",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "DELETE",
        "url": "={{$vars.ATS_API_URL}}/recordings/={{$json.interview_id}}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "position": [
        850,
        600
      ]
    },
    {
      "id": "b10",
      "name": "Slack #legal-privacy",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#legal-privacy",
        "text": "=\u26a0\ufe0f IL AIVIA: Recording deleted for candidate {{$json.candidate_id}} ({{$json.candidate_email}}). Final decision: {{$json.final_decision_date}}. Deleted: {{new Date().toISOString()}}. 820 ILCS 42 compliant."
      },
      "position": [
        1050,
        600
      ]
    }
  ],
  "connections": {
    "Candidate Interview Webhook": {
      "main": [
        [
          {
            "node": "Check IL Resident + Video",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check IL Resident + Video": {
      "main": [
        [
          {
            "node": "IF IL Compliance Required",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF IL Compliance Required": {
      "main": [
        [
          {
            "node": "Email Consent Form",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Consent Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Daily Deletion Check 7AM": {
      "main": [
        [
          {
            "node": "Load Pending Deletions",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Pending Deletions": {
      "main": [
        [
          {
            "node": "Flag Due for Deletion",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Flag Due for Deletion": {
      "main": [
        [
          {
            "node": "HTTP DELETE Recording",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP DELETE Recording": {
      "main": [
        [
          {
            "node": "Slack #legal-privacy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: EEOC Adverse Impact Ratio Monitor & OFCCP AAP Trigger

Who needs it: ENTERPRISE_ATS_SAAS, BACKGROUND_CHECK_SAAS, SKILLS_ASSESSMENT_SAAS, COMPENSATION_ANALYTICS_SAAS

The EEOC Uniform Guidelines on Employee Selection Procedures (29 CFR Part 1607) require employers to test selection procedures—including AI screening tools—for adverse impact using the 4/5ths (80%) rule: if a protected group's selection rate is less than 80% of the majority group's rate at any hiring stage, that is evidence of adverse impact.

For HRTech vendors, this translates to two obligations: (1) your platform should surface adverse impact data to customers before EEOC does, and (2) federal contractors using your ATS must update their OFCCP Affirmative Action Plans (41 CFR Part 60-2) when adverse impact is detected.

This workflow runs weekly on Mondays, loads applicant flow data by stage and protected group, calculates the 4/5ths ratio across all stages and groups, and flags any ratio below 0.80. Findings trigger immediate Slack alerts and email to CPO and Legal with FLSA §211(c) record retention reminder (3 years).

{
  "name": "EEOC Adverse Impact Ratio Monitor & OFCCP AAP Trigger",
  "nodes": [
    {
      "id": "c1",
      "name": "Weekly Monday 9AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 9 * * 1"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "c2",
      "name": "Load Applicant Flow Data",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "spreadsheetId": "={{$vars.EEOC_DATA_SHEET_ID}}",
        "range": "ApplicantFlow!A:N"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "c3",
      "name": "Calculate 4/5ths Rule",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const items=$input.all();const out=[];for(const item of items){const d=item.json;const stages=['applications','phone_screens','interviews','offers','hires'];const protected_groups=['female','black','hispanic','asian','native_american','age_40_plus','disabled'];for(const stage of stages){const majority_rate=d[`${stage}_majority_count`]>0?d[`${stage}_majority_selected`]/d[`${stage}_majority_count`]:null;if(!majority_rate)continue;for(const group of protected_groups){const group_count=d[`${stage}_${group}_count`];const group_selected=d[`${stage}_${group}_selected`];if(!group_count)continue;const group_rate=group_selected/group_count;const ratio=group_rate/majority_rate;const adverse_impact=ratio<0.8;if(adverse_impact){out.push({json:{...d,stage,protected_group:group,majority_rate:majority_rate.toFixed(4),group_rate:group_rate.toFixed(4),adverse_impact_ratio:ratio.toFixed(4),threshold:0.8,regulation:'EEOC_4_5THS_RULE',statute:'29 CFR Part 1607 UGESP',ofccp_aap_required:d.federal_contractor===true,flsa_retention:'3 years required per FLSA \u00a7211(c)'}});}}}return out;"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "c4",
      "name": "IF Adverse Impact Found",
      "type": "n8n-nodes-base.if",
      "parameters": {
        "conditions": {
          "conditions": [
            {
              "leftValue": "={{$json.adverse_impact_ratio}}",
              "rightValue": 0.8,
              "operator": {
                "type": "number",
                "operation": "smaller"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "c5",
      "name": "Slack #legal-hr-urgent",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#legal-hr-urgent",
        "text": "=\ud83d\udea8 EEOC ADVERSE IMPACT: {{$json.protected_group}} at {{$json.stage}} stage. Ratio: {{$json.adverse_impact_ratio}} (threshold: 0.80). Employer: {{$json.employer_name}}. 29 CFR \u00a71607 UGESP violation risk. OFCCP AAP review required if federal contractor."
      },
      "position": [
        1050,
        200
      ]
    },
    {
      "id": "c6",
      "name": "Email Chief People Officer",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "sendTo": "={{$json.cpo_email}},={{$json.legal_email}}",
        "subject": "=[EEOC] Adverse Impact Detected \u2014 {{$json.stage}} \u2014 {{$json.protected_group}} \u2014 Immediate Review Required",
        "message": "=EEOC Adverse Impact Analysis\n\nEmployer: {{$json.employer_name}} | Period: {{$json.analysis_period}}\nStage: {{$json.stage}}\nProtected Group: {{$json.protected_group}}\nMajority Selection Rate: {{$json.majority_rate}}\nGroup Selection Rate: {{$json.group_rate}}\nAdverse Impact Ratio: {{$json.adverse_impact_ratio}} (< 0.80 threshold = VIOLATION RISK)\n\nRegulation: EEOC Uniform Guidelines on Employee Selection Procedures (29 CFR Part 1607)\nOFCCP: {{$json.ofccp_aap_required?'AAP update required per 41 CFR Part 60-2':'Not a federal contractor'}}\nRecord Retention: FLSA \u00a7211(c) requires 3 years retention of all applicant flow data\n\nImmediate Actions:\n1. Conduct business necessity review for the selection procedure at this stage\n2. Document job-relatedness validation study\n3. If OFCCP contractor: update AAP to reflect finding and corrective action plan\n4. Consult employment counsel before any AEDT adjustments"
      },
      "position": [
        1050,
        400
      ]
    },
    {
      "id": "c7",
      "name": "Log Adverse Impact Finding",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "spreadsheetId": "={{$vars.EEOC_DATA_SHEET_ID}}",
        "range": "AdverseImpactLog!A:J",
        "data": {
          "mappingMode": "defineBelow",
          "values": [
            {
              "name": "detected_date",
              "value": "={{new Date().toISOString()}}"
            },
            {
              "name": "employer_id",
              "value": "={{$json.employer_id}}"
            },
            {
              "name": "stage",
              "value": "={{$json.stage}}"
            },
            {
              "name": "protected_group",
              "value": "={{$json.protected_group}}"
            },
            {
              "name": "adverse_impact_ratio",
              "value": "={{$json.adverse_impact_ratio}}"
            },
            {
              "name": "alert_sent",
              "value": "=true"
            }
          ]
        }
      },
      "position": [
        1050,
        600
      ]
    }
  ],
  "connections": {
    "Weekly Monday 9AM": {
      "main": [
        [
          {
            "node": "Load Applicant Flow Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Applicant Flow Data": {
      "main": [
        [
          {
            "node": "Calculate 4/5ths Rule",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate 4/5ths Rule": {
      "main": [
        [
          {
            "node": "IF Adverse Impact Found",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Adverse Impact Found": {
      "main": [
        [
          {
            "node": "Slack #legal-hr-urgent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Email Chief People Officer",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Chief People Officer": {
      "main": [
        [
          {
            "node": "Log Adverse Impact Finding",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: GDPR Art.22 Right-to-Explanation Pipeline

Who needs it: ENTERPRISE_ATS_SAAS, AI_VIDEO_INTERVIEW_SAAS, SMB_HIRING_PLATFORM, HRTECH_AI_STARTUP (EU candidates)

GDPR Article 22 gives EU candidates the right not to be subject to solely automated decisions with significant effects—including hiring decisions. When a candidate exercises this right, controllers must provide: (1) meaningful information about the logic involved, (2) the significance and envisaged consequences, and (3) the right to obtain human review and contest the decision.

This webhook-triggered workflow handles the full explanation pipeline. When a candidate submits an explanation request, it validates the request, calls your ATS API to retrieve the AI decision record and feature importance scores, generates a plain-language explanation, emails the candidate, logs the request to a GDPR compliance sheet, and alerts #data-privacy in Slack.

FLSA §211(c) cross-requirement: All hiring records—including AI decision explanations—must be retained for 3 years under FLSA. This workflow logs every explanation to Google Sheets for your retention audit trail.

{
  "name": "GDPR Art.22 Right-to-Explanation Pipeline",
  "nodes": [
    {
      "id": "d1",
      "name": "Explanation Request Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "httpMethod": "POST",
        "path": "gdpr-art22-request"
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "d2",
      "name": "Validate & Parse Request",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const d=$input.first().json;if(!d.candidate_id||!d.decision_id)throw new Error('Missing candidate_id or decision_id');const deadline=new Date(Date.now()+30*86400000).toISOString().split('T')[0];return[{json:{...d,gdpr_article:'Art.22 Right to Explanation for Automated Decisions',deadline_30d:deadline,response_required:'Written explanation of logic, significance, and envisaged consequences of automated decision'}}];"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "d3",
      "name": "Fetch AI Decision Record",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "method": "GET",
        "url": "={{$vars.ATS_API_URL}}/decisions/={{$json.decision_id}}",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "d4",
      "name": "Generate Plain-Language Explanation",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const req=$input.first().json;const decision=req.decision_data||{};const top_factors=(decision.feature_importance||[]).slice(0,5).map((f,i)=>`${i+1}. ${f.feature_name}: ${f.direction==='positive'?'positively':'negatively'} influenced your score (weight: ${(f.weight*100).toFixed(1)}%)`).join('\\n');const outcome=decision.outcome==='ADVANCE'?'advance to next round':'not advance at this time';return[{json:{...req,outcome,top_factors,model_name:decision.model_name||'AI Screening Model',decision_date:decision.decision_date,explanation_text:`Our automated screening system reviewed your application on ${decision.decision_date}. The system decided to ${outcome} based on the following factors:\\n\\n${top_factors}\\n\\nThis decision was made automatically. You have the right to request human review, express your point of view, and contest this decision.`}}];"
      },
      "position": [
        850,
        300
      ]
    },
    {
      "id": "d5",
      "name": "Email Explanation to Candidate",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "sendTo": "={{$json.candidate_email}}",
        "subject": "Your Right to Explanation \u2014 Automated Hiring Decision (GDPR Art.22)",
        "message": "=Dear {{$json.candidate_name}},\n\nYou requested an explanation of the automated decision made about your application. We provide this pursuant to GDPR Article 22.\n\n{{$json.explanation_text}}\n\nYour Rights Under GDPR Art.22:\n- Request human review of this decision\n- Express your point of view to our recruitment team\n- Contest the decision if you believe it was made in error\n\nTo exercise these rights, reply to this email or contact: {{$vars.DPO_EMAIL}}\n\nData Controller: {{$vars.COMPANY_NAME}} | DPO: {{$vars.DPO_EMAIL}}\nRetention: Your application data is retained for {{$vars.RETENTION_PERIOD}} per our privacy policy."
      },
      "position": [
        1050,
        200
      ]
    },
    {
      "id": "d6",
      "name": "Log Explanation Request",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "spreadsheetId": "={{$vars.GDPR_LOG_SHEET_ID}}",
        "range": "Art22Log!A:H",
        "data": {
          "mappingMode": "defineBelow",
          "values": [
            {
              "name": "request_date",
              "value": "={{new Date().toISOString()}}"
            },
            {
              "name": "candidate_id",
              "value": "={{$json.candidate_id}}"
            },
            {
              "name": "decision_id",
              "value": "={{$json.decision_id}}"
            },
            {
              "name": "response_deadline",
              "value": "={{$json.deadline_30d}}"
            },
            {
              "name": "explanation_sent",
              "value": "=true"
            },
            {
              "name": "regulation",
              "value": "GDPR Art.22 + FLSA \u00a7211(c) 3yr retention"
            }
          ]
        }
      },
      "position": [
        1050,
        400
      ]
    },
    {
      "id": "d7",
      "name": "Slack #data-privacy",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#data-privacy",
        "text": "=GDPR Art.22 request processed for candidate {{$json.candidate_id}}. Decision {{$json.decision_id}}. Explanation sent. 30-day response deadline: {{$json.deadline_30d}}."
      },
      "position": [
        1050,
        600
      ]
    }
  ],
  "connections": {
    "Explanation Request Webhook": {
      "main": [
        [
          {
            "node": "Validate & Parse Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Validate & Parse Request": {
      "main": [
        [
          {
            "node": "Fetch AI Decision Record",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch AI Decision Record": {
      "main": [
        [
          {
            "node": "Generate Plain-Language Explanation",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Generate Plain-Language Explanation": {
      "main": [
        [
          {
            "node": "Email Explanation to Candidate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Email Explanation to Candidate": {
      "main": [
        [
          {
            "node": "Log Explanation Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Explanation Request": {
      "main": [
        [
          {
            "node": "Slack #data-privacy",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Weekly HRTech AI Compliance KPI Dashboard

Who needs it: All 7 tiers — ENTERPRISE_ATS_SAAS, SMB_HIRING_PLATFORM, AI_VIDEO_INTERVIEW_SAAS, SKILLS_ASSESSMENT_SAAS, BACKGROUND_CHECK_SAAS, COMPENSATION_ANALYTICS_SAAS, HRTECH_AI_STARTUP

This Monday morning dashboard gives CPO, Legal, and CEO a single-view RAG (Red/Amber/Green) status across all HRTech AI compliance obligations: NYC LL144 audit currency, IL AIVIA consent and deletion rates, EEOC adverse impact clean stages, OFCCP AAP currency, and GDPR Art.22 response rate.

The n8n Code node calculates an overall compliance score (0-100), applies RAG thresholds per regulation, builds an HTML report table, emails it to leadership, and posts a one-liner summary to Slack #compliance. Red items require immediate action; Amber items require 48-hour plans.

{
  "name": "Weekly HRTech AI Compliance KPI Dashboard",
  "nodes": [
    {
      "id": "e1",
      "name": "Monday 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      },
      "position": [
        250,
        300
      ]
    },
    {
      "id": "e2",
      "name": "Load Compliance Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "spreadsheetId": "={{$vars.COMPLIANCE_METRICS_SHEET_ID}}",
        "range": "WeeklyMetrics!A:P"
      },
      "position": [
        450,
        300
      ]
    },
    {
      "id": "e3",
      "name": "Score & RAG Rate",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "const d=$input.first().json;const rag=(val,green,amber)=>val>=green?'\ud83d\udfe2 GREEN':val>=amber?'\ud83d\udfe1 AMBER':'\ud83d\udd34 RED';const metrics={nyc_ll144_audits_current:rag(d.nyc_ll144_audit_pct,100,80),il_aivia_consent_rate:rag(d.il_consent_rate_pct,100,95),il_aivia_deletion_on_time:rag(d.il_deletion_ontime_pct,100,90),eeoc_adverse_impact_clean:rag(d.eeoc_clean_stages_pct,100,80),ofccp_aap_current:rag(d.ofccp_aap_current_pct,100,100),gdpr_art22_response_rate:rag(d.gdpr_art22_response_pct,100,90),overall_compliance_score:Math.round((d.nyc_ll144_audit_pct+d.il_consent_rate_pct+d.il_deletion_ontime_pct+d.eeoc_clean_stages_pct+d.gdpr_art22_response_pct)/5)};const html=`<html><body style='font-family:sans-serif'><h2>HRTech AI Compliance Dashboard \u2014 Week of ${d.week_of}</h2><table border='1' cellpadding='8' style='border-collapse:collapse'><tr><th>Regulation</th><th>Metric</th><th>Value</th><th>Status</th></tr><tr><td>NYC LL144 \u00a75-336</td><td>Annual Bias Audits Current</td><td>${d.nyc_ll144_audit_pct}%</td><td>${metrics.nyc_ll144_audits_current}</td></tr><tr><td>IL AIVIA 820 ILCS 42</td><td>Consent Rate</td><td>${d.il_consent_rate_pct}%</td><td>${metrics.il_aivia_consent_rate}</td></tr><tr><td>IL AIVIA 820 ILCS 42</td><td>30-Day Deletions On Time</td><td>${d.il_deletion_ontime_pct}%</td><td>${metrics.il_aivia_deletion_on_time}</td></tr><tr><td>EEOC 29 CFR \u00a71607</td><td>Stages w/o Adverse Impact</td><td>${d.eeoc_clean_stages_pct}%</td><td>${metrics.eeoc_adverse_impact_clean}</td></tr><tr><td>OFCCP 41 CFR \u00a760-2</td><td>AAP Current</td><td>${d.ofccp_aap_current_pct}%</td><td>${metrics.ofccp_aap_current}</td></tr><tr><td>GDPR Art.22</td><td>Explanation Response Rate</td><td>${d.gdpr_art22_response_pct}%</td><td>${metrics.gdpr_art22_response_rate}</td></tr></table><p><strong>Overall Score: ${metrics.overall_compliance_score}/100</strong></p></body></html>`;return[{json:{...d,...metrics,html_report:html,week_of:d.week_of}}];"
      },
      "position": [
        650,
        300
      ]
    },
    {
      "id": "e4",
      "name": "Gmail Dashboard to CPO+Legal",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "sendTo": "={{$vars.CPO_EMAIL}},={{$vars.LEGAL_EMAIL}},={{$vars.CEO_EMAIL}}",
        "subject": "=[HRTech AI Compliance] Weekly Dashboard \u2014 {{$json.week_of}} \u2014 Score: {{$json.overall_compliance_score}}/100",
        "message": "={{$json.html_report}}",
        "options": {
          "bodyContentType": "html"
        }
      },
      "position": [
        850,
        200
      ]
    },
    {
      "id": "e5",
      "name": "Slack #compliance one-liner",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance",
        "text": "=HRTech AI Compliance Week {{$json.week_of}}: Score {{$json.overall_compliance_score}}/100 | NYC LL144 {{$json.nyc_ll144_audits_current}} | IL AIVIA Consent {{$json.il_aivia_consent_rate}} | EEOC {{$json.eeoc_adverse_impact_clean}} | GDPR Art.22 {{$json.gdpr_art22_response_rate}}"
      },
      "position": [
        850,
        400
      ]
    }
  ],
  "connections": {
    "Monday 8AM": {
      "main": [
        [
          {
            "node": "Load Compliance Metrics",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Compliance Metrics": {
      "main": [
        [
          {
            "node": "Score & RAG Rate",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Score & RAG Rate": {
      "main": [
        [
          {
            "node": "Gmail Dashboard to CPO+Legal",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack #compliance one-liner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Implementation Notes

Environment variables to set in n8n:

  • AUDIT_SHEET_ID — Google Sheets ID for NYC LL144 audit schedule
  • IL_COMPLIANCE_SHEET_ID — Sheets ID for AIVIA consent/deletion log
  • EEOC_DATA_SHEET_ID — Sheets ID for applicant flow data
  • GDPR_LOG_SHEET_ID — Sheets ID for GDPR Art.22 requests
  • COMPLIANCE_METRICS_SHEET_ID — Sheets ID for weekly KPI metrics
  • ATS_API_URL — Your ATS API base URL
  • DPO_EMAIL, CPO_EMAIL, LEGAL_EMAIL, CEO_EMAIL — Stakeholder routing

FLSA §211(c) record retention: All five workflows log to Google Sheets. Export to your long-term storage (S3, GCS, Postgres) before 3-year retention window closes.

MD HB 1202 / Cal. FEHA DFEH: Maryland and California have similar (but not identical) bias audit and disclosure requirements for employers using AI hiring tools. Adapt the urgency thresholds in Workflow 1 to include MD/CA-specific deadlines from your customer's address data.

n8n credential setup:

  • Google Sheets: use a service account with Sheets API enabled
  • Gmail: Gmail OAuth2 credential in n8n
  • Slack: Slack Bot Token with chat:write scope
  • HTTP Request nodes: add your ATS API auth header in the credential manager

Get the Full FlowKit n8n Template Pack

These workflows are part of the FlowKit n8n Automation Templates collection — 15 production-ready n8n workflows for HRTech, SaaS ops, AI compliance, and business automation.

Browse FlowKit Templates on Gumroad →

Each template includes the full workflow JSON, a setup guide, and example environment variable configs.


Tags: n8n, hrtech, automation, ai-compliance

Top comments (0)