DEV Community

Alex Kane
Alex Kane

Posted on

n8n for RetailTech SaaS: 5 Automations for PCI DSS, CCPA, and ADA Compliance (Free Workflow JSON)

If your SaaS platform powers POS systems, inventory management, or loyalty programs for retailers, compliance isn't just your customers' problem — it's yours.

Under PCI DSS v4.0, if your platform touches cardholder data, you're either a service provider with your own QSA assessment or you're in scope on your merchants' SAQ. Under CCPA/CPRA, if you process California consumers' personal information on behalf of retailers, you're a service provider with contractual deletion and restriction obligations. Under ADA Title III, your web-based POS and customer-facing interfaces must meet WCAG 2.1 AA — courts have ruled that retail websites (and the SaaS platforms powering them) are public accommodations.

The problem: most RetailTech teams manage these obligations in spreadsheets, email threads, or not at all. When a merchant asks 'Can you confirm your PCI DSS scope?' or a state AG sends a CCPA audit letter, the answer is either documented or it isn't.

Here are 5 n8n workflows that close the gap. All JSON is import-ready — paste into n8n → Import from JSON.


Why self-hosted n8n for RetailTech compliance?

  • PCI DSS Req 12.8 / 12.9: Service provider inventory and assessment documentation must be maintained. Routing merchant PII or cardholder-adjacent data through Zapier/Make cloud adds an unassessed service provider to your QSA scope.
  • CCPA/CPRA Cal. Civ. Code §1798.100 / §1798.140(ag): Service provider status requires contractual restrictions on data use. Cloud iPaaS tools that log workflow payloads may void service-provider status, exposing you to direct consumer claims.
  • ADA Title III / WCAG 2.1 AA: Robles v. Domino's (9th Cir. 2019) and subsequent cases have extended ADA obligations to mobile apps and web platforms — including the SaaS tools that power them. An accessibility outage on your POS interface is a potential §308 statutory damages exposure.
  • State consumer protection: CPRA (CA), CPA (CO), CTDPA (CT), UCPA (UT), VCDPA (VA) all impose data minimization and consumer rights obligations that flow to service providers.

Self-hosted n8n means workflow data stays in your VPC. Git-versioned workflow JSON = audit-ready change history. No cloud vendor in your QSA scope.


Workflow 1: Retailer Customer Onboarding Drip

Segments new merchants by type and compliance exposure, then sends a tailored 3-touch onboarding sequence with the right compliance documentation for each tier.

Trigger: Google Sheets — new row in retailer_customers tab
Logic: Code node classifies by merchant_type (POS_ENTERPRISE / REGIONAL_CHAIN / SMB_BRICK_MORTAR / ECOMMERCE_HYBRID) and sets compliance flags (PCI_DSS_SAQ_D / PCI_DSS_SAQ_A / CCPA / CPRA / ADA_TITLE_III / STATE_CONSUMER_PROTECTION). Each flag controls which documentation links appear in the drip emails.

