The FDA 15-day adverse event clock starts the moment your platform receives the first credible report of a serious unexpected reaction — not when your QA team reviews it, not when a ticket is assigned, and not when your cloud automation vendor processes the webhook.
21 CFR §312.32(c)(1)(i) is calendar days. No weekends excluded. No processing time subtracted.
If your pharmacovigilance automation routes AE data through a cloud iPaaS node that queues, delays, or fails silently, that clock is running while you wait for a retry.
This is the architecture problem BioTech and PharmaRegulatoryTech SaaS vendors face: the fastest regulatory clock in life sciences runs through your automation layer. And if that layer is a shared cloud service with no GxP validation, no 21 CFR Part 11 audit trail, and no DEA-auditable data custody record, you have a procurement-level compliance gap — not just a security risk.
Below are five import-ready n8n workflow templates built specifically for BioTech and PharmaRegulatoryTech SaaS vendors, with full JSON, FDA/ICH/DEA/EMA compliance annotations, and the self-hosting rationale your enterprise pharma customers will ask for in procurement.
Why BioTech and PharmaRegulatoryTech SaaS Vendors Can't Use Generic Cloud iPaaS
| Regulatory Risk | Citation | Why Cloud iPaaS Creates the Problem |
|---|---|---|
| 21 CFR Part 11 audit trail gap | FDA 21 CFR §11.10(a)(e) | Cloud iPaaS nodes touching eCTD/IND/NDA e-records are in the Part 11 boundary but not in your validated GxP system |
| ICH GCP E6(R2) TMF inspection-readiness | ICH E6(R2) §5.18.7 | TMF metadata routed through cloud vendor = undocumented third-party access in your TMF inspection record |
| DEA CSA Schedule II-V data custody | DEA 21 CFR §1301 | CS ordering/CSOS data in cloud = potential §827 record-keeping gap |
| FDA Part 820 QSR SaMD validation | 21 CFR Part 820 | DHF/CAPA workflows through unvalidated cloud = undocumented system in Design Controls |
| HIPAA PHI — clinical trial participants | HIPAA §164.308(b) | Each cloud iPaaS node routing clinical trial PHI is a Business Associate without a separate BAA |
The 7 BioTech/Pharma SaaS Customer Tiers
| Tier | Who They Are | Key Compliance Flags |
|---|---|---|
| BIOTECH_SAAS_VENDOR | Software for biotech R&D — IND management, lab automation, genomics platforms | FDA_21_CFR_PART_11_SUBJECT, ICH_GCP_E6_APPLICABLE |
| CLINICAL_TRIAL_MANAGEMENT_SAAS | CTMS, EDC, eTMF, IWRS, RTSM vendors | ICH_GCP_E6_APPLICABLE, HIPAA_PHI_CLINICAL_TRIALS, FDA_21_CFR_PART_11_SUBJECT |
| REGULATORY_AFFAIRS_SAAS | eCTD submission management, dossier trackers, regulatory intelligence | FDA_21_CFR_PART_11_SUBJECT, EMA_IMPD_REQUIRED |
| PHARMA_MANUFACTURING_SAAS | MES, QMS, batch record systems for pharma manufacturing | FDA_PART_820_QSR_SAMD, FDA_21_CFR_PART_11_SUBJECT |
| LAB_INFORMATICS_SAAS | LIMS, ELN, instrument data management for pharma/biotech labs | FDA_GLP_21_CFR_58_SUBJECT, FDA_21_CFR_PART_11_SUBJECT |
| PHARMACOVIGILANCE_SAAS | ADR/AE reporting, ICSR management, signal detection | ICH_GCP_E6_APPLICABLE, HIPAA_PHI_CLINICAL_TRIALS, FDA_21_CFR_PART_11_SUBJECT |
| PHARMATECH_STARTUP | Early-stage pharma/biotech software startups, pre-IND, pre-commercial | SOC2_REQUIRED |
Workflow 1: Tiered Onboarding Drip — 7 Pharma/BioTech Customer Segments
Each tier gets a Day 0 regulatory brief specific to their FDA/ICH/DEA exposure, followed by use-case emails and a trial-ending offer.
{
"name": "BioTech/Pharma SaaS \u2014 Tiered Onboarding Drip",
"nodes": [
{
"id": "1",
"name": "Webhook \u2014 trial_started",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
260,
300
],
"parameters": {
"path": "pharma-trial-started",
"responseMode": "onReceived",
"responseData": ""
}
},
{
"id": "2",
"name": "Log to Sheets",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 1,
"position": [
460,
300
],
"parameters": {
"operation": "append",
"sheetId": "SHEET_ID",
"range": "Trials!A:H",
"values": {
"values": [
"={{$json.customer_id}}",
"={{$json.tier}}",
"={{$json.email}}",
"={{$json.company}}",
"={{$json.compliance_flags}}",
"={{ $now }}",
"=active",
""
]
}
}
},
{
"id": "3",
"name": "Day 0 \u2014 Welcome + Tier Brief",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
660,
300
],
"parameters": {
"operation": "send",
"to": "={{$node['Webhook \u2014 trial_started'].json.email}}",
"subject": "Welcome to FlowKit \u2014 your {{$node['Webhook \u2014 trial_started'].json.tier}} setup brief",
"message": "=<p>Hi {{$node['Webhook \u2014 trial_started'].json.first_name}},</p><p>Welcome to FlowKit for <strong>{{$node['Webhook \u2014 trial_started'].json.tier}}</strong>.</p>{{$node['Code \u2014 Tier Brief'].json.brief_html}}<p>Your trial runs 14 days. Reply to this email with any questions.</p><p>\u2014 Alex, FlowKit</p>"
}
},
{
"id": "4",
"name": "Code \u2014 Tier Brief",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
560,
200
],
"parameters": {
"jsCode": "const tier = $input.first().json.tier;\nconst flags = ($input.first().json.compliance_flags || '').split(',');\nconst briefs = {\n BIOTECH_SAAS_VENDOR: '<p><strong>FDA 21 CFR Part 11 note:</strong> Your customers route IND/NDA electronic records through your platform. Every automation node that touches those records is in your Part 11 audit trail boundary. Self-hosted n8n keeps the audit log inside your infrastructure \u2014 cloud iPaaS creates a gap a 483 Observation can cite.</p>',\n CLINICAL_TRIAL_MANAGEMENT_SAAS: '<p><strong>ICH GCP E6(R2) \u00a75.18.7 note:</strong> Trial Master File must be inspection-ready at all times. CTMS automation routing TMF metadata through a cloud vendor = undocumented third-party access in your inspection record. Self-hosted n8n = TMF data never leaves your validated environment.</p>',\n REGULATORY_AFFAIRS_SAAS: '<p><strong>eCTD/IND/NDA data note:</strong> FDA eCTD submissions contain CCI (commercially confidential information). Cloud iPaaS processing = data egress outside your controlled environment. 21 CFR Part 11 audit trail requirement extends to the automation layer.</p>',\n PHARMA_MANUFACTURING_SAAS: '<p><strong>21 CFR Part 11 + Part 820 note:</strong> Manufacturing batch records and deviation reports are Part 11 records. Routing them through Zapier/Make = undocumented system in your validation package. FDA inspectors ask for complete data flow maps \u2014 cloud automation vendors are not on them.</p>',\n LAB_INFORMATICS_SAAS: '<p><strong>FDA 21 CFR Part 58 GLP note:</strong> Lab data integrity requires complete audit trail from raw instrument output to final report. LIMS automation through cloud iPaaS = data egress gap in your GLP audit trail. Self-hosted n8n = audit trail stays in your validated system boundary.</p>',\n PHARMACOVIGILANCE_SAAS: '<p><strong>FDA \u00a7312.32 15-day clock note:</strong> Serious unexpected adverse reactions require MedWatch submission within 15 calendar days of first receipt \u2014 not 15 business days. Every hour of automation downtime or data-routing failure counts. Cloud iPaaS SLA gaps create regulatory exposure your pharma customers cannot absorb.</p>',\n PHARMATECH_STARTUP: '<p><strong>Getting started:</strong> Import the onboarding drip workflow, connect your CRM webhook, and configure Gmail. Your first automated sequence will be live in under 30 minutes.</p>'\n};\nconst brief = briefs[tier] || briefs['PHARMATECH_STARTUP'];\nreturn [{ json: { brief_html: brief } }];"
}
},
{
"id": "5",
"name": "Wait 3 days",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
860,
300
],
"parameters": {
"unit": "days",
"amount": 3
}
},
{
"id": "6",
"name": "Day 4 \u2014 Use-Case Deep Dive",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
1060,
300
],
"parameters": {
"operation": "send",
"to": "={{$node['Webhook \u2014 trial_started'].json.email}}",
"subject": "3 workflows your {{$node['Webhook \u2014 trial_started'].json.tier}} team uses most",
"message": "=<p>Hi {{$node['Webhook \u2014 trial_started'].json.first_name}},</p><p>Here are the three FlowKit workflows most popular with <strong>{{$node['Webhook \u2014 trial_started'].json.tier}}</strong> teams:</p><ul><li>Adverse Event / 15-Day MedWatch Pipeline</li><li>FDA/ICH/DEA Regulatory Deadline Tracker</li><li>Weekly Clinical KPI Dashboard</li></ul><p>Each is import-ready JSON \u2014 15 minutes to production.</p><p>\u2014 Alex</p>"
}
},
{
"id": "7",
"name": "Wait 4 days",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
1260,
300
],
"parameters": {
"unit": "days",
"amount": 4
}
},
{
"id": "8",
"name": "Day 8 \u2014 Social Proof",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
1460,
300
],
"parameters": {
"operation": "send",
"to": "={{$node['Webhook \u2014 trial_started'].json.email}}",
"subject": "How a CTMS vendor cut their audit-prep time by 60%",
"message": "=<p>Hi {{$node['Webhook \u2014 trial_started'].json.first_name}},</p><p>A clinical trial management SaaS customer used FlowKit's FDA deadline tracker to replace a spreadsheet-based process their QA team spent 4 hours/week maintaining.</p><p>Result: audit prep dropped from a full day to 90 minutes. The tracker now fires Slack alerts 30/14/7 days before every FDA, ICH, and DEA deadline \u2014 automatically.</p><p>Want to see their exact workflow? Reply and I'll send the JSON.</p><p>\u2014 Alex</p>"
}
},
{
"id": "9",
"name": "Wait 3 days",
"type": "n8n-nodes-base.wait",
"typeVersion": 1,
"position": [
1660,
300
],
"parameters": {
"unit": "days",
"amount": 3
}
},
{
"id": "10",
"name": "Day 11 \u2014 Trial Ending Offer",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
1860,
300
],
"parameters": {
"operation": "send",
"to": "={{$node['Webhook \u2014 trial_started'].json.email}}",
"subject": "Your FlowKit trial ends in 3 days \u2014 20% off today",
"message": "=<p>Hi {{$node['Webhook \u2014 trial_started'].json.first_name}},</p><p>Your 14-day trial ends in 3 days. Use <strong>PHARMA20</strong> for 20% off any plan before it expires.</p><p>Get the complete bundle \u2014 all 15 n8n workflow templates \u2014 at <a href='https://stripeai.gumroad.com'>stripeai.gumroad.com</a>.</p><p>\u2014 Alex</p>"
}
}
],
"connections": {
"Webhook \u2014 trial_started": {
"main": [
[
{
"node": "Log to Sheets",
"type": "main",
"index": 0
},
{
"node": "Code \u2014 Tier Brief",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Tier Brief": {
"main": [
[
{
"node": "Day 0 \u2014 Welcome + Tier Brief",
"type": "main",
"index": 0
}
]
]
},
"Day 0 \u2014 Welcome + Tier Brief": {
"main": [
[
{
"node": "Wait 3 days",
"type": "main",
"index": 0
}
]
]
},
"Wait 3 days": {
"main": [
[
{
"node": "Day 4 \u2014 Use-Case Deep Dive",
"type": "main",
"index": 0
}
]
]
},
"Day 4 \u2014 Use-Case Deep Dive": {
"main": [
[
{
"node": "Wait 4 days",
"type": "main",
"index": 0
}
]
]
},
"Wait 4 days": {
"main": [
[
{
"node": "Day 8 \u2014 Social Proof",
"type": "main",
"index": 0
}
]
]
},
"Day 8 \u2014 Social Proof": {
"main": [
[
{
"node": "Wait 3 days (2)",
"type": "main",
"index": 0
}
]
]
},
"Wait 3 days (2)": {
"main": [
[
{
"node": "Day 11 \u2014 Trial Ending Offer",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 2: FDA / ICH / DEA Regulatory Deadline Tracker
Runs every morning. Pulls deadlines from a Google Sheet. Classifies each by urgency tier. Routes CRITICAL and OVERDUE to #compliance-critical Slack + owner email. Lower-priority to #compliance channel.
Deadlines tracked: FDA 15-day serious AE MedWatch, IND Annual Safety Report, IND 60-day safety, DEA 224 renewal, NDA PDUFA clock, FDA 510(k), ICH GCP annual audit, EMA IMPD annual update, 21 CFR Part 11 system validation, GLP study completion reports.
Fastest clock: FDA Serious Unexpected AE — 15 calendar days (21 CFR §312.32(c)(1)(i)).
{
"name": "FDA / ICH / DEA Regulatory Deadline Tracker",
"nodes": [
{
"id": "1",
"name": "Daily 8 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
260,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * *"
}
]
}
}
},
{
"id": "2",
"name": "Read regulatory_deadlines Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 1,
"position": [
460,
300
],
"parameters": {
"operation": "read",
"sheetId": "SHEET_ID",
"range": "deadlines!A:F",
"options": {}
}
},
{
"id": "3",
"name": "Code \u2014 Classify Deadlines",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
660,
300
],
"parameters": {
"jsCode": "const today = new Date();\nconst items = $input.all();\nconst DEADLINE_DEFS = {\n FDA_SAER_15_DAY: { label: 'FDA Serious AE \u2014 15-day MedWatch', days: 15, regs: '21 CFR \u00a7312.32(c)', severity: 'CRITICAL' },\n IND_ANNUAL_SAFETY_REPORT: { label: 'IND Annual Safety Report', days: 365, regs: '21 CFR \u00a7312.33(b)', severity: 'WARNING' },\n FDA_IND_60_DAY_SAFETY: { label: 'IND 60-Day Safety Report', days: 60, regs: '21 CFR \u00a7312.32(c)(1)(i)', severity: 'URGENT' },\n DEA_224_RENEWAL: { label: 'DEA Form 224 Annual Renewal', days: 365, regs: 'DEA CSA 21 CFR \u00a71301.35', severity: 'WARNING' },\n FDA_NDA_PDUFA: { label: 'NDA PDUFA Review Clock', days: 300, regs: '21 USC \u00a7379h PDUFA', severity: 'NOTICE' },\n FDA_510K_PREMARKET: { label: 'FDA 510(k) Premarket Submission', days: 90, regs: '21 CFR \u00a7807.87', severity: 'URGENT' },\n ICH_GCP_ANNUAL_AUDIT: { label: 'ICH GCP Annual Site Audit', days: 365, regs: 'ICH E6(R2) \u00a75.1.3', severity: 'WARNING' },\n EMA_IMPD_ANNUAL: { label: 'EMA IMPD Annual Update', days: 365, regs: 'EMA/CHMP/QWP/185401/2004', severity: 'WARNING' },\n FDA_21_CFR_PART_11_VALIDATION: { label: '21 CFR Part 11 System Validation', days: 365, regs: '21 CFR \u00a711.10(a)', severity: 'WARNING' },\n FDA_GLP_STUDY_COMPLETION: { label: 'FDA GLP Study Completion Report', days: 90, regs: '21 CFR \u00a758.185', severity: 'URGENT' },\n SOC2_TYPE2_RENEWAL: { label: 'SOC2 Type II Renewal', days: 365, regs: 'AICPA TSC', severity: 'NOTICE' },\n ANNUAL_PENTEST: { label: 'Annual Penetration Test', days: 365, regs: 'Internal policy', severity: 'NOTICE' }\n};\nconst alerts = [];\nfor (const item of items) {\n const d = item.json;\n const def = DEADLINE_DEFS[d.deadline_type];\n if (!def) continue;\n const due = new Date(d.due_date);\n const daysLeft = Math.ceil((due - today) / 86400000);\n let tier = null;\n if (daysLeft < 0) tier = 'OVERDUE';\n else if (daysLeft <= 3) tier = 'CRITICAL';\n else if (daysLeft <= 7) tier = 'URGENT';\n else if (daysLeft <= 14) tier = 'WARNING';\n else if (daysLeft <= 30) tier = 'NOTICE';\n if (tier) alerts.push({ ...d, daysLeft, tier, label: def.label, regs: def.regs });\n}\nreturn alerts.map(a => ({ json: a }));"
}
},
{
"id": "4",
"name": "IF \u2014 CRITICAL or OVERDUE",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
860,
200
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json.tier}}",
"operation": "regex",
"value2": "CRITICAL|OVERDUE"
}
]
}
}
},
{
"id": "5",
"name": "Slack #compliance-critical",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
1060,
160
],
"parameters": {
"channel": "#compliance-critical",
"text": "=:rotating_light: *{{$json.tier}}: {{$json.label}}* ({{$json.daysLeft}}d)\nCustomer: {{$json.customer_name}} | Regs: {{$json.regs}}\nDue: {{$json.due_date}} | Assigned: {{$json.owner_email}}"
}
},
{
"id": "6",
"name": "Gmail \u2014 owner",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
1060,
280
],
"parameters": {
"operation": "send",
"to": "={{$json.owner_email}}",
"subject": "={{$json.tier}}: {{$json.label}} \u2014 {{$json.daysLeft}} days",
"message": "=<p>Regulatory deadline alert: <strong>{{$json.label}}</strong></p><p>Customer: {{$json.customer_name}}<br>Due: {{$json.due_date}} ({{$json.daysLeft}} days)<br>Regulation: {{$json.regs}}</p>"
}
},
{
"id": "7",
"name": "Slack #compliance (lower priority)",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
1060,
400
],
"parameters": {
"channel": "#compliance",
"text": "={{$json.tier}}: {{$json.label}} \u2014 {{$json.daysLeft}}d | {{$json.customer_name}} | {{$json.regs}}"
}
}
],
"connections": {
"Daily 8 AM": {
"main": [
[
{
"node": "Read regulatory_deadlines Sheet",
"type": "main",
"index": 0
}
]
]
},
"Read regulatory_deadlines Sheet": {
"main": [
[
{
"node": "Code \u2014 Classify Deadlines",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Classify Deadlines": {
"main": [
[
{
"node": "IF \u2014 CRITICAL or OVERDUE",
"type": "main",
"index": 0
}
]
]
},
"IF \u2014 CRITICAL or OVERDUE": {
"main": [
[
{
"node": "Slack #compliance-critical",
"type": "main",
"index": 0
},
{
"node": "Gmail \u2014 owner",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack #compliance (lower priority)",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 3: Clinical / Regulatory API Health Monitor — 21 CFR Part 11
Every 15 minutes. Checks five regulated API endpoints with their compliance annotation. If any return non-200 or timeout, fires #pharma-ops-critical Slack with @oncall tag and the specific regulatory exposure (e.g., '15-day MedWatch clock' for the AE API).
Endpoints: eCTD submission API (Part 11 audit trail), adverse event API (§312.32(c) 15-day clock), LIMS integration API (Part 58 GLP), DEA CSOS API (§1305 CS ordering records), pharmacovigilance API (ICH E2B(R3) ICSRs).
{
"name": "Clinical / Regulatory API Health Monitor \u2014 21 CFR Part 11",
"nodes": [
{
"id": "1",
"name": "Every 15 min",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
260,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "*/15 * * * *"
}
]
}
}
},
{
"id": "2",
"name": "HTTP \u2014 eCTD submission API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
460,
160
],
"parameters": {
"url": "={{$env.ECTD_API}}/health",
"method": "GET",
"timeout": 10000,
"continueOnFail": true
}
},
{
"id": "3",
"name": "HTTP \u2014 adverse event API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
460,
260
],
"parameters": {
"url": "={{$env.AE_API}}/health",
"method": "GET",
"timeout": 10000,
"continueOnFail": true
}
},
{
"id": "4",
"name": "HTTP \u2014 LIMS integration API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
460,
360
],
"parameters": {
"url": "={{$env.LIMS_API}}/health",
"method": "GET",
"timeout": 10000,
"continueOnFail": true
}
},
{
"id": "5",
"name": "HTTP \u2014 DEA CSOS API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
460,
460
],
"parameters": {
"url": "={{$env.DEA_CSOS_API}}/health",
"method": "GET",
"timeout": 10000,
"continueOnFail": true
}
},
{
"id": "6",
"name": "HTTP \u2014 pharmacovigilance API",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 1,
"position": [
460,
560
],
"parameters": {
"url": "={{$env.PV_API}}/health",
"method": "GET",
"timeout": 10000,
"continueOnFail": true
}
},
{
"id": "7",
"name": "Code \u2014 Evaluate Health",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
700,
360
],
"parameters": {
"jsCode": "const endpoints = [\n { name: 'eCTD Submission API', node: 'HTTP \u2014 eCTD submission API', reg: '21 CFR Part 11 \u2014 e-record audit trail' },\n { name: 'Adverse Event API', node: 'HTTP \u2014 adverse event API', reg: '21 CFR \u00a7312.32(c) \u2014 15-day MedWatch clock' },\n { name: 'LIMS Integration API', node: 'HTTP \u2014 LIMS integration API', reg: '21 CFR Part 58 GLP \u2014 lab data integrity' },\n { name: 'DEA CSOS API', node: 'HTTP \u2014 DEA CSOS API', reg: 'DEA 21 CFR \u00a71305 \u2014 CS ordering records' },\n { name: 'Pharmacovigilance API', node: 'HTTP \u2014 pharmacovigilance API', reg: 'ICH E2B(R3) \u2014 individual case safety reports' }\n];\nconst down = [];\nfor (const ep of endpoints) {\n const r = $node[ep.node]?.json;\n if (!r || r.statusCode !== 200 || r.error) {\n down.push({ endpoint: ep.name, reg: ep.reg, error: r?.error || 'non-200' });\n }\n}\nif (down.length === 0) return [{ json: { status: 'all_healthy', checked: endpoints.length, ts: new Date().toISOString() } }];\nreturn down.map(d => ({ json: { status: 'down', ...d, ts: new Date().toISOString() } }));"
}
},
{
"id": "8",
"name": "IF \u2014 Any Down",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
900,
360
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json.status}}",
"operation": "equal",
"value2": "down"
}
]
}
}
},
{
"id": "9",
"name": "Slack #pharma-ops-critical",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
1100,
280
],
"parameters": {
"channel": "#pharma-ops-critical",
"text": "=:red_circle: *API DOWN: {{$json.endpoint}}*\nReg exposure: {{$json.reg}}\nError: {{$json.error}} | {{$json.ts}}\n@oncall \u2014 acknowledge within 15 min"
}
}
],
"connections": {
"Every 15 min": {
"main": [
[
{
"node": "HTTP \u2014 eCTD submission API",
"type": "main",
"index": 0
},
{
"node": "HTTP \u2014 adverse event API",
"type": "main",
"index": 0
},
{
"node": "HTTP \u2014 LIMS integration API",
"type": "main",
"index": 0
},
{
"node": "HTTP \u2014 DEA CSOS API",
"type": "main",
"index": 0
},
{
"node": "HTTP \u2014 pharmacovigilance API",
"type": "main",
"index": 0
}
]
]
},
"HTTP \u2014 eCTD submission API": {
"main": [
[
{
"node": "Code \u2014 Evaluate Health",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Evaluate Health": {
"main": [
[
{
"node": "IF \u2014 Any Down",
"type": "main",
"index": 0
}
]
]
},
"IF \u2014 Any Down": {
"main": [
[
{
"node": "Slack #pharma-ops-critical",
"type": "main",
"index": 0
}
],
[]
]
}
}
}
Workflow 4: Adverse Event — FDA 15-Day MedWatch Pipeline (21 CFR §312.32)
Triggered by webhook on ae_reported event. Classifies the AE type and calculates the submission deadline from the moment of first receipt. CRITICAL/URGENT types (Serious Unexpected AE, Fatal/Life-Threatening, CIOMS I, EMA SUSAR, DEA theft) go to #pharmacovigilance-alert Slack + PV team email + Google Sheets AE log immediately.
AE types with deadlines:
- Serious Unexpected AE: 15 calendar days (21 CFR §312.32(c)(1)(i)) — MedWatch FDA-3500A
- Fatal/Life-Threatening Unexpected: 7 calendar days + telephone first (21 CFR §312.32(c)(2))
- CIOMS I international: 15 calendar days (ICH E2B(R3))
- EMA SUSAR (EU clinical trial): 7 calendar days (EU CTR 2021/536 Art. 42)
- DEA CS theft/significant loss: 1 business day (DEA 21 CFR §1301.76(b)) — DEA Form 106
{
"name": "Adverse Event \u2014 FDA 15-Day MedWatch Pipeline (21 CFR \u00a7312.32)",
"nodes": [
{
"id": "1",
"name": "Webhook \u2014 ae_reported",
"type": "n8n-nodes-base.webhook",
"typeVersion": 1,
"position": [
260,
300
],
"parameters": {
"path": "ae-reported",
"responseMode": "onReceived",
"responseData": ""
}
},
{
"id": "2",
"name": "Code \u2014 Classify AE",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
460,
300
],
"parameters": {
"jsCode": "const ae = $input.first().json;\nconst AE_TYPES = {\n SERIOUS_UNEXPECTED: { label: 'Serious Unexpected AE', deadline_days: 15, regulation: '21 CFR \u00a7312.32(c)(1)(i)', form: 'MedWatch FDA-3500A', clock: 'calendar days from first receipt', severity: 'CRITICAL' },\n SERIOUS_EXPECTED: { label: 'Serious Expected AE', deadline_days: 15, regulation: '21 CFR \u00a7312.32(c)(1)(ii)', form: 'MedWatch FDA-3500A', clock: 'calendar days', severity: 'URGENT' },\n FATAL_UNEXPECTED: { label: 'Fatal/Life-Threatening Unexpected AE', deadline_days: 7, regulation: '21 CFR \u00a7312.32(c)(2)', form: 'MedWatch FDA-3500A EXPEDITED', clock: 'calendar days \u2014 telephone first', severity: 'CRITICAL' },\n NON_SERIOUS: { label: 'Non-Serious AE', deadline_days: 365, regulation: '21 CFR \u00a7312.33(b) annual IND report', form: 'Annual Safety Report', clock: 'annual', severity: 'NOTICE' },\n CIOMS_I: { label: 'CIOMS I International Safety Report', deadline_days: 15, regulation: 'ICH E2B(R3) / EMA GVP Module VI', form: 'CIOMS I', clock: 'calendar days', severity: 'CRITICAL' },\n EMA_SUSAR: { label: 'EMA SUSAR (EU Clinical Trial)', deadline_days: 7, regulation: 'EU CTR 2021/536 Art. 42', form: 'EudraVigilance', clock: 'fatal/life-threatening = 7 days', severity: 'CRITICAL' },\n DEA_THEFT_SIGNIFICANT_LOSS: { label: 'DEA CS Theft/Significant Loss', deadline_days: 1, regulation: 'DEA 21 CFR \u00a71301.76(b)', form: 'DEA Form 106', clock: '1 business day', severity: 'CRITICAL' },\n IND_SAFETY_REPORT: { label: 'IND Safety Report (domestic)', deadline_days: 15, regulation: '21 CFR \u00a7312.32(c)', form: 'MedWatch', clock: '15 calendar days', severity: 'URGENT' }\n};\nconst def = AE_TYPES[ae.ae_type] || AE_TYPES['NON_SERIOUS'];\nconst received = new Date(ae.received_at || new Date());\nconst due = new Date(received.getTime() + def.deadline_days * 86400000);\nreturn [{ json: { ...ae, ...def, received_at: received.toISOString(), submission_due: due.toISOString() } }];"
}
},
{
"id": "3",
"name": "IF \u2014 CRITICAL/URGENT",
"type": "n8n-nodes-base.if",
"typeVersion": 1,
"position": [
660,
300
],
"parameters": {
"conditions": {
"string": [
{
"value1": "={{$json.severity}}",
"operation": "regex",
"value2": "CRITICAL|URGENT"
}
]
}
}
},
{
"id": "4",
"name": "Slack #pharmacovigilance-alert",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
860,
200
],
"parameters": {
"channel": "#pharmacovigilance-alert",
"text": "=:rotating_light: *AE CLOCK STARTED: {{$json.label}}*\nPatient: {{$json.patient_id}} | Study: {{$json.study_id}}\nReceived: {{$json.received_at}}\nSubmission due: *{{$json.submission_due}}* ({{$json.deadline_days}} calendar days)\nForm: {{$json.form}} | Reg: {{$json.regulation}}\n@pharmacovigilance \u2014 acknowledge immediately"
}
},
{
"id": "5",
"name": "Gmail \u2014 PV team",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
860,
320
],
"parameters": {
"operation": "send",
"to": "={{$env.PV_TEAM_EMAIL}}",
"subject": "=AE CLOCK: {{$json.label}} \u2014 due {{$json.submission_due}}",
"message": "=<p><strong>Adverse Event Notification \u2014 Regulatory Clock Started</strong></p><p>Type: {{$json.label}}<br>Patient ID: {{$json.patient_id}}<br>Study: {{$json.study_id}}<br>Received: {{$json.received_at}}<br>Submission due: <strong>{{$json.submission_due}}</strong> ({{$json.deadline_days}} calendar days)<br>Required form: {{$json.form}}<br>Regulation: {{$json.regulation}}<br>Clock: {{$json.clock}}</p><p>Action required immediately.</p>"
}
},
{
"id": "6",
"name": "Log to AE Tracker Sheet",
"type": "n8n-nodes-base.googleSheets",
"typeVersion": 1,
"position": [
1060,
200
],
"parameters": {
"operation": "append",
"sheetId": "SHEET_ID",
"range": "AE_Log!A:K",
"values": {
"values": [
"={{$json.patient_id}}",
"={{$json.study_id}}",
"={{$json.ae_type}}",
"={{$json.label}}",
"={{$json.received_at}}",
"={{$json.submission_due}}",
"={{$json.deadline_days}}",
"={{$json.form}}",
"={{$json.regulation}}",
"=pending",
""
]
}
}
},
{
"id": "7",
"name": "Slack #pv-log (non-urgent)",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
860,
440
],
"parameters": {
"channel": "#pv-log",
"text": "=AE logged: {{$json.label}} | Patient {{$json.patient_id}} | Due {{$json.submission_due}}"
}
}
],
"connections": {
"Webhook \u2014 ae_reported": {
"main": [
[
{
"node": "Code \u2014 Classify AE",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Classify AE": {
"main": [
[
{
"node": "IF \u2014 CRITICAL/URGENT",
"type": "main",
"index": 0
}
]
]
},
"IF \u2014 CRITICAL/URGENT": {
"main": [
[
{
"node": "Slack #pharmacovigilance-alert",
"type": "main",
"index": 0
},
{
"node": "Gmail \u2014 PV team",
"type": "main",
"index": 0
},
{
"node": "Log to AE Tracker Sheet",
"type": "main",
"index": 0
}
],
[
{
"node": "Slack #pv-log (non-urgent)",
"type": "main",
"index": 0
},
{
"node": "Log to AE Tracker Sheet",
"type": "main",
"index": 0
}
]
]
}
}
}
Workflow 5: Weekly BioTech/Pharma SaaS KPI Dashboard
Every Monday at 8 AM. Queries Postgres for customer counts by tier, MRR, compliance flag distribution, and open AE reports. Builds an HTML email to CEO (BCC CISO, CCO) and posts a one-liner to #go-to-market.
{
"name": "Weekly BioTech/Pharma SaaS KPI Dashboard",
"nodes": [
{
"id": "1",
"name": "Monday 8 AM",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1,
"position": [
260,
300
],
"parameters": {
"rule": {
"interval": [
{
"field": "cronExpression",
"expression": "0 8 * * 1"
}
]
}
}
},
{
"id": "2",
"name": "Read KPI data from Postgres",
"type": "n8n-nodes-base.postgres",
"typeVersion": 1,
"position": [
460,
300
],
"parameters": {
"operation": "executeQuery",
"query": "SELECT\n COUNT(DISTINCT customer_id) FILTER (WHERE tier = 'BIOTECH_SAAS_VENDOR') AS biotech_saas_accounts,\n COUNT(DISTINCT customer_id) FILTER (WHERE tier = 'CLINICAL_TRIAL_MANAGEMENT_SAAS') AS ctms_accounts,\n COUNT(DISTINCT customer_id) FILTER (WHERE tier = 'PHARMACOVIGILANCE_SAAS') AS pv_saas_accounts,\n COUNT(DISTINCT customer_id) FILTER (WHERE tier = 'PHARMATECH_STARTUP') AS startup_accounts,\n SUM(mrr_usd) AS total_mrr,\n COUNT(*) FILTER (WHERE compliance_flags LIKE '%FDA_21_CFR_PART_11_SUBJECT%') AS part11_accounts,\n COUNT(*) FILTER (WHERE compliance_flags LIKE '%ICH_GCP_E6_APPLICABLE%') AS gcp_accounts,\n COUNT(*) FILTER (WHERE compliance_flags LIKE '%DEA_CSA_SCHEDULE_REGISTERED%') AS dea_accounts,\n COUNT(*) FILTER (WHERE open_ae_reports > 0) AS customers_with_open_ae,\n COUNT(*) FILTER (WHERE status = 'churned' AND churned_at >= NOW() - INTERVAL '7 days') AS churned_7d\nFROM customers\nWHERE status != 'cancelled'"
}
},
{
"id": "3",
"name": "Code \u2014 Build HTML Report",
"type": "n8n-nodes-base.code",
"typeVersion": 1,
"position": [
660,
300
],
"parameters": {
"jsCode": "const d = $input.first().json;\nconst html = `\n<h2>FlowKit BioTech/Pharma Weekly KPI \u2014 ${new Date().toISOString().split('T')[0]}</h2>\n<table border='1' cellpadding='6' style='border-collapse:collapse'>\n<tr><th>Metric</th><th>Value</th></tr>\n<tr><td>BioTech SaaS accounts</td><td>${d.biotech_saas_accounts}</td></tr>\n<tr><td>CTMS/EDC accounts</td><td>${d.ctms_accounts}</td></tr>\n<tr><td>Pharmacovigilance SaaS accounts</td><td>${d.pv_saas_accounts}</td></tr>\n<tr><td>Startup accounts</td><td>${d.startup_accounts}</td></tr>\n<tr><td>Total MRR (USD)</td><td>$${Number(d.total_mrr||0).toLocaleString()}</td></tr>\n<tr><td>21 CFR Part 11 accounts</td><td>${d.part11_accounts}</td></tr>\n<tr><td>ICH GCP E6(R2) accounts</td><td>${d.gcp_accounts}</td></tr>\n<tr><td>DEA CSA accounts</td><td>${d.dea_accounts}</td></tr>\n<tr><td>Customers with open AE reports</td><td>${d.customers_with_open_ae}</td></tr>\n<tr><td>Churned (last 7d)</td><td>${d.churned_7d}</td></tr>\n</table>\n`;\nreturn [{ json: { html, ...d } }];"
}
},
{
"id": "4",
"name": "Gmail \u2014 CEO + BCC CISO CCO",
"type": "n8n-nodes-base.gmail",
"typeVersion": 1,
"position": [
860,
300
],
"parameters": {
"operation": "send",
"to": "={{$env.CEO_EMAIL}}",
"bcc": "={{$env.CISO_EMAIL}},{{$env.CCO_EMAIL}}",
"subject": "=FlowKit Pharma Weekly KPI \u2014 {{$now.format('YYYY-MM-DD')}}",
"message": "={{$json.html}}"
}
},
{
"id": "5",
"name": "Slack #go-to-market",
"type": "n8n-nodes-base.slack",
"typeVersion": 1,
"position": [
860,
420
],
"parameters": {
"channel": "#go-to-market",
"text": "=Weekly KPI: {{$json.total_mrr}} MRR | {{$json.biotech_saas_accounts}} BioTech + {{$json.ctms_accounts}} CTMS + {{$json.pv_saas_accounts}} PV accounts | {{$json.customers_with_open_ae}} open AEs | {{$json.churned_7d}} churns"
}
}
],
"connections": {
"Monday 8 AM": {
"main": [
[
{
"node": "Read KPI data from Postgres",
"type": "main",
"index": 0
}
]
]
},
"Read KPI data from Postgres": {
"main": [
[
{
"node": "Code \u2014 Build HTML Report",
"type": "main",
"index": 0
}
]
]
},
"Code \u2014 Build HTML Report": {
"main": [
[
{
"node": "Gmail \u2014 CEO + BCC CISO CCO",
"type": "main",
"index": 0
},
{
"node": "Slack #go-to-market",
"type": "main",
"index": 0
}
]
]
}
}
}
Self-Hosting Rationale for Enterprise Pharma/BioTech Procurement
| Question Your Customer Will Ask | Regulation | The Self-Hosted n8n Answer |
|---|---|---|
| 21 CFR Part 11 audit trail gap | FDA 21 CFR §11.10(a)(e) | Cloud iPaaS nodes that touch eCTD/IND/NDA electronic records are in the Part 11 audit trail boundary but not in your validated system. FDA 483 Observations cite audit trail gaps as OAI findings. Self-hosted n8n keeps the automation layer inside your validated GxP environment. |
| ICH GCP E6(R2) TMF inspection-readiness | ICH E6(R2) §5.18.7 | Trial Master File must be accessible to inspectors at all times. CTMS or EDC automation routing TMF metadata through a cloud vendor = undocumented third-party access point in your TMF. Self-hosted n8n = TMF data inside your inspection-ready controlled environment. |
| DEA CSA Schedule II-V data egress | DEA 21 CFR §1301 / CSA 21 USC §801 | Controlled substance ordering, inventory, and CSOS data in cloud iPaaS = potential CSA §827 record-keeping violation. DEA inspectors demand complete chain of custody. Self-hosted n8n = DEA-auditable controlled substance workflow data stays inside your DEA-registered facility boundary. |
| FDA 21 CFR Part 820 QSR SaMD validation | FDA 21 CFR Part 820 / FDA SaMD Guidance | Software as a Medical Device QMS requires validation of all systems handling design history records, DHFs, and CAPAs. Cloud iPaaS in that flow = undocumented system in your Design Controls validation package. Self-hosted n8n is validatable. |
| HIPAA PHI — clinical trial participants | HIPAA §164.308(b) BAA / HITECH §13402 | Clinical trial participant data is PHI. Every cloud iPaaS node routing PHI in trial workflows is a Business Associate under HIPAA §164.308(b). Self-hosted n8n in your HIPAA-compliant AWS/Azure environment keeps PHI inside your Business Associate boundary and avoids the chain-of-custody gap. |
Get All 15 Workflows (Import-Ready JSON)
The complete FlowKit bundle — 15 n8n automation templates including all five pharma/biotech workflows above — is available at https://stripeai.gumroad.com.
Individual templates: $12–$29. Complete bundle: $97.
Each template includes the full import-ready JSON, a step-by-step setup guide, and compliance annotations for the relevant FDA/ICH/DEA regulations.
Top comments (0)