DEV Community

Alex Kane
Alex Kane

Posted on

n8n for FoodTech & Food Safety SaaS Vendors: 5 Automations for FDA FSMA, HACCP, EU FIC 1169, and USDA FSIS Compliance

If you're building SaaS for food safety officers, HACCP coordinators, quality assurance managers, or food traceability teams — your customers operate inside a regulatory stack where the fastest clock has no grace period: HACCP critical limit deviation requires immediate corrective action and product hold under 21 CFR §120.12, before any affected product can ship.

HACCP critical limit deviation: immediate action, no exceptions. FDA FSMA §204 traceability request: records available within 24 hours. FDA Class I recall notification: 24 hours. EU RASFF rapid alert: 24 hours. These are not month-end reporting cycles — they are real-time operational clocks that require your automation layer to respond faster than any human approval chain.

The compliance stack for food safety SaaS vendors spans FDA FSMA preventive controls (21 CFR §117), FSMA food traceability rule (§204), HACCP plans (21 CFR §120 for juice, USDA FSIS 9 CFR §417 for meat/poultry), EU Food Information to Consumers Regulation (FIC 1169/2011) allergen labeling, and FSMA Foreign Supplier Verification Program (21 CFR §1.502). Each of these has a distinct record-keeping, incident response, and verification cadence that your platform must orchestrate.

Here are 5 n8n automation workflows for FoodTech and food safety SaaS vendors, covering FDA FSMA, HACCP, EU FIC 1169, USDA FSIS, and FSVP compliance.

Why food safety data belongs inside your network

Food safety SaaS handles some of the most legally sensitive operational data in the food industry:

FDA FSMA §204 traceability records — foods on the FDA Traceability List must be available to FDA within 24 hours of a request (§204(d)). If your traceability records are stored in a cloud SaaS, the cloud vendor becomes a records custodian that FDA may subpoena independently. Keeping records in self-hosted Postgres under your legal custody simplifies the FDA access pathway.

HACCP CCP monitoring records (21 CFR §120.8, 9 CFR §417.5) — USDA FSIS inspectors have real-time on-site access during inspections. Cloud-routed HACCP monitoring records risk inspector access delays and create an additional entity in your audit trail. FSIS expects records available on demand at the establishment.

EU FIC 1169/2011 allergen composition data — allergen declarations are product liability. A third-party SaaS breach that exposes or corrupts allergen composition data creates recall exposure and potential criminal liability under EU food law for undeclared allergens. Self-hosted Postgres keeps allergen master data under your IFQ control.

USDA FSIS 9 CFR §417.5(d) HACCP record retention — records must be maintained on-site in the establishment or at an accessible location for 1-2 years. Cloud storage may complicate 'accessible location' interpretation during FSIS enforcement.

SOC 2 CC9.2 — each cloud automation vendor you route food safety data through is a vendor in scope for your next SOC 2 audit. Self-hosted n8n eliminates the iPaaS subprocessor from your vendor risk management scope.

Workflow 1: FoodSafety Customer Onboarding Drip

Tier-segmented onboarding sequence for new food safety SaaS customers. Injects regulatory-specific content based on each customer's compliance profile: ENTERPRISE_FOOD_SAFETY_PLATFORM, HACCP_MANAGEMENT_SAAS_VENDOR, FOOD_TRACEABILITY_SAAS_VENDOR, ALLERGEN_MANAGEMENT_SAAS_VENDOR, FOOD_INSPECTION_TECH_VENDOR, FOOD_TESTING_LABORATORY_SAAS, EMERGING_FOOD_SAFETY_STARTUP.

Seven compliance flags: FDA_FSMA_PREVENTIVE_CONTROLS_REQUIRED, FDA_FSMA_TRACEABILITY_RULE_APPLICABLE, HACCP_PLAN_REQUIRED, EU_FIC_1169_ALLERGEN_APPLICABLE, USDA_FSIS_REGULATED, FDA_FSVP_FOREIGN_SUPPLIER_VERIFICATION, SOC2_REQUIRED.

Day 0 email injects the applicable penalty clock (HACCP critical limit deviation = immediate, FDA §204 traceability request = 24h, EU RASFF = 24h) so the customer's food safety officer knows your platform understands their regulatory obligations from the first touchpoint.

Day 3 focuses on HACCP critical limit monitoring pipeline configuration — the most time-sensitive workflow. Day 7 runs the FDA §204 traceability readiness drill to confirm 24-hour response capability.