{
  "name": "RetailTech \u2014 Retailer Onboarding Drip",
  "nodes": [
    {
      "id": "n1",
      "name": "New Retailer Row",
      "type": "n8n-nodes-base.googleSheetsTrigger",
      "position": [
        250,
        300
      ],
      "parameters": {
        "sheetId": "YOUR_SHEET_ID",
        "range": "retailer_customers!A:M",
        "event": "rowAdded"
      }
    },
    {
      "id": "n2",
      "name": "Classify Tier & Compliance Flags",
      "type": "n8n-nodes-base.code",
      "position": [
        470,
        300
      ],
      "parameters": {
        "jsCode": "const row = $input.first().json;\nconst type = (row.merchant_type || '').toUpperCase();\nconst revenue = parseFloat(row.annual_revenue_usd || 0);\nconst state = (row.state || '').toUpperCase();\n\nconst tier = type === 'POS_ENTERPRISE' || revenue > 5000000 ? 'ENTERPRISE' :\n  type === 'REGIONAL_CHAIN' || revenue > 500000 ? 'REGIONAL' :\n  type === 'ECOMMERCE_HYBRID' ? 'ECOMMERCE' : 'SMB';\n\nconst flags = [];\nif (tier === 'ENTERPRISE' || tier === 'REGIONAL') flags.push('PCI_DSS_SAQ_D');\nelse flags.push('PCI_DSS_SAQ_A');\nif (['CA','CO','CT','UT','VA'].includes(state)) flags.push('CCPA');\nif (state === 'CA') flags.push('CPRA');\nif (type === 'ECOMMERCE_HYBRID' || tier === 'ENTERPRISE') flags.push('ADA_TITLE_III');\nif (['CA','NY','TX','FL','IL'].includes(state)) flags.push('STATE_CONSUMER_PROTECTION');\n\nreturn [{ json: { ...row, tier, flags, cs_owner: row.cs_owner || 'cs-team@yourcompany.com' } }];"
      }
    },
    {
      "id": "n3",
      "name": "Day 0 \u2014 Welcome + Compliance Docs",
      "type": "n8n-nodes-base.gmail",
      "position": [
        700,
        300
      ],
      "parameters": {
        "operation": "send",
        "to": "={{ $json.email }}",
        "subject": "Welcome to FlowKit \u2014 Your compliance documentation",
        "message": "={{ 'Hi ' + $json.contact_name + ',\\n\\nWelcome aboard. Here are the compliance documents relevant to your account:\\n\\n' + ($json.flags.includes('PCI_DSS_SAQ_D') ? '\u2022 PCI DSS SAQ D Service Provider Checklist\\n' : '\u2022 PCI DSS SAQ A Checklist\\n') + ($json.flags.includes('CCPA') ? '\u2022 CCPA/CPRA Service Provider DPA (please sign and return)\\n' : '') + ($json.flags.includes('ADA_TITLE_III') ? '\u2022 ADA Title III / WCAG 2.1 AA Accessibility Statement\\n' : '') + '\\nYour CSM: ' + $json.cs_owner }}",
        "additionalFields": {}
      }
    },
    {
      "id": "n4",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        700,
        430
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "onboarding_log!A:F",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      }
    },
    {
      "id": "n5",
      "name": "Wait 3 Days",
      "type": "n8n-nodes-base.wait",
      "position": [
        920,
        300
      ],
      "parameters": {
        "amount": 3,
        "unit": "days"
      }
    },
    {
      "id": "n6",
      "name": "Day 3 \u2014 PCI DSS Best Practices",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1150,
        300
      ],
      "parameters": {
        "operation": "send",
        "to": "={{ $json.email }}",
        "subject": "3 PCI DSS mistakes RetailTech platforms make (and how to avoid them)",
        "message": "={{ 'Hi ' + $json.contact_name + ',\\n\\nThree days in \u2014 quick note on PCI DSS Req 12.8/12.9 for service providers:\\n\\n1. Maintain an inventory of all service providers (including your iPaaS tools)\\n2. Conduct annual assessments or obtain their AOC\\n3. Confirm their PCI DSS compliance status in writing\\n\\nWe maintain our own SAQ D / QSA assessment documentation. Reply and we will send it.' }}",
        "additionalFields": {}
      }
    },
    {
      "id": "n7",
      "name": "Wait 4 Days",
      "type": "n8n-nodes-base.wait",
      "position": [
        1370,
        300
      ],
      "parameters": {
        "amount": 4,
        "unit": "days"
      }
    },
    {
      "id": "n8",
      "name": "Day 7 \u2014 CCPA/ADA Check-In",
      "type": "n8n-nodes-base.gmail",
      "position": [
        1590,
        300
      ],
      "parameters": {
        "operation": "send",
        "to": "={{ $json.email }}",
        "subject": "Week 1 check-in \u2014 CCPA deletion workflows and ADA accessibility",
        "message": "={{ 'Hi ' + $json.contact_name + ',\\n\\nWeek 1 check-in. Two compliance areas to confirm:\\n\\n' + ($json.flags.includes('CCPA') ? '\u2022 CCPA/CPRA: Have you configured consumer deletion request routing? As your service provider, we need 45 days to process deletion requests you forward to us (Cal. Civ. Code \u00a71798.105).\\n\\n' : '') + ($json.flags.includes('ADA_TITLE_III') ? '\u2022 ADA Title III: Our POS interface is WCAG 2.1 AA certified. If you are embedding our checkout widget, please confirm your own accessibility review is complete.\\n\\n' : '') + 'Reply to schedule a compliance walkthrough.' }}",
        "additionalFields": {}
      }
    }
  ],
  "connections": {
    "New Retailer Row": {
      "main": [
        [
          {
            "node": "Classify Tier & Compliance Flags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Tier & Compliance Flags": {
      "main": [
        [
          {
            "node": "Day 0 \u2014 Welcome + Compliance Docs",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 0 \u2014 Welcome + Compliance Docs": {
      "main": [
        [
          {
            "node": "Wait 3 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 3 Days": {
      "main": [
        [
          {
            "node": "Day 3 \u2014 PCI DSS Best Practices",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Day 3 \u2014 PCI DSS Best Practices": {
      "main": [
        [
          {
            "node": "Wait 4 Days",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Wait 4 Days": {
      "main": [
        [
          {
            "node": "Day 7 \u2014 CCPA/ADA Check-In",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Key angle: CCPA/CPRA service provider status (Cal. Civ. Code §1798.140(ag)) requires written contracts limiting data use. If Day 0 doesn't send a signed DPA, you're exposed to direct consumer claims — not just your merchant's problem.


Workflow 2: POS / Inventory API Health Monitor

Polls your platform's core APIs every 5 minutes and escalates by tier. A POS outage during peak retail hours is both a revenue event and a PCI DSS data integrity issue.

{
  "name": "RetailTech \u2014 POS API Health Monitor",
  "nodes": [
    {
      "id": "n1",
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      }
    },
    {
      "id": "n2",
      "name": "Load Endpoints",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        450,
        300
      ],
      "parameters": {
        "operation": "getAll",
        "sheetId": "YOUR_SHEET_ID",
        "range": "api_endpoints!A:F"
      }
    },
    {
      "id": "n3",
      "name": "Check Each Endpoint",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        650,
        300
      ],
      "parameters": {
        "url": "={{ $json.endpoint_url }}",
        "method": "GET",
        "timeout": 8000,
        "continueOnFail": true
      }
    },
    {
      "id": "n4",
      "name": "Classify Status",
      "type": "n8n-nodes-base.code",
      "position": [
        850,
        300
      ],
      "parameters": {
        "jsCode": "const results = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  const status = d.statusCode === 200 ? 'OK' :\n    d.statusCode >= 500 ? 'DOWN' :\n    d.error ? 'DOWN' : 'DEGRADED';\n  const pciRisk = status !== 'OK' && (d.endpoint_type || '').includes('PAYMENT') ?\n    'PCI_DSS_REQ_10_INTEGRITY_RISK' : null;\n  const ccpaRisk = status !== 'OK' && (d.endpoint_type || '').includes('CONSUMER') ?\n    'CCPA_DELETION_PIPELINE_BROKEN' : null;\n  results.push({ json: { ...d, status, pciRisk, ccpaRisk,\n    ts: new Date().toISOString() } });\n}\nreturn results;"
      }
    },
    {
      "id": "n5",
      "name": "Filter Non-OK",
      "type": "n8n-nodes-base.filter",
      "position": [
        1050,
        300
      ],
      "parameters": {
        "conditions": {
          "string": [
            {
              "value1": "={{ $json.status }}",
              "operation": "notEqual",
              "value2": "OK"
            }
          ]
        }
      }
    },
    {
      "id": "n6",
      "name": "Dedup (30min)",
      "type": "n8n-nodes-base.code",
      "position": [
        1250,
        300
      ],
      "parameters": {
        "jsCode": "const state = $getWorkflowStaticData('global');\nif (!state.alerted) state.alerted = {};\nconst now = Date.now();\nconst out = [];\nfor (const item of $input.all()) {\n  const key = item.json.endpoint_url;\n  if (!state.alerted[key] || now - state.alerted[key] > 1800000) {\n    state.alerted[key] = now;\n    out.push(item);\n  }\n}\nreturn out;"
      }
    },
    {
      "id": "n7",
      "name": "Alert Slack #ops",
      "type": "n8n-nodes-base.slack",
      "position": [
        1450,
        300
      ],
      "parameters": {
        "channel": "#retailtech-ops",
        "text": "={{ '[' + $json.status + '] ' + $json.endpoint_name + ' (' + $json.endpoint_url + ')' + ($json.pciRisk ? ' \u26a0\ufe0f ' + $json.pciRisk : '') + ($json.ccpaRisk ? ' \u26a0\ufe0f ' + $json.ccpaRisk : '') }}"
      }
    },
    {
      "id": "n8",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1450,
        430
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "sheetId": "YOUR_SHEET_ID",
        "range": "api_health_log!A:G",
        "columns": {
          "mappingMode": "autoMapInputData"
        }
      }
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Load Endpoints",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Endpoints": {
      "main": [
        [
          {
            "node": "Check Each Endpoint",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Each Endpoint": {
      "main": [
        [
          {
            "node": "Classify Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Status": {
      "main": [
        [
          {
            "node": "Filter Non-OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Filter Non-OK": {
      "main": [
        [
          {
            "node": "Dedup (30min)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedup (30min)": {
      "main": [
        [
          {
            "node": "Alert Slack #ops",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

PCI DSS angle: PCI DSS v4.0 Req 10.7 requires detection of critical control failures within 24 hours. A 5-minute poll with Req 10 labeling satisfies the detection component — and gives your QSA a monitoring log to review.


Workflow 3: PCI DSS / CCPA / CPRA / ADA Compliance Deadline Tracker

Runs daily at 8 AM, checks a spreadsheet of deadlines, and routes alerts at OVERDUE / CRITICAL (≤14d) / URGENT (≤30d) / WARNING (≤60d) / NOTICE (≤90d) thresholds. Includes dedup so each deadline generates one Slack message per severity band per day.

{
  "name": "RetailTech \u2014 PCI DSS / CCPA / ADA Deadline Tracker",
  "nodes": [
    {
      "id": "n1",
      "name": "Daily 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * *"
            }
          ]
        }
      }
    },
    {
      "id": "n2",
      "name": "Load Deadlines",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        450,
        300
      ],
      "parameters": {
        "operation": "getAll",
        "sheetId": "YOUR_SHEET_ID",
        "range": "compliance_deadlines!A:H"
      }
    },
    {
      "id": "n3",
      "name": "Classify Urgency",
      "type": "n8n-nodes-base.code",
      "position": [
        650,
        300
      ],
      "parameters": {
        "jsCode": "const today = new Date();\nconst out = [];\nconst DEADLINES = [\n  'PCI_DSS_QSA_ASSESSMENT', 'PCI_DSS_SAQ_SUBMISSION', 'PCI_DSS_ASV_SCAN',\n  'PCI_DSS_PENETRATION_TEST', 'CCPA_PRIVACY_POLICY_REVIEW', 'CCPA_DPA_RENEWAL',\n  'CPRA_DATA_MINIMIZATION_AUDIT', 'ADA_TITLE_III_ACCESSIBILITY_AUDIT',\n  'WCAG_21_AA_REMEDIATION', 'STATE_AG_RESPONSE_DEADLINE',\n  'BREACH_NOTIFICATION_WINDOW', 'ANNUAL_VENDOR_SECURITY_REVIEW'\n];\nfor (const row of $input.all().map(i => i.json)) {\n  const due = new Date(row.due_date);\n  const days = Math.floor((due - today) / 86400000);\n  const severity = days < 0 ? 'OVERDUE' : days <= 14 ? 'CRITICAL' :\n    days <= 30 ? 'URGENT' : days <= 60 ? 'WARNING' :\n    days <= 90 ? 'NOTICE' : null;\n  if (severity) {\n    const key = row.deadline_id + '_' + severity;\n    if (row.alert_sent_date !== new Date().toISOString().slice(0,10))\n      out.push({ json: { ...row, days_remaining: days, severity, dedup_key: key } });\n  }\n}\nreturn out;"
      }
    },
    {
      "id": "n4",
      "name": "Route by Severity",
      "type": "n8n-nodes-base.switch",
      "position": [
        850,
        300
      ],
      "parameters": {
        "dataType": "string",
        "value1": "={{ $json.severity }}",
        "rules": {
          "rules": [
            {
              "value2": "OVERDUE",
              "output": 0
            },
            {
              "value2": "CRITICAL",
              "output": 1
            },
            {
              "value2": "URGENT",
              "output": 2
            },
            {
              "value2": "WARNING",
              "output": 3
            }
          ]
        }
      }
    },
    {
      "id": "n5",
      "name": "Slack OVERDUE",
      "type": "n8n-nodes-base.slack",
      "position": [
        1050,
        150
      ],
      "parameters": {
        "channel": "#retailtech-compliance",
        "text": "={{ '\ud83d\udea8 OVERDUE: ' + $json.deadline_type + ' \u2014 ' + $json.description + ' (' + Math.abs($json.days_remaining) + ' days past due). Owner: ' + $json.owner + '. Reg: ' + $json.regulation_citation }}"
      }
    },
    {
      "id": "n6",
      "name": "Slack CRITICAL",
      "type": "n8n-nodes-base.slack",
      "position": [
        1050,
        280
      ],
      "parameters": {
        "channel": "#retailtech-compliance",
        "text": "={{ '\ud83d\udd34 CRITICAL: ' + $json.deadline_type + ' due in ' + $json.days_remaining + ' days \u2014 ' + $json.description + '. Owner: ' + $json.owner }}"
      }
    },
    {
      "id": "n7",
      "name": "Slack URGENT",
      "type": "n8n-nodes-base.slack",
      "position": [
        1050,
        410
      ],
      "parameters": {
        "channel": "#retailtech-compliance",
        "text": "={{ '\ud83d\udfe0 URGENT: ' + $json.deadline_type + ' due in ' + $json.days_remaining + ' days. Owner: ' + $json.owner }}"
      }
    },
    {
      "id": "n8",
      "name": "Slack WARNING",
      "type": "n8n-nodes-base.slack",
      "position": [
        1050,
        540
      ],
      "parameters": {
        "channel": "#retailtech-compliance",
        "text": "={{ '\ud83d\udfe1 WARNING: ' + $json.deadline_type + ' due in ' + $json.days_remaining + ' days. Owner: ' + $json.owner }}"
      }
    },
    {
      "id": "n9",
      "name": "Mark Alert Sent",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1250,
        300
      ],
      "parameters": {
        "operation": "update",
        "sheetId": "YOUR_SHEET_ID",
        "range": "compliance_deadlines",
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "mappingMode": "defineBelow"
          }
        },
        "options": {}
      }
    }
  ],
  "connections": {
    "Daily 8 AM": {
      "main": [
        [
          {
            "node": "Load Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Deadlines": {
      "main": [
        [
          {
            "node": "Classify Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Urgency": {
      "main": [
        [
          {
            "node": "Route by Severity",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Severity": {
      "main": [
        [
          {
            "node": "Slack OVERDUE",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack CRITICAL",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack URGENT",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Slack WARNING",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

12 deadline types tracked: PCI_DSS_QSA_ASSESSMENT, PCI_DSS_SAQ_SUBMISSION, PCI_DSS_ASV_SCAN, PCI_DSS_PENETRATION_TEST, CCPA_PRIVACY_POLICY_REVIEW, CCPA_DPA_RENEWAL, CPRA_DATA_MINIMIZATION_AUDIT, ADA_TITLE_III_ACCESSIBILITY_AUDIT, WCAG_21_AA_REMEDIATION, STATE_AG_RESPONSE_DEADLINE, BREACH_NOTIFICATION_WINDOW, ANNUAL_VENDOR_SECURITY_REVIEW.


Workflow 4: Retail Data Breach & PCI DSS Alert Pipeline

Receives breach/incident webhooks and routes by incident type with the correct regulatory clock for each. PCI DSS §12.10.4 requires an incident response plan with defined notification timelines — this automates the first 30 minutes.

{
  "name": "RetailTech \u2014 Retail Data Breach Alert Pipeline",
  "nodes": [
    {
      "id": "n1",
      "name": "Incident Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        250,
        300
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "retail-breach-alert",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "n2",
      "name": "Immediate 200 OK",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        450,
        450
      ],
      "parameters": {
        "respondWith": "json",
        "responseBody": "{\"received\": true}"
      }
    },
    {
      "id": "n3",
      "name": "Classify Incident",
      "type": "n8n-nodes-base.code",
      "position": [
        450,
        300
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json.body || $input.first().json;\nconst type = (d.incident_type || '').toUpperCase();\nconst merchantState = (d.merchant_state || '').toUpperCase();\n\nconst REGULATORY_CLOCKS = {\n  'CARDHOLDER_DATA_BREACH': { clock: 'PCI_DSS_IMMEDIATE', hours: 24, citation: 'PCI DSS v4.0 \u00a712.10.4 + Card Brand Rules' },\n  'CCPA_CONSUMER_DATA_BREACH': { clock: 'CCPA_72H', hours: 72, citation: 'Cal. Civ. Code \u00a71798.82 \u2014 30 days if >500 CA residents' },\n  'POS_MALWARE_INCIDENT': { clock: 'PCI_DSS_IMMEDIATE', hours: 24, citation: 'PCI DSS \u00a712.10.4 \u2014 contains potential CHD exposure' },\n  'INVENTORY_DATA_EXFILTRATION': { clock: 'CCPA_72H', hours: 72, citation: 'CCPA \u00a71798.82 if contains PI; CPRA \u00a71798.150 if SPPI' },\n  'LOYALTY_DATA_BREACH': { clock: 'CCPA_72H', hours: 72, citation: 'CCPA \u00a71798.82 \u2014 loyalty = personal information under \u00a71798.140(v)' },\n  'CPRA_SENSITIVE_PI_EXPOSURE': { clock: 'CPRA_72H', hours: 72, citation: 'CPRA \u00a71798.121 \u2014 sensitive personal information: SSN, financial, precise geo' },\n  'ADA_ACCESSIBILITY_OUTAGE': { clock: 'INTERNAL_4H', hours: 4, citation: 'ADA Title III \u00a7308 \u2014 remediate promptly, document timeline' },\n  'STATE_AG_INQUIRY': { clock: 'STATE_AG_30D', hours: 720, citation: 'Varies by state: CA 30d, NY 30d, TX 30d from AG inquiry date' }\n};\n\nconst reg = REGULATORY_CLOCKS[type] || { clock: 'UNKNOWN', hours: 0, citation: 'Review applicable law' };\nconst notificationDeadline = new Date(Date.now() + reg.hours * 3600000).toISOString();\n\nreturn [{ json: { ...d, incident_type: type, ...reg, notification_deadline: notificationDeadline,\n  ts: new Date().toISOString(), merchant_state: merchantState } }];"
      }
    },
    {
      "id": "n4",
      "name": "Slack #security-incident",
      "type": "n8n-nodes-base.slack",
      "position": [
        700,
        300
      ],
      "parameters": {
        "channel": "#retailtech-security",
        "text": "={{ '\ud83d\udea8 INCIDENT: ' + $json.incident_type + '\\nMerchant: ' + ($json.merchant_id || 'UNKNOWN') + '\\nState: ' + $json.merchant_state + '\\nRegulatory clock: ' + $json.clock + ' (' + $json.hours + 'h)\\nNotification deadline: ' + $json.notification_deadline + '\\nCitation: ' + $json.citation + '\\nSummary: ' + ($json.description || 'See webhook payload') }}"
      }
    },
    {
      "id": "n5",
      "name": "Log to Postgres",
      "type": "n8n-nodes-base.postgres",
      "position": [
        700,
        430
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO incident_log (incident_id, incident_type, merchant_id, merchant_state, regulatory_clock, notification_deadline, citation, description, created_at) VALUES ('{{ $json.incident_id }}', '{{ $json.incident_type }}', '{{ $json.merchant_id }}', '{{ $json.merchant_state }}', '{{ $json.clock }}', '{{ $json.notification_deadline }}', '{{ $json.citation }}', '{{ $json.description }}', NOW()) ON CONFLICT (incident_id) DO NOTHING"
      }
    },
    {
      "id": "n6",
      "name": "Email Compliance Team",
      "type": "n8n-nodes-base.gmail",
      "position": [
        700,
        560
      ],
      "parameters": {
        "operation": "send",
        "to": "compliance@yourcompany.com",
        "cc": "ciso@yourcompany.com",
        "subject": "={{ '[' + $json.clock + '] ' + $json.incident_type + ' \u2014 ' + $json.notification_deadline }}",
        "message": "={{ 'Incident Type: ' + $json.incident_type + '\\nMerchant ID: ' + $json.merchant_id + '\\nMerchant State: ' + $json.merchant_state + '\\nRegulatory Clock: ' + $json.clock + ' (' + $json.hours + ' hours)\\nNotification Deadline: ' + $json.notification_deadline + '\\nCitation: ' + $json.citation + '\\nDescription: ' + $json.description + '\\n\\nPlease review incident response plan \u00a74.2 immediately.' }}",
        "additionalFields": {}
      }
    }
  ],
  "connections": {
    "Incident Webhook": {
      "main": [
        [
          {
            "node": "Classify Incident",
            "type": "main",
            "index": 0
          },
          {
            "node": "Immediate 200 OK",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident": {
      "main": [
        [
          {
            "node": "Slack #security-incident",
            "type": "main",
            "index": 0
          },
          {
            "node": "Log to Postgres",
            "type": "main",
            "index": 0
          },
          {
            "node": "Email Compliance Team",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

8 incident types: CARDHOLDER_DATA_BREACH, CCPA_CONSUMER_DATA_BREACH, POS_MALWARE_INCIDENT, INVENTORY_DATA_EXFILTRATION, LOYALTY_DATA_BREACH, CPRA_SENSITIVE_PI_EXPOSURE, ADA_ACCESSIBILITY_OUTAGE, STATE_AG_INQUIRY. Each has the correct regulatory clock and citation pre-loaded.


Workflow 5: Weekly RetailTech Platform KPI Dashboard

Runs every Monday at 8 AM. Pulls merchant health, compliance event counts, and PCI/CCPA at-risk flags into a single HTML email to CEO + CISO (BCC).

{
  "name": "RetailTech \u2014 Weekly Platform KPI Dashboard",
  "nodes": [
    {
      "id": "n1",
      "name": "Monday 8 AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        250,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "n2",
      "name": "Load Platform Metrics",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        450,
        300
      ],
      "parameters": {
        "operation": "getAll",
        "sheetId": "YOUR_SHEET_ID",
        "range": "platform_metrics!A:N"
      }
    },
    {
      "id": "n3",
      "name": "Load Compliance Events",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        450,
        430
      ],
      "parameters": {
        "operation": "getAll",
        "sheetId": "YOUR_SHEET_ID",
        "range": "compliance_deadlines!A:H"
      }
    },
    {
      "id": "n4",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "position": [
        700,
        300
      ],
      "parameters": {
        "jsCode": "const metrics = $('Load Platform Metrics').all().map(i => i.json);\nconst compliance = $('Load Compliance Events').all().map(i => i.json);\n\nconst latest = metrics[metrics.length - 1] || {};\nconst prev = metrics[metrics.length - 2] || {};\nconst wow = (a, b) => b && b !== 0 ? ((a - b) / b * 100).toFixed(1) + '%' : 'N/A';\n\nconst overdue = compliance.filter(c => c.severity === 'OVERDUE');\nconst critical = compliance.filter(c => c.severity === 'CRITICAL');\nconst pciAtRisk = compliance.filter(c => (c.deadline_type || '').startsWith('PCI'));\nconst ccpaAtRisk = compliance.filter(c => (c.deadline_type || '').startsWith('CCPA') || (c.deadline_type || '').startsWith('CPRA'));\n\nconst flagLine = [\n  overdue.length > 0 ? '[COMPLIANCE OVERDUE: ' + overdue.length + ']' : null,\n  pciAtRisk.length > 0 ? '[PCI AT RISK: ' + pciAtRisk.length + ']' : null,\n  ccpaAtRisk.length > 0 ? '[CCPA/CPRA AT RISK: ' + ccpaAtRisk.length + ']' : null\n].filter(Boolean).join(' ');\n\nconst html = '<h2>RetailTech Weekly KPI \u2014 ' + new Date().toISOString().slice(0,10) + '</h2>' +\n  (flagLine ? '<p style=\"color:red;font-weight:bold\">' + flagLine + '</p>' : '') +\n  '<table border=\"1\" cellpadding=\"4\"><tr><th>Metric</th><th>This Week</th><th>WoW</th></tr>' +\n  '<tr><td>Active Merchants</td><td>' + latest.active_merchants + '</td><td>' + wow(latest.active_merchants, prev.active_merchants) + '</td></tr>' +\n  '<tr><td>MRR ($)</td><td>' + latest.mrr_usd + '</td><td>' + wow(latest.mrr_usd, prev.mrr_usd) + '</td></tr>' +\n  '<tr><td>New Merchants</td><td>' + latest.new_merchants + '</td><td>' + wow(latest.new_merchants, prev.new_merchants) + '</td></tr>' +\n  '<tr><td>Churn</td><td>' + latest.churned_merchants + '</td><td>-</td></tr>' +\n  '<tr><td>Open Compliance Items</td><td>' + (overdue.length + critical.length) + '</td><td>-</td></tr>' +\n  '<tr><td>PCI Events</td><td>' + pciAtRisk.length + '</td><td>-</td></tr>' +\n  '</table>';\n\nreturn [{ json: { html, flagLine, active_merchants: latest.active_merchants, mrr: latest.mrr_usd } }];"
      }
    },
    {
      "id": "n5",
      "name": "Email CEO + CISO BCC",
      "type": "n8n-nodes-base.gmail",
      "position": [
        950,
        300
      ],
      "parameters": {
        "operation": "send",
        "to": "ceo@yourcompany.com",
        "bcc": "ciso@yourcompany.com",
        "subject": "={{ 'RetailTech Weekly KPI \u2014 ' + new Date().toISOString().slice(0,10) + ($json.flagLine ? ' | ' + $json.flagLine : '') }}",
        "message": "={{ $json.html }}",
        "additionalFields": {
          "mimeType": "html"
        }
      }
    },
    {
      "id": "n6",
      "name": "Slack #leadership",
      "type": "n8n-nodes-base.slack",
      "position": [
        950,
        430
      ],
      "parameters": {
        "channel": "#leadership",
        "text": "={{ 'RetailTech KPI: ' + $json.active_merchants + ' active merchants, $' + $json.mrr + ' MRR' + ($json.flagLine ? ' | \u26a0\ufe0f ' + $json.flagLine : '') }}"
      }
    }
  ],
  "connections": {
    "Monday 8 AM": {
      "main": [
        [
          {
            "node": "Load Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Load Compliance Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Platform Metrics": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Load Compliance Events": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Email CEO + CISO BCC",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack #leadership",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

CISO BCC is intentional: PCI DSS Req 12.4.2 and CCPA's 'reasonable security' standard both require executive visibility into compliance posture. The BCC pattern ensures your CISO sees the same data as your CEO without requiring a separate report.


Getting these workflows

All 5 workflows are available as import-ready JSON in the FlowKit n8n Template Bundle at stripeai.gumroad.com — includes 15+ industry-specific compliance and ops workflows for SaaS teams.

To import: n8n → Workflows → Import from JSON → paste the JSON above.

The bundle also includes pre-built deadline spreadsheet templates for PCI DSS, CCPA/CPRA, ADA Title III, and state consumer protection law tracking.


RetailTech compliance comparison: n8n vs Zapier/Make

Requirement n8n (self-hosted) Zapier / Make
PCI DSS Req 12.8 — service provider inventory ✅ In-scope, documentable ❌ Adds unassessed cloud vendor
CCPA service provider status ✅ Data stays in your VPC ❌ Cloud logs may void SP status
ADA Title III remediation audit trail ✅ Git-versioned workflow JSON ❌ No change history
State AG response documentation ✅ Postgres audit log ❌ Partial at best
QSA attestation ✅ Single-tenant, fully auditable ❌ Shared infrastructure

All workflows are production patterns — replace YOUR_SHEET_ID, merchant_id field names, and Slack channels with your actual values before deploying.

Top comments (0)