DEV Community

Alex Kane
Alex Kane

Posted on

n8n for Cannabis/Hemp SaaS Vendors: 5 Automations for FinCEN BSA/AML, USDA Hemp COA, State Seed-to-Sale, and FDA Drug Product Compliance

The USDA hemp program has a clock most software vendors don't know they're running: under 7 CFR sec 990.70, hemp must be tested within 15 days before harvest. If the test returns THC above 0.3% on a dry-weight basis -- 'hot hemp' -- the disposal clock starts immediately: 90 days to destroy or convert the crop, with mandatory reporting to the state department of agriculture. That clock started the moment your cultivation management SaaS received the harvest record and queued the COA upload.

That's one of eight compliance clocks this guide covers for Cannabis and Hemp SaaS vendors. From FinCEN suspicious activity reports (SAR) to Metrc seed-to-sale tag reconciliation to FDA drug product distinction, cannabis-adjacent software companies operate under a compliance stack with no margin for error and no standard cloud automation playbook.

This article shows five production-ready n8n workflows with full JSON -- covering onboarding, deadline tracking, API health, incident response, and weekly KPIs -- built specifically for CannaTech and Hemp SaaS vendors.

Who this is for

The workflows cover seven customer tiers:

Tier Description
ENTERPRISE_CANNABIS_PLATFORM Multi-state operator ERP and inventory management
DISPENSARY_MANAGEMENT_SAAS Point-of-sale and inventory for retail dispensaries
CULTIVATOR_GROWER_SAAS Cultivation management, harvest tracking, yield analytics
HEMP_CBD_SAAS_VENDOR USDA hemp program management, COA tracking, FDA compliance
CANNABIS_BANKING_FINTECH FinCEN-compliant banking and financial services tools
STATE_SEED_TO_SALE_INTEGRATION Metrc / BioTrackTHC / MJ Platform API integration SaaS
CANNABISTECH_STARTUP Pre-revenue cannabis or hemp software companies

Compliance flags that drive workflow branching:

  • FINCEN_BSA_REQUIRED -- FinCEN 2014 Cannabis Guidance, BSA/AML program required
  • USDA_HEMP_PROGRAM_REGISTERED -- USDA AMS 7 CFR Part 990, COA testing obligation
  • FDA_DRUG_PRODUCT_RISK -- CBD disease claims risk product reclassification as drug (21 USC sec 321)
  • STATE_SEED_TO_SALE_REQUIRED -- Metrc/BioTrackTHC/MJ Platform API required by state license
  • CCPA_CONSUMER_DATA -- customer purchase history, medical card data, delivery addresses
  • FINCEN_SAR_REPORTER -- financial institution filing SARs on cannabis-related activity
  • FTC_HEALTH_CLAIMS_RISK -- hemp CBD health claims triggering FTC sec 5 deceptive practices risk

Workflow 1 -- Tier-Segmented Customer Onboarding Drip

Cannabis and hemp SaaS customers have wildly different compliance needs. A multi-state operator (MSO) needs FinCEN SAR routing documentation on Day 0. A hemp CBD vendor needs FDA Section 351 drug product distinction explained before they write their first marketing copy. A seed-to-sale integration vendor needs Metrc API credentials validated immediately.

This workflow routes each new customer to a compliance-specific Day 0 email based on their tier.

Trigger: Webhook from your CRM or billing system on new customer signup.
Key logic: Switch node routes on customer_tier. Each branch sends a tier-appropriate email with the regulatory context their team actually needs on Day 0 -- not a generic welcome.