{
  "name": "FoodSafety SaaS Customer Onboarding Drip",
  "nodes": [
    {
      "id": "trigger-1",
      "name": "New Customer Trigger",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "parameters": {
        "documentId": "your-crm-sheet-id",
        "sheetName": "NewCustomers",
        "event": "rowAdded"
      }
    },
    {
      "id": "flags-1",
      "name": "Inject Compliance Flags",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst d = $input.first().json;\nconst tier = d.customer_tier || 'EMERGING_FOOD_SAFETY_STARTUP';\nconst flags = (d.compliance_flags || '').split(',').map(f => f.trim()).filter(Boolean);\nconst clockMap = {\n  FDA_FSMA_PREVENTIVE_CONTROLS_REQUIRED: 'FDA FSMA 21 CFR \u00a7117 \u2014 preventive controls reanalysis at least every 3 years; any process change triggers immediate reanalysis',\n  FDA_FSMA_TRACEABILITY_RULE_APPLICABLE: 'FDA FSMA \u00a7204 \u2014 traceability records for Foods on the FTL must be available to FDA within 24 hours of request',\n  HACCP_PLAN_REQUIRED: 'HACCP 21 CFR \u00a7120.12 / USDA FSIS 9 CFR \u00a7417.3 \u2014 CRITICAL LIMIT DEVIATION requires immediate corrective action and product hold; this is the fastest clock in food safety compliance',\n  EU_FIC_1169_ALLERGEN_APPLICABLE: 'EU FIC 1169/2011 Art.21 \u2014 14 regulated allergens must be declared; undeclared allergen = unsafe food under Art.14, mandatory recall',\n  USDA_FSIS_REGULATED: 'USDA FSIS 9 CFR \u00a7417 \u2014 HACCP plan + Sanitation SOPs required; noncompliance record triggers corrective action or suspension',\n  FDA_FSVP_FOREIGN_SUPPLIER_VERIFICATION: 'FDA FSMA FSVP 21 CFR \u00a71.502 \u2014 importers must verify foreign suppliers annually; supplier failure requires quarantine or disposal of affected product',\n  SOC2_REQUIRED: 'SOC 2 Type II \u2014 annual audit cycle (customer contract requirement; CC9.2 vendor risk management)'\n};\nconst clocks = flags.map(f => clockMap[f] || f).join('\\n');\nreturn [{json: {tier, flags, clocks, customer_email: d.customer_email, company_name: d.company_name, ts: new Date().toISOString()}}];\n"
      }
    },
    {
      "id": "day0-1",
      "name": "Day 0 \u2014 Compliance Clocks Email",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{$json.customer_email}}",
        "subject": "Your FSMA/HACCP compliance clocks \u2014 day 0 activation",
        "message": "Welcome to {{$json.company_name}}. Your applicable compliance clocks:\\n\\n{{$json.clocks}}\\n\\nThe fastest clock in your stack: HACCP critical limit deviation requires immediate corrective action and product hold under 21 CFR \u00a7120.12. Your n8n incident pipeline is configured for this."
      }
    },
    {
      "id": "slack-1",
      "name": "Slack CSM Notification",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#cs-new-accounts",
        "text": "New {{$json.tier}} customer: {{$json.company_name}} | Flags: {{$json.flags.join(', ')}}"
      }
    },
    {
      "id": "wait3-1",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "day3-1",
      "name": "Day 3 \u2014 HACCP Integration Guide",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{$json.customer_email}}",
        "subject": "Day 3: Setting up your HACCP critical limit monitor",
        "message": "Day 3 check-in. The HACCP critical limit deviation pipeline is the most time-sensitive workflow in your instance \u2014 21 CFR \u00a7120.12 requires immediate corrective action when a CCP critical limit is not met, before any affected product ships. Here is how to connect your HACCP system to the incident webhook and configure the product hold notification path."
      }
    },
    {
      "id": "wait4-1",
      "name": "Wait 4 Days",
      "type": "n8n-nodes-base.wait",
      "parameters": {
        "amount": 4,
        "unit": "days"
      }
    },
    {
      "id": "day7-1",
      "name": "Day 7 \u2014 FDA Traceability Readiness",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{$json.customer_email}}",
        "subject": "Day 7: FDA \u00a7204 traceability \u2014 24-hour readiness test",
        "message": "Day 7: FDA FSMA \u00a7204 requires traceability records for Foods on the Traceability List to be available to FDA within 24 hours of a request. Run the traceability export drill now to confirm your response time. Your n8n instance logs every \u00a7204 request event with a timestamp so you can demonstrate readiness in any FDA audit."
      }
    }
  ],
  "connections": {
    "New Customer Trigger": {
      "main": [
        [
          {
            "node": "Inject Compliance Flags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Inject Compliance Flags": {
      "main": [
        [
          {
            "node": "Day 0 \u2014 Compliance Clocks Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 0 \u2014 Compliance Clocks Email": {
      "main": [
        [
          {
            "node": "Slack CSM Notification",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack CSM Notification": {
      "main": [
        [
          {
            "node": "Wait 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Days": {
      "main": [
        [
          {
            "node": "Day 3 \u2014 HACCP Integration Guide",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 3 \u2014 HACCP Integration Guide": {
      "main": [
        [
          {
            "node": "Wait 4 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 Days": {
      "main": [
        [
          {
            "node": "Day 7 \u2014 FDA Traceability Readiness",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 2: FDA FSMA & USDA FSIS API Health Monitor

Polls 5 critical food safety API endpoints every 3 minutes. Uses $getWorkflowStaticData to track UP/DOWN state transitions — only fires a Slack alert when an endpoint transitions from UP to DOWN, suppressing noise on sustained outages.

Five endpoints with regulatory context: fda_fsma_api (21 CFR §117 preventive controls — outage delays corrective action documentation), usda_fsis_api (9 CFR §417 HACCP — silent failure means CCP deviation events go unlogged; FSIS inspector access requires real-time record availability), eu_fic_allergen_api (EU 1169/2011 Art.21 — 14 allergens; API downtime risks undeclared allergen in production batch), haccp_records_api (21 CFR §120.8 — monitoring records must be maintained; downtime during production = gap in required CCP log), traceability_api (FSMA §204 — records for FTL foods available within 24h of FDA request; outage during investigation window = non-compliance).

{
  "name": "FDA FSMA & USDA FSIS API Health Monitor",
  "nodes": [
    {
      "id": "cron-2",
      "name": "Every 3 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/3 * * * *"
            }
          ]
        }
      }
    },
    {
      "id": "endpoints-2",
      "name": "Define Endpoints",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nreturn [\n  {json: {name: 'fda_fsma_api', url: 'https://your-fsma-compliance-endpoint/health', reg: 'FDA FSMA 21 CFR \u00a7117 preventive controls \u2014 outage delays corrective action documentation and reanalysis triggers'}},\n  {json: {name: 'usda_fsis_api', url: 'https://your-usda-fsis-endpoint/health', reg: 'USDA FSIS 9 CFR \u00a7417 HACCP \u2014 silent failure means CCP deviation events go unlogged; FSIS inspector access requires real-time record availability'}},\n  {json: {name: 'eu_fic_allergen_api', url: 'https://your-allergen-management-endpoint/health', reg: 'EU FIC 1169/2011 Art.21 \u2014 14 allergens declared on label; API downtime risks undeclared allergen in production batch = immediate recall exposure'}},\n  {json: {name: 'haccp_records_api', url: 'https://your-haccp-records-endpoint/health', reg: 'HACCP 21 CFR \u00a7120.8 \u2014 monitoring records must be maintained; downtime during production = gap in required CCP log'}},\n  {json: {name: 'traceability_api', url: 'https://your-traceability-endpoint/health', reg: 'FDA FSMA \u00a7204 \u2014 traceability records for FTL foods available within 24h of FDA request; API outage during investigation window = non-compliance'}}\n];\n"
      }
    },
    {
      "id": "http-2",
      "name": "HTTP Health Check",
      "type": "n8n-nodes-base.httpRequest",
      "parameters": {
        "url": "={{$json.url}}",
        "method": "GET",
        "timeout": 8000,
        "continueOnFail": true
      }
    },
    {
      "id": "state-2",
      "name": "State Tracker",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst store = $getWorkflowStaticData('global');\nif (!store.endpointState) store.endpointState = {};\nconst name = $('Define Endpoints').first().json.name;\nconst reg = $('Define Endpoints').first().json.reg;\nconst wasUp = store.endpointState[name] !== 'DOWN';\nconst isUp = $input.first().json.statusCode === 200;\nstore.endpointState[name] = isUp ? 'UP' : 'DOWN';\nconst justWentDown = !isUp && wasUp;\nreturn [{json: {name, reg, status: isUp ? 'UP' : 'DOWN', justWentDown, ts: new Date().toISOString()}}];\n"
      }
    },
    {
      "id": "filter-2",
      "name": "Filter New Outages",
      "type": "n8n-nodes-base.filter",
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{$json.justWentDown}}",
              "value2": true
            }
          ]
        }
      }
    },
    {
      "id": "slack-2",
      "name": "Slack #compliance-ops",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-ops",
        "text": "FOOD SAFETY API DOWN: {{$json.name}} \u2014 {{$json.reg}} | {{$json.ts}}"
      }
    },
    {
      "id": "sheets-2",
      "name": "Log SLA Event",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "append",
        "documentId": "your-sla-sheet-id",
        "sheetName": "SLAEvents",
        "fieldsUi": {
          "values": [
            {
              "fieldId": "A",
              "fieldValue": "={{$json.ts}}"
            },
            {
              "fieldId": "B",
              "fieldValue": "={{$json.name}}"
            },
            {
              "fieldId": "C",
              "fieldValue": "DOWN"
            },
            {
              "fieldId": "D",
              "fieldValue": "={{$json.reg}}"
            }
          ]
        }
      }
    }
  ],
  "connections": {
    "Every 3 Minutes": {
      "main": [
        [
          {
            "node": "Define Endpoints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Define Endpoints": {
      "main": [
        [
          {
            "node": "HTTP Health Check",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Health Check": {
      "main": [
        [
          {
            "node": "State Tracker",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "State Tracker": {
      "main": [
        [
          {
            "node": "Filter New Outages",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter New Outages": {
      "main": [
        [
          {
            "node": "Slack #compliance-ops",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log SLA Event",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3: FSMA / HACCP / EU FIC Compliance Deadline Tracker

Runs weekday mornings against a Google Sheets deadline register. Routes by severity — OVERDUE, CRITICAL (≤7d), URGENT (≤21d), WARNING (≤60d), NOTICE (≤90d). Sends Slack alerts to #compliance-critical for CRITICAL/OVERDUE and email to deadline owners for all tiers. Deduplication prevents repeat alerts on the same open item.

Twelve deadline types: FDA_FSMA_PREVENTIVE_CONTROLS_ANNUAL_REANALYSIS, HACCP_QUARTERLY_VERIFICATION_ACTIVITIES, FDA_FSMA_TRACEABILITY_RECORDS_24H_READINESS, USDA_FSIS_HACCP_ANNUAL_REASSESSMENT, FDA_FSVP_ANNUAL_SUPPLIER_REVERIFICATION, EU_FIC_ALLERGEN_LABEL_ANNUAL_ACCURACY_AUDIT, FDA_ENVIRONMENTAL_MONITORING_QUARTERLY, USDA_FSIS_SANITATION_SOP_ANNUAL_REVIEW, FDA_IMPORT_ALERT_RESPONSE_WINDOW, FDA_FACILITY_REGISTRATION_BIENNIAL, SOC2_TYPE2_RENEWAL, ANNUAL_PENTEST.

{
  "name": "FSMA / HACCP / EU FIC Compliance Deadline Tracker",
  "nodes": [
    {
      "id": "cron-3",
      "name": "Weekday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      }
    },
    {
      "id": "sheets-3",
      "name": "Read Deadline Sheet",
      "type": "n8n-nodes-base.googleSheets",
      "parameters": {
        "operation": "getAll",
        "documentId": "your-sheet-id",
        "sheetName": "ComplianceDeadlines"
      }
    },
    {
      "id": "classify-3",
      "name": "Classify Severity",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst store = $getWorkflowStaticData('global');\nif (!store.alerted) store.alerted = {};\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.floor((due - today) / 86400000);\n  let severity = null;\n  if (daysLeft < 0) severity = 'OVERDUE';\n  else if (daysLeft <= 7) severity = 'CRITICAL';\n  else if (daysLeft <= 21) severity = 'URGENT';\n  else if (daysLeft <= 60) severity = 'WARNING';\n  else if (daysLeft <= 90) severity = 'NOTICE';\n  if (!severity) continue;\n  const key = d.deadline_id + '_' + d.due_date;\n  if (store.alerted[key]) continue;\n  store.alerted[key] = true;\n  results.push({json: {...d, severity, daysLeft}});\n}\nreturn results;\n"
      }
    },
    {
      "id": "switch-3",
      "name": "Route by Severity",
      "type": "n8n-nodes-base.switch",
      "parameters": {
        "dataType": "string",
        "value1": "={{$json.severity}}",
        "rules": {
          "rules": [
            {
              "value2": "OVERDUE"
            },
            {
              "value2": "CRITICAL"
            },
            {
              "value2": "URGENT"
            }
          ]
        }
      }
    },
    {
      "id": "slack-critical-3",
      "name": "Slack #compliance-critical",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-critical",
        "text": "={{$json.severity}}: {{$json.deadline_type}} \u2014 {{$json.daysLeft}} days remaining ({{$json.due_date}}) | Owner: {{$json.owner_email}}"
      }
    },
    {
      "id": "slack-warn-3",
      "name": "Slack #compliance-calendar",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#compliance-calendar",
        "text": "={{$json.severity}}: {{$json.deadline_type}} \u2014 {{$json.daysLeft}} days ({{$json.due_date}})"
      }
    },
    {
      "id": "email-3",
      "name": "Email Deadline Owner",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{$json.owner_email}}",
        "subject": "={{$json.severity}}: {{$json.deadline_type}} due {{$json.due_date}}",
        "message": "={{$json.deadline_type}} is {{$json.daysLeft}} days away. Regulatory reference: {{$json.reg_ref}}. Action required by: {{$json.due_date}}."
      }
    }
  ],
  "connections": {
    "Weekday 8 AM": {
      "main": [
        [
          {
            "node": "Read Deadline Sheet",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Deadline Sheet": {
      "main": [
        [
          {
            "node": "Classify Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Severity": {
      "main": [
        [
          {
            "node": "Route by Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Severity": {
      "main": [
        [
          {
            "node": "Slack #compliance-critical",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Deadline Owner",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack #compliance-critical",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Deadline Owner",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack #compliance-calendar",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Deadline Owner",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4: HACCP Deviation & FDA Recall Incident Pipeline

Webhook-triggered incident router. Classifies by incident type, maps the regulatory clock and escalation path, fires Slack and Gmail simultaneously, logs to Postgres food_safety_incidents table, responds 200 OK immediately (responseMode:onReceived to prevent timeout on slow email delivery).

Eight incident types with exact clocks:

HACCP_CRITICAL_LIMIT_DEVIATION — IMMEDIATE. 21 CFR §120.12 / USDA FSIS 9 CFR §417.3(a): correct the deviation before any affected product is distributed. Hold all product since last acceptable CCP monitoring. Document corrective action. This is the fastest mandatory clock in food safety compliance — no grace period, no business day buffer. This is the sharpest self-hosting argument: HACCP deviation response must be faster than any cloud queue latency.

FDA_FSMA_FOOD_RECALL_CLASS_I — 24 hours. FDA Class I recall notification within 24h of recall decision (21 CFR §7.59(c)). Serious adverse health consequences or death. Traceability records under FSMA §204 must be available to FDA within 24h of request to support scope determination. Public warning may be required.

FDA_FSMA_TRACEABILITY_REQUEST — 24 hours. FDA requests traceability key data elements (KDEs) for Foods on the Traceability List under §204(d). Records must be available within 24h: KDEs at growing/shipping/receiving/transformation/distribution. Failure to produce = civil penalty + potential product detention.

USDA_FSIS_NONCOMPLIANCE_RECORD — 72 hours. FSIS NR issued under 9 CFR §417.6. Corrective action response required before FSIS issues Notice of Suspension. Document in HACCP records.

EU_RASFF_RAPID_ALERT_NOTIFICATION — 24 hours. EU RASFF under Regulation 178/2002 Art.50. Serious risk food on EU market — immediate notification to national competent authority within 24h.

FDA_IMPORT_ALERT_DETENTION — IMMEDIATE. Product detained at US port under FDA import alert (21 USC §381(a)). Importer must respond with admissibility evidence. Traceability documentation, FSVP records, and lab results must be assembled immediately. No sales of detained lots.

ALLERGEN_UNDECLARED_CONTAMINATION — IMMEDIATE. Undeclared allergen confirmed or suspected. EU FIC 1169/2011 Art.14: unsafe food = mandatory withdrawal. FDA Class I recall likely. Issue product hold immediately. Pull allergen declaration from label database for audit.

FOREIGN_SUPPLIER_FSMA_VIOLATION — 48 hours. Foreign supplier fails FSVP verification (21 CFR §1.502). Must quarantine or dispose of affected product. Document verification failure. Enhanced supplier verification before resuming imports.

{
  "name": "HACCP Deviation & FDA Recall Incident Pipeline",
  "nodes": [
    {
      "id": "webhook-4",
      "name": "Incident Webhook",
      "type": "n8n-nodes-base.webhook",
      "parameters": {
        "path": "food-safety-incident",
        "httpMethod": "POST",
        "responseMode": "onReceived",
        "responseData": "allEntries"
      }
    },
    {
      "id": "router-4",
      "name": "Incident Router",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst INCIDENTS = {\n  \"HACCP_CRITICAL_LIMIT_DEVIATION\": {\n    \"clock\": \"IMMEDIATE\",\n    \"action\": \"21 CFR \\u00a7120.12 / USDA FSIS 9 CFR \\u00a7417.3(a): correct the deviation BEFORE any affected product can be distributed. Hold all product produced since last acceptable CCP monitoring. Document corrective action. This is the fastest mandatory clock in food safety compliance \\u2014 no grace period, no business day buffer.\",\n    \"escalation\": \"Food Safety Manager + QA Director + Production Supervisor + Legal\"\n  },\n  \"FDA_FSMA_FOOD_RECALL_CLASS_I\": {\n    \"clock\": \"24 hours\",\n    \"action\": \"FDA Class I recall \\u2014 reasonable probability of serious adverse health consequences or death. Notify FDA within 24h of decision (21 CFR \\u00a77.59(c)). Public warning may be required. Traceability records under FSMA \\u00a7204 must be available to FDA within 24h of request to support recall scope determination.\",\n    \"escalation\": \"Chief Food Safety Officer + Legal Counsel + FDA Recall Coordinator\"\n  },\n  \"FDA_FSMA_TRACEABILITY_REQUEST\": {\n    \"clock\": \"24 hours\",\n    \"action\": \"FDA requests traceability records for Foods on the Traceability List (FTL) under FSMA \\u00a7204(d). Records must be available within 24 hours. Records must cover the full key data element chain: KDEs at growing/shipping/receiving/transformation/distribution. Failure to produce = civil penalty and potential detention of product in commerce.\",\n    \"escalation\": \"FSMA Compliance Lead + IT/Data Team + Legal\"\n  },\n  \"USDA_FSIS_NONCOMPLIANCE_RECORD\": {\n    \"clock\": \"72 hours\",\n    \"action\": \"USDA FSIS Noncompliance Record (NR) issued under 9 CFR \\u00a7417.6. Corrective action response required. If not corrected, FSIS will verify that the establishment has taken corrective action or issue a Notice of Suspension. Document corrective action in HACCP records.\",\n    \"escalation\": \"HACCP Coordinator + QA Manager + FSIS Plant Representative\"\n  },\n  \"EU_RASFF_RAPID_ALERT_NOTIFICATION\": {\n    \"clock\": \"24 hours\",\n    \"action\": \"EU Rapid Alert System for Food and Feed (RASFF) notification under Regulation 178/2002 Art.50. Serious risk food/feed on EU market \\u2014 immediate notification to national authority within 24h. Business operators must notify the competent authority in the Member State where the food was placed on the market.\",\n    \"escalation\": \"EU Regulatory Affairs + National Competent Authority + Legal\"\n  },\n  \"FDA_IMPORT_ALERT_DETENTION\": {\n    \"clock\": \"IMMEDIATE\",\n    \"action\": \"Product detained at US port of entry under FDA import alert (21 USC \\u00a7381(a)). Importer must respond with evidence that the product is not violative or arrange for re-export/destruction. No sales of detained lots may proceed. Traceability documentation, supplier FSVP records, and lab results must be assembled immediately.\",\n    \"escalation\": \"Import Operations + FSVP Compliance Manager + Legal Counsel\"\n  },\n  \"ALLERGEN_UNDECLARED_CONTAMINATION\": {\n    \"clock\": \"IMMEDIATE\",\n    \"action\": \"Undeclared allergen confirmed or suspected in product. EU FIC 1169/2011 Art.14 \\u2014 food unsafe if it injures or is likely to injure health; undeclared allergen = unsafe food = mandatory withdrawal/recall. US: FDA Class I recall likely. Issue product hold immediately. Notify regulators. Pull allergen declaration from label database for audit.\",\n    \"escalation\": \"Food Safety Manager + Regulatory Affairs + Legal + PR\"\n  },\n  \"FOREIGN_SUPPLIER_FSMA_VIOLATION\": {\n    \"clock\": \"48 hours\",\n    \"action\": \"Foreign supplier fails FSVP verification under 21 CFR \\u00a71.502. Must quarantine or dispose of affected product received from that supplier if there is reason to believe it may be adulterated or misbranded. Document verification failure. Conduct enhanced supplier verification before resuming imports.\",\n    \"escalation\": \"FSVP Program Manager + Procurement + QA + Legal\"\n  }\n};\nconst type = $input.first().json.incident_type;\nconst meta = INCIDENTS[type] || {clock: 'Unknown', action: 'Manual review required', escalation: 'Food Safety Manager'};\nreturn [{json: {...($input.first().json), ...meta, ts: new Date().toISOString()}}];\n"
      }
    },
    {
      "id": "slack-4",
      "name": "Slack #food-safety-incidents",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#food-safety-incidents",
        "text": "FOOD SAFETY INCIDENT: {{$json.incident_type}} | Clock: {{$json.clock}} | {{$json.action}}"
      }
    },
    {
      "id": "email-4",
      "name": "Email Escalation Team",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "={{$json.escalation_email}}",
        "bccList": "ciso@your-foodtech-platform.com,legal@your-foodtech-platform.com",
        "subject": "FOOD SAFETY INCIDENT: {{$json.incident_type}} \u2014 Clock: {{$json.clock}}",
        "message": "Incident: {{$json.incident_type}}\\n\\nClock: {{$json.clock}}\\n\\nRequired action: {{$json.action}}\\n\\nEscalation: {{$json.escalation}}\\n\\nTimestamp: {{$json.ts}}"
      }
    },
    {
      "id": "pg-4",
      "name": "Log to Postgres",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "insert",
        "schema": "public",
        "table": "food_safety_incidents",
        "columns": "incident_type,clock,action,escalation,ts",
        "additionalFields": {
          "mode": "define"
        }
      }
    },
    {
      "id": "respond-4",
      "name": "Respond 200",
      "type": "n8n-nodes-base.respondToWebhook",
      "parameters": {
        "responseCode": 200,
        "responseBody": "{\"status\":\"routed\"}"
      }
    }
  ],
  "connections": {
    "Incident Webhook": {
      "main": [
        [
          {
            "node": "Incident Router",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Incident Router": {
      "main": [
        [
          {
            "node": "Slack #food-safety-incidents",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Escalation Team",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log to Postgres",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Postgres": {
      "main": [
        [
          {
            "node": "Respond 200",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 5: Weekly FoodSafety Platform KPI Dashboard

Monday 8 AM dual Postgres query (this week plus last week), Merge node, Code node builds HTML table with week-over-week delta via $getWorkflowStaticData, emails CEO and CPO (BCC CISO + Legal), posts one-liner to Slack #weekly-kpi.

KPIs tracked: active customers, new customers (7d), HACCP plans created (7d), traceability exports (7d), FDA incidents (7d), allergen alerts (7d), recalls (7d), MRR. The allergen alert and recall rows are the compliance debt signal that regulators and insurance carriers watch most closely.

{
  "name": "Weekly FoodSafety Platform KPI Dashboard",
  "nodes": [
    {
      "id": "cron-5",
      "name": "Monday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "pg-this-5",
      "name": "Postgres \u2014 This Week",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(DISTINCT customer_id) as active_customers, COUNT(CASE WHEN created_at > NOW()-INTERVAL '7 days' THEN 1 END) as new_customers_7d, COUNT(CASE WHEN event_type='HACCP_PLAN_CREATED' AND created_at > NOW()-INTERVAL '7 days' THEN 1 END) as haccp_plans_7d, COUNT(CASE WHEN event_type='TRACEABILITY_EXPORT' AND created_at > NOW()-INTERVAL '7 days' THEN 1 END) as traceability_exports_7d, COUNT(CASE WHEN event_type='FDA_INCIDENT' AND created_at > NOW()-INTERVAL '7 days' THEN 1 END) as fda_incidents_7d, COUNT(CASE WHEN event_type='ALLERGEN_ALERT' AND created_at > NOW()-INTERVAL '7 days' THEN 1 END) as allergen_alerts_7d, COUNT(CASE WHEN event_type='RECALL' AND created_at > NOW()-INTERVAL '7 days' THEN 1 END) as recalls_7d, SUM(CASE WHEN event_type='SUBSCRIPTION_PAYMENT' AND created_at > NOW()-INTERVAL '7 days' THEN amount ELSE 0 END) as mrr_7d FROM platform_events WHERE created_at > NOW()-INTERVAL '30 days'"
      }
    },
    {
      "id": "pg-last-5",
      "name": "Postgres \u2014 Last Week",
      "type": "n8n-nodes-base.postgres",
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(DISTINCT customer_id) as active_customers_lw, COUNT(CASE WHEN created_at BETWEEN NOW()-INTERVAL '14 days' AND NOW()-INTERVAL '7 days' THEN 1 END) as new_customers_lw, COUNT(CASE WHEN event_type='HACCP_PLAN_CREATED' AND created_at BETWEEN NOW()-INTERVAL '14 days' AND NOW()-INTERVAL '7 days' THEN 1 END) as haccp_plans_lw, COUNT(CASE WHEN event_type='TRACEABILITY_EXPORT' AND created_at BETWEEN NOW()-INTERVAL '14 days' AND NOW()-INTERVAL '7 days' THEN 1 END) as traceability_exports_lw, SUM(CASE WHEN event_type='SUBSCRIPTION_PAYMENT' AND created_at BETWEEN NOW()-INTERVAL '14 days' AND NOW()-INTERVAL '7 days' THEN amount ELSE 0 END) as mrr_lw FROM platform_events"
      }
    },
    {
      "id": "merge-5",
      "name": "Merge KPIs",
      "type": "n8n-nodes-base.merge",
      "parameters": {
        "mode": "combine",
        "combinationMode": "mergeByPosition"
      }
    },
    {
      "id": "report-5",
      "name": "Build HTML Report",
      "type": "n8n-nodes-base.code",
      "parameters": {
        "jsCode": "\nconst store = $getWorkflowStaticData('global');\nconst tw = $('Postgres \u2014 This Week').first().json;\nconst lw = $('Postgres \u2014 Last Week').first().json;\nconst pct = (a, b) => b && b > 0 ? ((a - b) / b * 100).toFixed(1) + '%' : 'N/A';\nconst html = `<table border='1' cellpadding='6' style='border-collapse:collapse;font-family:monospace'>\n<tr><th>KPI</th><th>This Week</th><th>Last Week</th><th>WoW</th></tr>\n<tr><td>Active Customers</td><td>${tw.active_customers}</td><td>${lw.active_customers_lw}</td><td>${pct(tw.active_customers, lw.active_customers_lw)}</td></tr>\n<tr><td>New Customers (7d)</td><td>${tw.new_customers_7d}</td><td>${lw.new_customers_lw}</td><td>${pct(tw.new_customers_7d, lw.new_customers_lw)}</td></tr>\n<tr><td>HACCP Plans Created (7d)</td><td>${tw.haccp_plans_7d}</td><td>${lw.haccp_plans_lw}</td><td>${pct(tw.haccp_plans_7d, lw.haccp_plans_lw)}</td></tr>\n<tr><td>Traceability Exports (7d)</td><td>${tw.traceability_exports_7d}</td><td>${lw.traceability_exports_lw}</td><td>${pct(tw.traceability_exports_7d, lw.traceability_exports_lw)}</td></tr>\n<tr><td>FDA Incidents (7d)</td><td>${tw.fda_incidents_7d}</td><td>\u2014</td><td>\u2014</td></tr>\n<tr><td>Allergen Alerts (7d)</td><td>${tw.allergen_alerts_7d}</td><td>\u2014</td><td>\u2014</td></tr>\n<tr><td>Recalls (7d)</td><td>${tw.recalls_7d}</td><td>\u2014</td><td>\u2014</td></tr>\n<tr><td>MRR (7d)</td><td>$${(tw.mrr_7d||0).toLocaleString()}</td><td>$${(lw.mrr_lw||0).toLocaleString()}</td><td>${pct(tw.mrr_7d, lw.mrr_lw)}</td></tr>\n</table>`;\nstore.lastWeekKPIs = tw;\nreturn [{json: {html, tw, ts: new Date().toISOString()}}];\n"
      }
    },
    {
      "id": "email-5",
      "name": "Email CEO + CPO",
      "type": "n8n-nodes-base.gmail",
      "parameters": {
        "operation": "send",
        "toList": "ceo@your-foodtech-platform.com,cpo@your-foodtech-platform.com",
        "bccList": "ciso@your-foodtech-platform.com,legal@your-foodtech-platform.com",
        "subject": "Weekly FoodSafety Platform KPI \u2014 {{$json.ts.slice(0,10)}}",
        "message": "={{$json.html}}",
        "emailType": "html"
      }
    },
    {
      "id": "slack-5",
      "name": "Slack #weekly-kpi",
      "type": "n8n-nodes-base.slack",
      "parameters": {
        "channel": "#weekly-kpi",
        "text": "Weekly: {{$json.tw.active_customers}} active customers | {{$json.tw.fda_incidents_7d}} FDA incidents | {{$json.tw.allergen_alerts_7d}} allergen alerts | {{$json.tw.recalls_7d}} recalls | MRR ${{$json.tw.mrr_7d}} \u2014 full report in email"
      }
    }
  ],
  "connections": {
    "Monday 8 AM": {
      "main": [
        [
          {
            "node": "Postgres \u2014 This Week",
            "type": "main",
            "index": 0
          },
          {
            "node": "Postgres \u2014 Last Week",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres \u2014 This Week": {
      "main": [
        [
          {
            "node": "Merge KPIs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres \u2014 Last Week": {
      "main": [
        [
          {
            "node": "Merge KPIs",
            "type": "main",
            "index": 1
          }
        ]
      ]
    },
    "Merge KPIs": {
      "main": [
        [
          {
            "node": "Build HTML Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build HTML Report": {
      "main": [
        [
          {
            "node": "Email CEO + CPO",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack #weekly-kpi",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

The HACCP critical limit clock: why it matters for automation architecture

HACCP critical limit deviation under 21 CFR §120.12 is the only mandatory food safety clock that requires corrective action before distribution — with no grace period and no business day buffer. When a critical control point (CCP) critical limit is not met, the establishment must take corrective action, hold all affected product, and document the deviation before any product is released.

If your HACCP deviation alert routes through a cloud iPaaS message queue before triggering the product hold, that queue latency is the interval during which non-conforming product could ship. Self-hosted n8n, running inside your network perimeter alongside your HACCP system, eliminates that latency from the corrective action path.

The FDA §204 traceability request is the second critical timing constraint: 24 hours from FDA request to full key data element production. If your traceability records are stored in a cloud SaaS with third-party retrieval dependencies, meeting that 24-hour window requires coordinating across two organizations. Self-hosted traceability records under your legal custody remove that dependency.

Self-hosting argument for FoodTech SaaS

Five reasons food safety SaaS procurement teams ask about automation data handling:

FDA FSMA §204 traceability sovereignty: FDA may subpoena cloud vendors independently — self-hosted Postgres keeps your FTL traceability records under your legal custody, simplifying the FDA access pathway and eliminating the cloud vendor from the records chain.

HACCP CCP monitoring records (21 CFR §120.8): USDA FSIS inspectors require real-time on-site record access during inspections. Cloud retrieval latency or third-party service outages risk non-compliance during an inspection window.

EU FIC 1169/2011 allergen composition data: allergen declarations are product liability. A cloud breach affecting allergen master data creates undeclared allergen exposure — a recall and potential criminal liability under EU food law. Self-hosted Postgres keeps allergen composition under your IFQ control boundary.

USDA FSIS 9 CFR §417.5(d) on-site retention: HACCP records must be maintained at the establishment or an accessible location. On-premises Postgres satisfies this requirement definitively; cloud storage may introduce ambiguity during enforcement.

SOC 2 CC9.2: routing food safety data through an additional cloud automation vendor expands your SOC 2 vendor risk management scope. Self-hosted n8n keeps your compliance automation inside your existing security boundary.

Get the complete workflow pack

All 5 workflows above — with full import-ready JSON — plus 10 more food safety, supply chain, and regulatory compliance workflows are available in the FlowKit n8n Automation Pack at stripeai.gumroad.com. Individual templates: $12–$29. Full bundle (15 workflows): $97.

Top comments (0)