{
  "name": "Cannabis/Hemp SaaS - Tier-Segmented Customer Onboarding Drip",
  "nodes": [
    {
      "id": "1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "cannabis-saas-onboarding",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "2",
      "name": "Extract Tier & Flags",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        300
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json;\nconst tier = d.customer_tier || 'CANNABISTECH_STARTUP';\nconst flags = d.compliance_flags || [];\nconst state = d.state_license || 'CA-2024-DCC-12345';\nconst company = d.company_name || 'Customer';\nconst email = d.contact_email || '';\nconst mrr = d.mrr_usd || 0;\nreturn [{json:{tier,state,company,email,mrr}}];"
      }
    },
    {
      "id": "3",
      "name": "Route by Tier",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3,
      "position": [
        640,
        300
      ],
      "parameters": {
        "mode": "rules",
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "a1",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "ENTERPRISE_CANNABIS_PLATFORM",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "enterprise"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "b1",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "HEMP_CBD_SAAS_VENDOR",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "hemp"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "c1",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "CANNABIS_BANKING_FINTECH",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "banking"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": true,
                  "leftValue": "",
                  "typeValidation": "strict"
                },
                "conditions": [
                  {
                    "id": "d1",
                    "leftValue": "={{ $json.tier }}",
                    "rightValue": "STATE_SEED_TO_SALE_INTEGRATION",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "renameOutput": true,
              "outputKey": "metrc"
            }
          ]
        }
      }
    },
    {
      "id": "4",
      "name": "Enterprise Day0 Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        860,
        160
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Welcome to FlowKit - Multi-State Cannabis Platform Onboarding",
        "message": "<p>Welcome to FlowKit, {{ $json.company }}.</p><p>Your enterprise cannabis platform configuration includes: FinCEN BSA/SAR API endpoints (30-day SAR clock, 15-day CTR), Metrc multi-state tag reconciliation, and seed-to-sale audit trail in Postgres INSERT-only tables.</p><p><strong>Priority Day 1:</strong> Confirm FinCEN BSA module endpoint and Metrc API credentials for {{ $json.state }}. SAR confidentiality under 31 USC sec 5318(g)(2) prohibits disclosure - workflow routing must stay in-boundary.</p>",
        "additionalFields": {
          "replyTo": "support@flowkitai.com"
        }
      }
    },
    {
      "id": "5",
      "name": "Hemp/CBD Day0 Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        860,
        300
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Welcome to FlowKit - USDA Hemp Program & FDA Compliance Configuration",
        "message": "<p>Welcome, {{ $json.company }}.</p><p>Your FlowKit configuration: USDA AMS 7 CFR Part 990 pre-harvest testing COA tracker (15-day testing window, 90-day disposal clock for hot hemp over 0.3% THC), FDA Section 351 drug product distinction monitor, and 21 CFR Part 111 dietary supplement quality system deadline tracker.</p><p><strong>Critical:</strong> Our FTC sec 5 health claim detection module flags at-risk content before it reaches your marketing channels.</p>"
      }
    },
    {
      "id": "6",
      "name": "Banking FinTech Day0 Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        860,
        440
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Welcome to FlowKit - FinCEN Cannabis Banking Compliance Suite",
        "message": "<p>Welcome, {{ $json.company }}.</p><p>Your FinCEN cannabis banking compliance configuration: SAR filing pipeline (30-day sec 1020.320 clock), CTR automation (15-day sec 1010.311), Suspicious Activity monitoring under FinCEN 2014 Cannabis Guidance, and BSA program evidence collection.</p><p><strong>SAR Confidentiality:</strong> 31 USC sec 5318(g)(2) makes SAR disclosure a federal crime. Your n8n instance processes all SAR routing in your private enclave.</p>"
      }
    },
    {
      "id": "7",
      "name": "Metrc Integration Day0 Email",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        860,
        580
      ],
      "parameters": {
        "sendTo": "={{ $json.email }}",
        "subject": "Welcome to FlowKit - State Seed-to-Sale Integration Suite",
        "message": "<p>Welcome, {{ $json.company }}.</p><p>Your seed-to-sale integration: Metrc API real-time tag reconciliation, BioTrackTHC webhook pipeline, transfer manifest validation, and state inventory reconciliation. Tag discrepancy alerts trigger within 60 seconds of Metrc API event.</p><p><strong>State: {{ $json.state }}</strong> - license suspension triggers on unresolved tag discrepancies.</p>"
      }
    },
    {
      "id": "8",
      "name": "Log to Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4,
      "position": [
        1100,
        300
      ],
      "parameters": {
        "operation": "appendOrUpdate",
        "documentId": "YOUR_SHEET_ID",
        "sheetName": "onboarding_log",
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "company": "={{ $json.company }}",
            "tier": "={{ $json.tier }}",
            "email": "={{ $json.email }}",
            "state": "={{ $json.state }}",
            "timestamp": "={{ $now.toISO() }}",
            "mrr_usd": "={{ $json.mrr }}"
          }
        },
        "matchingColumns": [
          "company"
        ]
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Extract Tier & Flags",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Tier & Flags": {
      "main": [
        [
          {
            "node": "Route by Tier",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by Tier": {
      "enterprise": [
        [
          {
            "node": "Enterprise Day0 Email",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "hemp": [
        [
          {
            "node": "Hemp/CBD Day0 Email",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "banking": [
        [
          {
            "node": "Banking FinTech Day0 Email",
            "type": "main",
            "index": 0
          }
        ]
      ],
      "metrc": [
        [
          {
            "node": "Metrc Integration Day0 Email",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Enterprise Day0 Email": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Hemp/CBD Day0 Email": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Banking FinTech Day0 Email": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Metrc Integration Day0 Email": {
      "main": [
        [
          {
            "node": "Log to Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why this matters: Cannabis SaaS customers churn when they feel the vendor doesn't understand their compliance environment. An onboarding email that references the SAR confidentiality statute (31 USC sec 5318(g)(2)) signals expertise.


Workflow 2 -- FinCEN BSA / USDA Hemp / FDA Compliance Deadline Monitor

The 12 compliance deadline types this workflow tracks:

Deadline Type Clock Risk
USDA_HEMP_COA_15DAY 7 CFR sec 990.70 -- test within 15 days pre-harvest Negligent violation; repeat = culpable = DEA referral
USDA_HEMP_HOT_DISPOSAL_90DAY Destroy/convert hot hemp within 90 days Continued possession of hot hemp = Schedule I
FINCEN_SAR_30DAY 31 CFR sec 1020.320 -- file within 30 days of detection FinCEN penalty; potential criminal liability
FINCEN_CTR_15DAY 31 CFR sec 1010.311 -- CTR within 15 business days BSA violation; $10K+ fine per missed filing
FINCEN_2014_BSA_ANNUAL_REVIEW FinCEN 2014 Cannabis Guidance Bank relationship risk; SAR escalation
STATE_METRC_MONTHLY_RECONCILIATION State seed-to-sale monthly reconciliation License suspension on discrepancy
STATE_CANNABIS_LICENSE_RENEWAL 90-day advance application required License lapse = shutdown
FDA_SUPPLEMENT_CGMP_ANNUAL 21 CFR Part 111 -- annual hemp/CBD cGMP audit Warning letter; consent decree risk
FTC_HEALTH_CLAIMS_REVIEW FTC sec 5 UDAP -- annual review of health claims $51,744/day per ongoing deceptive practice
CCPA_CONSUMER_REQUEST_45DAY CCPA sec 1798.130 -- 45-day response window $100-$750/consumer; AG enforcement
STATE_SEED_TO_SALE_QUARTERLY State quarterly compliance report License non-renewal risk
USDA_HEMP_PROGRAM_RENEWAL 7 CFR sec 990.3 -- annual plan renewal Program termination; hemp = unregistered marijuana
{
  "name": "FinCEN BSA / USDA Hemp / FDA Compliance Deadline Monitor",
  "nodes": [
    {
      "id": "1",
      "name": "Weekdays 8AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 8 * * 1-5"
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Query Compliance Deadlines",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        440,
        300
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT customer_id, company_name, compliance_type, deadline_date, state_license, contact_email, tier FROM compliance_deadlines WHERE deadline_date <= CURRENT_DATE + INTERVAL '120 days' AND resolved = false ORDER BY deadline_date ASC LIMIT 200"
      }
    },
    {
      "id": "3",
      "name": "Calculate Urgency",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        640,
        300
      ],
      "parameters": {
        "jsCode": "const now = new Date();\nconst deadlineMap = {\n  USDA_HEMP_COA_15DAY: 'USDA AMS 7 CFR sec 990.70 - pre-harvest testing within 15 days. Hot hemp (>0.3% THC) triggers 90-day disposal clock.',\n  USDA_HEMP_HOT_DISPOSAL_90DAY: 'USDA AMS 7 CFR sec 990.70 - destroy or convert hot hemp within 90 days of confirmation.',\n  FINCEN_SAR_30DAY: 'FinCEN 31 CFR sec 1020.320 - SAR filing within 30 days of suspicious activity detection.',\n  FINCEN_CTR_15DAY: 'FinCEN 31 CFR sec 1010.311 - CTR for cash transactions >$10K within 15 business days.',\n  FINCEN_2014_BSA_ANNUAL_REVIEW: 'FinCEN 2014 Cannabis Guidance - annual BSA program review.',\n  STATE_METRC_MONTHLY_RECONCILIATION: 'State seed-to-sale Metrc monthly inventory reconciliation - license suspension risk on discrepancy.',\n  STATE_CANNABIS_LICENSE_RENEWAL: 'State cannabis operator license renewal - 90-day advance application required.',\n  FDA_SUPPLEMENT_CGMP_ANNUAL: 'FDA 21 CFR Part 111 - annual dietary supplement cGMP audit for hemp CBD products.',\n  FTC_HEALTH_CLAIMS_REVIEW: 'FTC sec 5 UDAP - annual review of health claims across digital marketing channels.',\n  CCPA_CONSUMER_REQUEST_45DAY: 'CCPA sec 1798.130 - 45-day response window for consumer data requests.',\n  STATE_SEED_TO_SALE_QUARTERLY: 'State quarterly seed-to-sale compliance report submission.',\n  USDA_HEMP_PROGRAM_RENEWAL: 'USDA AMS 7 CFR sec 990.3 - annual hemp producer/processor plan renewal.'\n};\nconst results = [];\nfor (const item of $input.all()) {\n  const d = item.json;\n  const days = Math.floor((new Date(d.deadline_date) - now) / 86400000);\n  let urgency = 'NOTICE';\n  if (days < 0) urgency = 'OVERDUE';\n  else if (days <= 7) urgency = 'CRITICAL';\n  else if (days <= 21) urgency = 'URGENT';\n  else if (days <= 45) urgency = 'WARNING';\n  const note = deadlineMap[d.compliance_type] || d.compliance_type;\n  results.push({json:{...d, days_remaining: days, urgency, regulatory_note: note}});\n}\nreturn results.filter(r => ['OVERDUE','CRITICAL','URGENT','WARNING'].includes(r.json.urgency));"
      }
    },
    {
      "id": "4",
      "name": "IF Critical or Overdue",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        840,
        300
      ],
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "e1",
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "CRITICAL",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            },
            {
              "id": "e2",
              "leftValue": "={{ $json.urgency }}",
              "rightValue": "OVERDUE",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "or"
        }
      }
    },
    {
      "id": "5",
      "name": "Slack Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1040,
        200
      ],
      "parameters": {
        "channel": "#cannabis-compliance-ops",
        "text": "={{ $json.urgency }}: {{ $json.company_name }} - {{ $json.compliance_type }} due {{ $json.deadline_date }} ({{ $json.days_remaining }}d). {{ $json.regulatory_note }}"
      }
    },
    {
      "id": "6",
      "name": "Gmail to Compliance Contact",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        1040,
        400
      ],
      "parameters": {
        "sendTo": "={{ $json.contact_email }}",
        "subject": "={{ $json.urgency }}: {{ $json.compliance_type }} - {{ $json.days_remaining }} days remaining",
        "message": "<p>Compliance action required for {{ $json.company_name }} ({{ $json.state_license }}).</p><p><strong>Deadline:</strong> {{ $json.compliance_type }} - {{ $json.deadline_date }} ({{ $json.days_remaining }} days).</p><p><strong>Regulatory reference:</strong> {{ $json.regulatory_note }}</p><p>Log in to your FlowKit compliance dashboard to mark this resolved.</p>"
      }
    }
  ],
  "connections": {
    "Weekdays 8AM": {
      "main": [
        [
          {
            "node": "Query Compliance Deadlines",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Compliance Deadlines": {
      "main": [
        [
          {
            "node": "Calculate Urgency",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Calculate Urgency": {
      "main": [
        [
          {
            "node": "IF Critical or Overdue",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "IF Critical or Overdue": {
      "main": [
        [
          {
            "node": "Slack Alert",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Gmail to Compliance Contact",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 3 -- Cannabis Platform API & Seed-to-Sale Health Monitor

Five endpoints to monitor every 5 minutes:

Endpoint Compliance Risk on Failure
metrc_api Tag tracking stops -- discrepancies accumulate, license suspension risk
fincen_bsa_portal SAR/CTR filing pipeline blocked -- 30-day clock continues regardless
usda_hemp_portal COA upload and pre-harvest test tracking unavailable -- 15-day clock continues
state_license_api Real-time license status unknown -- cannot validate customer credentials
cannabis_erp_api Inventory sync, harvest records, and transfer manifests unavailable

Key logic: $getWorkflowStaticData tracks previous endpoint state to detect DOWN transitions and RECOVERED transitions -- alerts fire only on state change, not on every poll cycle.

{
  "name": "Cannabis Platform API & Seed-to-Sale Health Monitor",
  "nodes": [
    {
      "id": "1",
      "name": "Every 5 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 5
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Check Metrc API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        160
      ],
      "parameters": {
        "url": "https://api.metrc.com/health",
        "method": "GET",
        "options": {
          "timeout": 8000
        }
      }
    },
    {
      "id": "3",
      "name": "Check FinCEN BSA Portal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        260
      ],
      "parameters": {
        "url": "https://your-fincen-bsa-integration.internal/health",
        "method": "GET",
        "options": {
          "timeout": 8000
        }
      }
    },
    {
      "id": "4",
      "name": "Check USDA Hemp Portal",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        360
      ],
      "parameters": {
        "url": "https://hems.ams.usda.gov/api/health",
        "method": "GET",
        "options": {
          "timeout": 8000
        }
      }
    },
    {
      "id": "5",
      "name": "Check State License API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        460
      ],
      "parameters": {
        "url": "https://your-state-license-api.internal/health",
        "method": "GET",
        "options": {
          "timeout": 8000
        }
      }
    },
    {
      "id": "6",
      "name": "Check Cannabis ERP API",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        440,
        560
      ],
      "parameters": {
        "url": "https://your-cannabis-erp.internal/api/health",
        "method": "GET",
        "options": {
          "timeout": 8000
        }
      }
    },
    {
      "id": "7",
      "name": "Evaluate Health",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        360
      ],
      "parameters": {
        "jsCode": "const endpoints = [\n  {name:'metrc_api', code: $('Check Metrc API').first()?.json?.statusCode, note:'Metrc API DOWN - state seed-to-sale tag tracking stopped. License suspension risk if tag discrepancies accumulate undetected.'},\n  {name:'fincen_bsa_portal', code: $('Check FinCEN BSA Portal').first()?.json?.statusCode, note:'FinCEN BSA portal unreachable - SAR/CTR filing pipeline at risk. 30-day SAR clock continues regardless of API status.'},\n  {name:'usda_hemp_portal', code: $('Check USDA Hemp Portal').first()?.json?.statusCode, note:'USDA HEMS portal DOWN - COA upload and pre-harvest test tracking blocked. 15-day testing window does not pause.'},\n  {name:'state_license_api', code: $('Check State License API').first()?.json?.statusCode, note:'State license API unreachable - real-time license status validation unavailable.'},\n  {name:'cannabis_erp_api', code: $('Check Cannabis ERP API').first()?.json?.statusCode, note:'Cannabis ERP API DOWN - inventory sync, harvest records, and transfer manifests unavailable.'}\n];\nconst key = 'cannabis_monitor_v1';\nconst prev = $getWorkflowStaticData('global')[key] || {};\nconst alerts = [];\nconst now = new Date().toISOString();\nfor (const ep of endpoints) {\n  const ok = ep.code === 200;\n  const wasOk = prev[ep.name] !== false;\n  if (!ok && wasOk) alerts.push({...ep, event:'DOWN', ts: now});\n  else if (ok && !wasOk) alerts.push({...ep, event:'RECOVERED', ts: now});\n  prev[ep.name] = ok;\n}\n$getWorkflowStaticData('global')[key] = prev;\nreturn alerts.map(a => ({json: a}));"
      }
    },
    {
      "id": "8",
      "name": "Slack Ops Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        900,
        360
      ],
      "parameters": {
        "channel": "#cannabis-ops-alerts",
        "text": "={{ $json.event }}: {{ $json.name }} - {{ $json.note }} ({{ $json.ts }})"
      }
    }
  ],
  "connections": {
    "Every 5 Minutes": {
      "main": [
        [
          {
            "node": "Check Metrc API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check FinCEN BSA Portal",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check USDA Hemp Portal",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check State License API",
            "type": "main",
            "index": 0
          },
          {
            "node": "Check Cannabis ERP API",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Metrc API": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check FinCEN BSA Portal": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check USDA Hemp Portal": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check State License API": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check Cannabis ERP API": {
      "main": [
        [
          {
            "node": "Evaluate Health",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Evaluate Health": {
      "main": [
        [
          {
            "node": "Slack Ops Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Workflow 4 -- Cannabis Compliance Incident Alert Pipeline

Eight incident types with pre-wired clocks and regulatory notes:

Incident Type Clock Fastest Action
METRC_TAG_DISCREPANCY 4 hours before inspection cycle Resolve discrepancy or notify state regulator
FINCEN_SAR_TRIGGER 30 days (720h) -- 31 CFR sec 1020.320 File SAR; do NOT disclose to subject (31 USC sec 5318(g)(2))
FDA_DRUG_PRODUCT_CLAIM IMMEDIATE Remove claim; CBD + disease claim = drug requiring IND/NDA
USDA_HEMP_THC_VIOLATION IMMEDIATE -- 90-day disposal Report to state ag department; negligent vs culpable distinction
STATE_LICENSE_SUSPENSION_RISK 24 hours to submit cure Engage counsel; compile Metrc audit trail
FTC_HEALTH_CLAIM_COMPLAINT IMMEDIATE Engage counsel; remove claim; FTC penalty $51,744/day
STATE_SEED_TO_SALE_AUDIT 4 hours to pull records Assemble Metrc chain-of-custody and manifest documentation
CANNABIS_BANKING_SAR_ESCALATION 30 days -- coordinate with BSA officer Bank may exit relationship; do not disclose SAR
{
  "name": "Cannabis Compliance Incident Alert Pipeline",
  "nodes": [
    {
      "id": "1",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "httpMethod": "POST",
        "path": "cannabis-incident",
        "responseMode": "responseNode"
      }
    },
    {
      "id": "2",
      "name": "Classify Incident",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        440,
        300
      ],
      "parameters": {
        "jsCode": "const d = $input.first().json;\nconst type = d.incident_type || 'UNKNOWN';\nconst incidentMap = {\n  METRC_TAG_DISCREPANCY: {urgency:'CRITICAL', deadline_hours:4, clock:'State regulator may pull license on unresolved Metrc tag discrepancy. 4-hour window before scheduled inspection cycle.', channel:'#cannabis-compliance-critical'},\n  FINCEN_SAR_TRIGGER: {urgency:'URGENT', deadline_hours:720, clock:'FinCEN 31 CFR sec 1020.320 - SAR must be filed within 30 days (720h). SAR confidentiality: 31 USC sec 5318(g)(2) prohibits disclosure to subject.', channel:'#fincen-compliance'},\n  FDA_DRUG_PRODUCT_CLAIM: {urgency:'CRITICAL', deadline_hours:0, clock:'FDA detected disease-related claim - CBD product reclassifiable as drug under 21 USC sec 321(g)(1)(B). Triggers IND/NDA requirement. Remove claim IMMEDIATELY.', channel:'#fda-compliance-critical'},\n  USDA_HEMP_THC_VIOLATION: {urgency:'CRITICAL', deadline_hours:0, clock:'Hot hemp: THC >0.3% on dry-weight basis. USDA 7 CFR sec 990.70(b) - 90-day disposal clock. Report to state department of agriculture IMMEDIATELY.', channel:'#usda-hemp-critical'},\n  STATE_LICENSE_SUSPENSION_RISK: {urgency:'CRITICAL', deadline_hours:24, clock:'State cannabis authority flagged compliance gap. 24h to submit cure documentation before formal suspension proceedings.', channel:'#cannabis-compliance-critical'},\n  FTC_HEALTH_CLAIM_COMPLAINT: {urgency:'URGENT', deadline_hours:0, clock:'FTC sec 5 UDAP complaint filed. Engage counsel IMMEDIATELY. FTC penalty: up to $51,744/day per violation for ongoing deceptive practices.', channel:'#legal-fda-ftc'},\n  STATE_SEED_TO_SALE_AUDIT: {urgency:'URGENT', deadline_hours:4, clock:'State seed-to-sale audit initiated. 4h to pull Metrc chain-of-custody records and assemble manifest documentation.', channel:'#cannabis-compliance-critical'},\n  CANNABIS_BANKING_SAR_ESCALATION: {urgency:'URGENT', deadline_hours:720, clock:'Bank escalating cannabis SAR - 30-day FinCEN clock. Coordinate with BSA officer. Do NOT disclose SAR existence to customer (31 USC sec 5318(g)(2)).', channel:'#fincen-compliance'}\n};\nconst info = incidentMap[type] || {urgency:'WARNING', deadline_hours:168, clock:'Unknown incident type - review manually.', channel:'#cannabis-ops'};\nconst deadline_iso = new Date(Date.now() + info.deadline_hours*3600000).toISOString();\nreturn [{json:{...d,...info,incident_type:type,deadline_iso}}];"
      }
    },
    {
      "id": "3",
      "name": "Slack Critical Alert",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        700,
        200
      ],
      "parameters": {
        "channel": "={{ $json.channel }}",
        "text": "{{ $json.urgency }}: {{ $json.incident_type }} - {{ $json.clock }} Deadline: {{ $json.deadline_iso }}."
      }
    },
    {
      "id": "4",
      "name": "Postgres Audit Log",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        700,
        400
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "INSERT INTO cannabis_incident_log (incident_type, customer_id, urgency, clock_note, deadline_iso, received_at) VALUES ('{{ $json.incident_type }}', '{{ $json.customer_id }}', '{{ $json.urgency }}', '{{ $json.clock }}', '{{ $json.deadline_iso }}', NOW())"
      }
    }
  ],
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Classify Incident",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Classify Incident": {
      "main": [
        [
          {
            "node": "Slack Critical Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "Postgres Audit Log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

SAR confidentiality note: The workflow routes SAR-related incidents to #fincen-compliance -- a private channel -- because 31 USC sec 5318(g)(2) makes it a federal crime to tip off the subject of a suspicious activity report. Your n8n enclave keeps this routing entirely within your infrastructure.


Workflow 5 -- Weekly CannaTech Platform KPI Dashboard

Every Monday at 7:00 AM, pull from two Postgres queries -- platform metrics and compliance events -- merge, calculate WoW% deltas, and send to CEO and CCO/CISO.

KPIs tracked: Total MRR (WoW%), active customers, enterprise MSO count, hemp/CBD vendor count, new trials, Metrc discrepancies (7d), FinCEN SAR triggers (7d), FDA claims detected (7d), overdue compliance deadlines (7d).

Flags: FDA DRUG PRODUCT CLAIM DETECTED, METRC DISCREPANCY VOLUME ELEVATED, OVERDUE COMPLIANCE DEADLINES -- flagged automatically when thresholds exceed preset values.

{
  "name": "Weekly CannaTech Platform KPI Dashboard",
  "nodes": [
    {
      "id": "1",
      "name": "Monday 7AM",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        300
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 7 * * 1"
            }
          ]
        }
      }
    },
    {
      "id": "2",
      "name": "Query Platform Metrics",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        440,
        200
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(DISTINCT customer_id) AS active_customers, SUM(mrr_usd) AS total_mrr, COUNT(DISTINCT CASE WHEN tier='ENTERPRISE_CANNABIS_PLATFORM' THEN customer_id END) AS enterprise_count, COUNT(DISTINCT CASE WHEN tier='HEMP_CBD_SAAS_VENDOR' THEN customer_id END) AS hemp_count, COUNT(DISTINCT CASE WHEN trial_start > NOW()-INTERVAL '7 days' THEN customer_id END) AS new_trials FROM customer_accounts WHERE active = true"
      }
    },
    {
      "id": "3",
      "name": "Query Compliance Events",
      "type": "n8n-nodes-base.postgres",
      "typeVersion": 2.5,
      "position": [
        440,
        400
      ],
      "parameters": {
        "operation": "executeQuery",
        "query": "SELECT COUNT(*) FILTER (WHERE incident_type='METRC_TAG_DISCREPANCY' AND received_at > NOW()-INTERVAL '7 days') AS metrc_7d, COUNT(*) FILTER (WHERE incident_type='FINCEN_SAR_TRIGGER' AND received_at > NOW()-INTERVAL '7 days') AS sar_7d, COUNT(*) FILTER (WHERE incident_type='FDA_DRUG_PRODUCT_CLAIM' AND received_at > NOW()-INTERVAL '7 days') AS fda_7d, COUNT(*) FILTER (WHERE urgency='OVERDUE' AND received_at > NOW()-INTERVAL '7 days') AS overdue_7d FROM cannabis_incident_log"
      }
    },
    {
      "id": "4",
      "name": "Build KPI Report",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        680,
        300
      ],
      "parameters": {
        "jsCode": "const m = $('Query Platform Metrics').first().json;\nconst c = $('Query Compliance Events').first().json;\nconst mrr = Number(m.total_mrr||0);\nconst prev = $getWorkflowStaticData('global')['prev_mrr_cannabis'] || mrr;\nconst mrrWoW = prev > 0 ? ((mrr-prev)/prev*100).toFixed(1) : '0.0';\n$getWorkflowStaticData('global')['prev_mrr_cannabis'] = mrr;\nconst flags = [];\nif (Number(c.fda_7d) > 0) flags.push('FDA DRUG PRODUCT CLAIM DETECTED');\nif (Number(c.metrc_7d) > 2) flags.push('METRC DISCREPANCY VOLUME ELEVATED');\nif (Number(c.overdue_7d) > 0) flags.push('OVERDUE COMPLIANCE DEADLINES');\nconst html = '<h2>CannaTech Platform - Weekly KPI Report</h2><table border=\"1\" cellpadding=\"6\"><tr><th>Metric</th><th>Value</th></tr><tr><td>Total MRR</td><td>$'+mrr.toLocaleString()+' ('+mrrWoW+'% WoW)</td></tr><tr><td>Active Customers</td><td>'+m.active_customers+'</td></tr><tr><td>Enterprise MSO</td><td>'+m.enterprise_count+'</td></tr><tr><td>Hemp/CBD Vendors</td><td>'+m.hemp_count+'</td></tr><tr><td>New Trials (7d)</td><td>'+m.new_trials+'</td></tr><tr><td>Metrc Discrepancies (7d)</td><td>'+c.metrc_7d+'</td></tr><tr><td>FinCEN SAR Triggers (7d)</td><td>'+c.sar_7d+'</td></tr><tr><td>FDA Claims Detected (7d)</td><td>'+c.fda_7d+'</td></tr><tr><td>Overdue Deadlines (7d)</td><td>'+c.overdue_7d+'</td></tr></table>'+(flags.length ? '<p><strong>FLAGS: '+flags.join(' | ')+'</strong></p>' : '');\nreturn [{json:{html, mrr, mrrWoW, flags}}];"
      }
    },
    {
      "id": "5",
      "name": "Gmail CEO",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2,
      "position": [
        900,
        200
      ],
      "parameters": {
        "sendTo": "ceo@yourcompany.com",
        "additionalFields": {
          "bcc": "cco@yourcompany.com,ciso@yourcompany.com"
        },
        "subject": "[CannaTech Weekly] MRR ${{ $json.mrr }} | {{ $json.flags.length }} flags",
        "message": "={{ $json.html }}"
      }
    },
    {
      "id": "6",
      "name": "Slack Summary",
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        900,
        400
      ],
      "parameters": {
        "channel": "#management",
        "text": "CannaTech Weekly: MRR ${{ $json.mrr }} ({{ $json.mrrWoW }}% WoW) | Flags: {{ $json.flags.join(', ') || 'none' }}"
      }
    }
  ],
  "connections": {
    "Monday 7AM": {
      "main": [
        [
          {
            "node": "Query Platform Metrics",
            "type": "main",
            "index": 0
          },
          {
            "node": "Query Compliance Events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Platform Metrics": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Query Compliance Events": {
      "main": [
        [
          {
            "node": "Build KPI Report",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build KPI Report": {
      "main": [
        [
          {
            "node": "Gmail CEO",
            "type": "main",
            "index": 0
          },
          {
            "node": "Slack Summary",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Enter fullscreen mode Exit fullscreen mode

Why self-hosted n8n is the only defensible option for cannabis SaaS vendors

FinCEN SAR confidentiality (31 USC sec 5318(g)(2)): SARs are law enforcement sensitive instruments. Routing SAR trigger events through Zapier or Make means the cloud vendor processes data about a suspicious activity report -- a potential disclosure that compounds the compliance exposure. Self-hosted n8n processes the entire SAR routing pipeline in your private enclave.

State seed-to-sale audit trail: Metrc API event data flowing through a shared cloud automation platform creates a gap in the chain-of-custody record. State regulators reviewing your Metrc audit trail for a license renewal or compliance inspection will ask who else touched the tag data.

USDA hemp COA chain-of-custody: The 7 CFR Part 990 COA requirement is a traceability instrument. Testing lab data flowing through Zapier introduces an unaudited processor into the chain. USDA inspectors reviewing a COA dispute will follow the data flow.

FinCEN 2014 Cannabis Guidance BSA program: Banks that serve cannabis-related businesses (CRBs) must file enhanced SARs with FinCEN. If your SaaS platform processes financial transaction data for these banks, and that data flows through a cloud automation vendor, the bank's BSA auditor will ask whether the automation vendor is covered by the program's data controls.

FDA Part 11 / electronic records (21 CFR Part 11): Hemp supplement manufacturers subject to 21 CFR Part 111 cGMP must maintain electronic records with audit trails. A workflow automation vendor in the chain of your quality records is an additional validated system in any FDA inspection package.


n8n vs Zapier/Make for CannaTech SaaS

Requirement n8n (self-hosted) Zapier / Make
SAR confidentiality (31 USC sec 5318(g)) All routing in your enclave Third-party cloud processes SAR data
Metrc audit trail chain-of-custody Complete in your Postgres Gap in chain -- unaudited processor
USDA COA traceability End-to-end in your infra Cloud vendor in COA data chain
State seed-to-sale compliance Audit trail in Postgres INSERT-only No audit log; data egress
FinCEN BSA program scope n8n inside program boundary Additional unscoped processor
FDA Part 11 validation One validated system Additional system to validate
Metrc API credential security Credentials in your secrets manager Credentials in Zapier/Make cloud

How to use these workflows

  1. Import each JSON block into n8n via Workflows -> Import from file.
  2. Replace placeholder values: Postgres connection, Google Sheets ID, Slack channel names, Gmail credentials, Metrc API credentials.
  3. Deploy your self-hosted n8n instance inside your compliance boundary -- AWS GovCloud, Azure Government, or on-prem with network isolation.
  4. Add your customers to the compliance_deadlines and customer_accounts Postgres tables.

These workflows are functional starting points. Adapt the regulatory citations and deadline windows to your specific state licensure requirements -- cannabis compliance varies significantly by jurisdiction.

The full n8n workflow collection for SaaS vendors is at stripeai.gumroad.com.

Top comments (0)