This workflow corresponds to n8n.io template #16909 — we link there as the canonical source.
This workflow follows the Agent → HTTP Request recipe pattern — see all workflows that pair these two integrations.
The workflow JSON
Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →
{
"id": "MFQCcb0KMTYUDjAI",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Monitor Live Crowd Density and Send Safety Reroute Alerts via SMS",
"tags": [],
"nodes": [
{
"id": "30ae036b-c234-4261-94fc-dda92633fd96",
"name": "Sticky Note - Overview",
"type": "n8n-nodes-base.stickyNote",
"position": [
2160,
-320
],
"parameters": {
"width": 1020,
"height": 1380,
"content": "## Live Crowd Density & Safety Heatmap\n\nThis workflow continuously monitors real-time crowd density readings and open incident reports across a set of zones, calculates a configurable risk score per zone, uses AI to write calm, actionable reroute guidance for high-risk zones, sends proactive SMS alerts to travelers subscribed to those zones, and logs every zone's status to a live Google Sheets heatmap on every run.\n\n### Who's it for\n- Event organizers and festival operations teams monitoring multiple crowd zones\n- Transit hubs, stadiums, and airports tracking occupancy against safety capacity\n- City safety teams combining sensor occupancy data with citizen incident reports\n- Anyone who wants an automated early-warning system instead of watching dashboards manually\n\n### How it works\n1. A schedule trigger polls a live crowd-density feed and an open-incident feed (both modeled as Google Sheets, but either can be swapped for a real sensor/API source)\n2. Density and incident data are merged and a risk score (0-100) is calculated per zone from occupancy rate and weighted incident severity\n3. Every zone's status is logged to the Heatmap tab on every run, building a continuous live heatmap regardless of risk level\n4. Zones scoring 65+ (High or Critical) are filtered out for further action\n5. AI writes a short, calm reroute message and a safety tip for each high-risk zone, suggesting a specific nearby lower-risk zone when one exists\n6. The workflow looks up travelers subscribed to that zone (or subscribed to all zones) from Google Sheets\n7. Each subscribed traveler receives an SMS with the reroute message and safety tip via Twilio\n\n### How to set up\n1. Import this workflow into n8n\n2. Add Anthropic Claude credentials and attach to the Anthropic model node\n3. Add Twilio Basic Auth credentials and replace YOUR_TWILIO_ACCOUNT_SID and YOUR_TWILIO_PHONE_NUMBER\n4. Switch the Google Sheets HTTP nodes to the native Google Sheets node with OAuth2, or add OAuth2 credentials directly\n5. Replace YOUR_SHEET_ID in all three Sheets HTTP nodes with your actual Spreadsheet ID\n6. Set up your Google Sheet with three tabs: ZoneDensity, IncidentReports, Subscribers, and Heatmap (column layouts below)\n7. Set the schedule interval to match how often your density feed updates (default: every 5 minutes)\n8. Activate the workflow\n\n### Requirements\n- Anthropic Claude API key (claude-sonnet-4 recommended)\n- Twilio account for SMS delivery\n- Google Sheets with OAuth2 credentials (ZoneDensity, IncidentReports, Subscribers, Heatmap tabs)\n\n### How to customize\n- Adjust the risk score weighting (occupancy vs incidents) and thresholds in JS - Merge Zone Data & Calculate Risk Scores\n- Raise or lower the SMS trigger threshold in Filter - High Risk Zones Only (default: score >= 65)\n- Replace the ZoneDensity and IncidentReports Sheets fetches with a real sensor/camera API or 311-style incident feed\n- Add a Slack or email node alongside SMS for an operations control room feed\n- Add geofencing so travelers only receive alerts when physically near the zone, using their live location instead of a static subscription\n\n### Google Sheets Column Layout\n\nZoneDensity tab - row 1 headers:\nZone ID | Zone Name | Current Occupancy | Capacity | Location | Last Updated\n\nIncidentReports tab - row 1 headers:\nReport ID | Zone ID | Incident Type | Severity | Description | Reported At | Status\n\nSubscribers tab - row 1 headers:\nSubscriber ID | Name | Phone | Zone ID (or 'All') | Status\n\nHeatmap tab - row 1 headers:\nChecked At | Zone ID | Zone Name | Current Occupancy | Capacity | Occupancy Rate | Incident Count | Risk Score | Risk Level | Location"
},
"typeVersion": 1
},
{
"id": "c3ed39bc-c39f-4a72-ae1d-61a9e59911eb",
"name": "Sticky Note - Stage 1",
"type": "n8n-nodes-base.stickyNote",
"position": [
3328,
-272
],
"parameters": {
"color": 3,
"width": 1320,
"height": 946,
"content": "## Stage 1: Data Collection & Risk Scoring\n\nSchedule Trigger polls on a fixed interval (default every 5 minutes). Two parallel fetches pull the latest occupancy readings from ZoneDensity and the currently open incidents from IncidentReports.\n\nEach fetch is followed by a small JS node that renames the raw Sheets `values` array so the two datasets don't collide once merged.\n\nMerge - Combine Density & Incidents joins both datasets into a single item by position.\n\nJS - Merge Zone Data & Calculate Risk Scores builds one object per zone: occupancy rate, matching open incidents weighted by severity, a combined riskScore (0-100, 60% occupancy / 40% incidents by default), a riskLevel label, and a list of the top 3 nearby zones currently under 40% risk to use as reroute suggestions.\n\nSplit Out - Zones turns that single array into one item per zone so the rest of the workflow can process each zone independently."
},
"typeVersion": 1
},
{
"id": "e6b058c9-628b-42ea-a66e-687fef688052",
"name": "Sticky Note - Stage 2",
"type": "n8n-nodes-base.stickyNote",
"position": [
4688,
432
],
"parameters": {
"color": 6,
"width": 780,
"height": 560,
"content": "## Stage 2: Continuous Heatmap Logging\n\nThis branch runs for every zone on every cycle, regardless of risk level \u2014 it's what builds the live, continuous heatmap.\n\nJS - Format Heatmap Log Record assembles the row for the Heatmap tab: timestamp, occupancy, occupancy rate, incident count, risk score, and risk level.\n\nSave Zone Status to Google Sheets Heatmap Tracker appends that row, so the sheet always reflects the latest state across every monitored zone."
},
"typeVersion": 1
},
{
"id": "f7d978d8-379c-4d6f-917c-acb64c237a41",
"name": "Sticky Note - Stage 3",
"type": "n8n-nodes-base.stickyNote",
"position": [
4688,
-288
],
"parameters": {
"color": 4,
"width": 860,
"height": 524,
"content": "## Stage 3: AI Rerouting for High-Risk Zones\n\nFilter - High Risk Zones Only lets through only zones scoring 65 or higher (High or Critical), so calm, low-occupancy zones never trigger AI calls or alerts.\n\nA Wait buffer respects API rate limits, then the reroute AI (claude-sonnet-4, temperature 0.3 for calm, consistent guidance) writes a short alert headline, an SMS-length reroute message that names a specific nearby lower-risk zone when one exists, and one practical safety tip suited to the risk level.\n\nJS - Parse Rerouting Output extracts the structured JSON safely, with a fallback that builds a generic reroute message from the nearby low-risk zone list if AI parsing fails."
},
"typeVersion": 1
},
{
"id": "f022fe2d-fb83-437f-ba9f-a846fe7028db",
"name": "Sticky Note - Stage 4",
"type": "n8n-nodes-base.stickyNote",
"position": [
5584,
-288
],
"parameters": {
"color": 5,
"width": 1076,
"height": 750,
"content": "## Stage 4: Traveler Targeting & SMS Delivery\n\nFetch Subscribed Travelers pulls every subscriber row from Google Sheets.\n\nJS - Filter Travelers in Zone & Format Alerts keeps only active subscribers whose Zone ID matches the current high-risk zone, or who are subscribed to 'All' zones.\n\nSplit Out - Travelers turns that list into one item per traveler, each still carrying the zone's reroute message and safety tip.\n\nSend SMS Alert to Traveler delivers the alert headline, reroute message, and safety tip to each traveler's phone via Twilio."
},
"typeVersion": 1
},
{
"id": "20a17c78-c2fb-4869-bad5-f2ea77c70fbb",
"name": "Schedule Trigger - Poll Crowd Data",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
3360,
272
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes"
}
]
}
},
"typeVersion": 1.2
},
{
"id": "f796e6f3-fbf3-47f1-80df-5be14c5b29e8",
"name": "Fetch Live Crowd Density Data",
"type": "n8n-nodes-base.httpRequest",
"position": [
3584,
160
],
"parameters": {
"url": "=https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/ZoneDensity!A1:F200",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api"
},
"credentials": {
"oAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "a7422d93-3f63-4f44-ad3a-5c2189fdff48",
"name": "JS - Rename Density Values",
"type": "n8n-nodes-base.code",
"position": [
3808,
160
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nreturn {\n json: {\n densityRows: item.values || []\n }\n};"
},
"typeVersion": 2
},
{
"id": "5384b5f1-2a59-4942-bc05-41eefe2bba7c",
"name": "Fetch Active Incident Reports",
"type": "n8n-nodes-base.httpRequest",
"position": [
3584,
464
],
"parameters": {
"url": "=https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/IncidentReports!A1:G500",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api"
},
"credentials": {
"oAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "e5441b19-df1b-4e23-b02c-1c6aa7603b8a",
"name": "JS - Rename Incident Values",
"type": "n8n-nodes-base.code",
"position": [
3808,
464
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nreturn {\n json: {\n incidentRows: item.values || []\n }\n};"
},
"typeVersion": 2
},
{
"id": "826070b3-9bc1-4729-bb26-dca33234a526",
"name": "Merge - Combine Density & Incidents",
"type": "n8n-nodes-base.merge",
"position": [
4032,
320
],
"parameters": {
"mode": "combine",
"options": {}
},
"typeVersion": 3.2
},
{
"id": "8307fda8-3e34-4352-9337-54188d3b9f09",
"name": "JS - Merge Zone Data & Calculate Risk Scores",
"type": "n8n-nodes-base.code",
"position": [
4256,
320
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst densityRows = (item.densityRows || []).slice(1); // skip header\nconst incidentRows = (item.incidentRows || []).slice(1); // skip header\n\n// Density columns: [zoneId, zoneName, currentOccupancy, capacity, location, lastUpdated]\n// Incident columns: [reportId, zoneId, incidentType, severity, description, reportedAt, status]\n\nconst severityWeight = { Low: 10, Medium: 25, High: 45, Critical: 70 };\n\nconst zones = densityRows.map(r => {\n const zoneId = r[0] || '';\n const zoneName = r[1] || '';\n const currentOccupancy = parseInt(r[2], 10) || 0;\n const capacity = parseInt(r[3], 10) || 1;\n const location = r[4] || '';\n const lastUpdated = r[5] || '';\n\n const zoneIncidents = incidentRows.filter(i => i[1] === zoneId && (i[6] || '').toLowerCase() === 'open');\n const incidentCount = zoneIncidents.length;\n const incidentScore = Math.min(100, zoneIncidents.reduce((sum, i) => sum + (severityWeight[i[3]] || 15), 0));\n\n const occupancyRate = capacity > 0 ? Math.round((currentOccupancy / capacity) * 100) : 0;\n const occupancyScore = Math.min(100, occupancyRate);\n const riskScore = Math.round(occupancyScore * 0.6 + incidentScore * 0.4);\n\n let riskLevel = 'Low';\n if (riskScore >= 85) riskLevel = 'Critical';\n else if (riskScore >= 65) riskLevel = 'High';\n else if (riskScore >= 40) riskLevel = 'Medium';\n\n return {\n zoneId, zoneName, currentOccupancy, capacity, location, lastUpdated,\n occupancyRate, incidentCount,\n incidentSummary: zoneIncidents.map(i => `${i[2]} (${i[3]})`).join('; '),\n riskScore, riskLevel\n };\n});\n\n// Attach nearby lower-risk zones as reroute candidates\nzones.forEach(z => {\n z.nearbyLowRiskZones = zones\n .filter(o => o.zoneId !== z.zoneId && o.riskScore < 40)\n .sort((a, b) => a.occupancyRate - b.occupancyRate)\n .slice(0, 3)\n .map(o => `${o.zoneName} (${o.occupancyRate}% full)`);\n});\n\nreturn { json: { zones, checkedAt: new Date().toISOString() } };"
},
"typeVersion": 2
},
{
"id": "7e484900-825e-4d5d-b9a8-9eb9e35207df",
"name": "Split Out - Zones",
"type": "n8n-nodes-base.splitOut",
"position": [
4480,
320
],
"parameters": {
"options": {},
"fieldToSplitOut": "zones"
},
"typeVersion": 1
},
{
"id": "f7bc6351-982b-4c9f-a16e-51c25227970d",
"name": "JS - Format Heatmap Log Record",
"type": "n8n-nodes-base.code",
"position": [
4768,
704
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst loggedAt = new Date().toISOString();\n\nreturn {\n json: {\n ...item,\n loggedAt,\n heatmapSummaryRow: [\n loggedAt,\n item.zoneId || '',\n item.zoneName || '',\n item.currentOccupancy || 0,\n item.capacity || 0,\n (item.occupancyRate || 0) + '%',\n item.incidentCount || 0,\n item.riskScore || 0,\n item.riskLevel || '',\n item.location || ''\n ]\n }\n};"
},
"typeVersion": 2
},
{
"id": "989838cf-b996-47f2-8a58-4251485747b1",
"name": "Save Zone Status to Google Sheets Heatmap Tracker",
"type": "n8n-nodes-base.httpRequest",
"position": [
5232,
704
],
"parameters": {
"url": "https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/Heatmap!A1:append?valueInputOption=USER_ENTERED",
"method": "POST",
"options": {},
"jsonBody": "={\n \"values\": [{{ JSON.stringify($json.heatmapSummaryRow) }}]\n}",
"sendBody": true,
"specifyBody": "json",
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api"
},
"credentials": {
"oAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "2b5735bc-ed25-41e5-93fc-b99626d9e2d5",
"name": "Filter - High Risk Zones Only",
"type": "n8n-nodes-base.filter",
"position": [
4736,
48
],
"parameters": {
"options": {},
"conditions": {
"conditions": [
{
"operator": {
"type": "number",
"operation": "gte"
},
"leftValue": "={{ $json.riskScore }}",
"rightValue": 65
}
]
}
},
"typeVersion": 2.2
},
{
"id": "7c64d5ef-9476-4c99-8556-229b3675ec4a",
"name": "Wait - API Rate Limit",
"type": "n8n-nodes-base.wait",
"position": [
4960,
48
],
"parameters": {},
"typeVersion": 1
},
{
"id": "0d02650a-3aed-4b8a-b335-57d173632ab8",
"name": "AI - Generate Rerouting Suggestion",
"type": "@n8n/n8n-nodes-langchain.agent",
"position": [
5120,
48
],
"parameters": {
"text": "=You are a real-time crowd safety coordinator.\n\nZone: {{ $json.zoneName }} ({{ $json.zoneId }})\nLocation: {{ $json.location }}\nCurrent Occupancy: {{ $json.currentOccupancy }} / {{ $json.capacity }} ({{ $json.occupancyRate }}% full)\nActive Incidents: {{ $json.incidentSummary || 'None reported' }}\nRisk Level: {{ $json.riskLevel }} ({{ $json.riskScore }}/100)\nNearby Lower-Risk Zones: {{ JSON.stringify($json.nearbyLowRiskZones) }}\n\nWrite short, calm, actionable guidance for travelers currently near or heading toward this zone. Suggest a specific reroute using one of the nearby lower-risk zones if any are available, and give one practical safety tip suited to the risk level.\n\nReturn a JSON object with EXACTLY these fields:\n{\n \"alertHeadline\": \"Under 10 words, urgent but calm\",\n \"rerouteMessage\": \"Under 200 characters, suitable for SMS, names a specific alternate zone if one is available\",\n \"safetyTip\": \"One short, practical safety tip\"\n}\n\nReturn ONLY the JSON. No markdown, no explanation.",
"options": {},
"promptType": "define"
},
"typeVersion": 1.6
},
{
"id": "34e51086-59c5-462a-a862-654e778f4b44",
"name": "Anthropic - Rerouting Model",
"type": "@n8n/n8n-nodes-langchain.lmChatAnthropic",
"position": [
5264,
272
],
"parameters": {
"model": {
"__rl": true,
"mode": "list",
"value": "claude-sonnet-4-20250514"
},
"options": {
"temperature": 0.3
}
},
"credentials": {
"anthropicApi": {
"name": "<your credential>"
}
},
"typeVersion": 1.3
},
{
"id": "45d503dd-9349-4ebf-8638-abc4873c2e49",
"name": "JS - Parse Rerouting Output",
"type": "n8n-nodes-base.code",
"position": [
5408,
48
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nlet parsed = {};\ntry {\n const rawText = item.output || item.text || item.response || '{}';\n const clean = rawText.replace(/```json|```/g, '').trim();\n parsed = JSON.parse(clean);\n} catch(e) {\n const alt = (item.nearbyLowRiskZones && item.nearbyLowRiskZones[0]) || 'a nearby area';\n parsed = {\n alertHeadline: `${item.riskLevel} crowd risk at ${item.zoneName}`,\n rerouteMessage: `${item.zoneName} is currently at ${item.occupancyRate}% capacity with ${item.riskLevel.toLowerCase()} risk. Consider heading toward ${alt} instead.`,\n safetyTip: 'Stay aware of exits and follow any on-site staff instructions.'\n };\n}\nreturn {\n json: {\n ...item,\n ...parsed\n }\n};"
},
"typeVersion": 2
},
{
"id": "9cf0ce77-2c43-4bb6-abdb-3725ea8201f7",
"name": "Fetch Subscribed Travelers",
"type": "n8n-nodes-base.httpRequest",
"position": [
5632,
48
],
"parameters": {
"url": "=https://sheets.googleapis.com/v4/spreadsheets/YOUR_SHEET_ID/values/Subscribers!A1:E1000",
"options": {},
"authentication": "genericCredentialType",
"genericAuthType": "oAuth2Api"
},
"credentials": {
"oAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.2
},
{
"id": "a4d842e2-cd8d-4cc2-acf2-f6b524878a1b",
"name": "JS - Filter Travelers in Zone & Format Alerts",
"type": "n8n-nodes-base.code",
"position": [
5856,
48
],
"parameters": {
"mode": "runOnceForEachItem",
"jsCode": "const item = $input.item.json;\nconst rows = (item.values || []).slice(1); // [subscriberId, name, phone, zoneId, status]\n\nconst travelers = rows\n .filter(r => (r[4] || '').toLowerCase() === 'active' && (r[3] === item.zoneId || (r[3] || '').toLowerCase() === 'all'))\n .map(r => ({ subscriberId: r[0] || '', name: r[1] || '', phone: r[2] || '' }));\n\nreturn {\n json: {\n ...item,\n travelers,\n travelerCount: travelers.length\n }\n};"
},
"typeVersion": 2
},
{
"id": "ea1162f7-b597-464d-a8ad-7113e4fa6688",
"name": "Split Out - Travelers",
"type": "n8n-nodes-base.splitOut",
"position": [
6080,
48
],
"parameters": {
"options": {},
"fieldToSplitOut": "travelers"
},
"typeVersion": 1
},
{
"id": "dce7fc5c-d5c5-49cd-976b-6daadc73460c",
"name": "Send SMS Alert to Traveler",
"type": "n8n-nodes-base.httpRequest",
"position": [
6304,
48
],
"parameters": {
"url": "=https://api.twilio.com/2010-04-01/Accounts/YOUR_TWILIO_ACCOUNT_SID/Messages.json",
"method": "POST",
"options": {},
"jsonBody": "={\n \"To\": \"{{ $json.phone }}\",\n \"From\": \"YOUR_TWILIO_PHONE_NUMBER\",\n \"Body\": \"{{ $json.alertHeadline }}: {{ $json.rerouteMessage }} Safety tip: {{ $json.safetyTip }}\"\n}",
"sendBody": true,
"sendHeaders": true,
"specifyBody": "json",
"headerParameters": {
"parameters": [
{
"name": "Authorization",
"value": "Basic YOUR_TWILIO_BASE64_AUTH"
}
]
}
},
"typeVersion": 4.2
},
{
"id": "60dbebb0-7e32-4781-970a-bb8cbda4faf5",
"name": "Wait For Limit",
"type": "n8n-nodes-base.wait",
"position": [
4976,
704
],
"parameters": {},
"typeVersion": 1.1
}
],
"active": false,
"settings": {
"executionOrder": "v1"
},
"versionId": "e8c11a3c-52f9-4a46-aa00-447bde462522",
"connections": {
"Wait For Limit": {
"main": [
[
{
"node": "Save Zone Status to Google Sheets Heatmap Tracker",
"type": "main",
"index": 0
}
]
]
},
"Split Out - Zones": {
"main": [
[
{
"node": "JS - Format Heatmap Log Record",
"type": "main",
"index": 0
},
{
"node": "Filter - High Risk Zones Only",
"type": "main",
"index": 0
}
]
]
},
"Split Out - Travelers": {
"main": [
[
{
"node": "Send SMS Alert to Traveler",
"type": "main",
"index": 0
}
]
]
},
"Wait - API Rate Limit": {
"main": [
[
{
"node": "AI - Generate Rerouting Suggestion",
"type": "main",
"index": 0
}
]
]
},
"Fetch Subscribed Travelers": {
"main": [
[
{
"node": "JS - Filter Travelers in Zone & Format Alerts",
"type": "main",
"index": 0
}
]
]
},
"JS - Rename Density Values": {
"main": [
[
{
"node": "Merge - Combine Density & Incidents",
"type": "main",
"index": 0
}
]
]
},
"Anthropic - Rerouting Model": {
"ai_languageModel": [
[
{
"node": "AI - Generate Rerouting Suggestion",
"type": "ai_languageModel",
"index": 0
}
]
]
},
"JS - Parse Rerouting Output": {
"main": [
[
{
"node": "Fetch Subscribed Travelers",
"type": "main",
"index": 0
}
]
]
},
"JS - Rename Incident Values": {
"main": [
[
{
"node": "Merge - Combine Density & Incidents",
"type": "main",
"index": 1
}
]
]
},
"Fetch Active Incident Reports": {
"main": [
[
{
"node": "JS - Rename Incident Values",
"type": "main",
"index": 0
}
]
]
},
"Fetch Live Crowd Density Data": {
"main": [
[
{
"node": "JS - Rename Density Values",
"type": "main",
"index": 0
}
]
]
},
"Filter - High Risk Zones Only": {
"main": [
[
{
"node": "Wait - API Rate Limit",
"type": "main",
"index": 0
}
]
]
},
"JS - Format Heatmap Log Record": {
"main": [
[
{
"node": "Wait For Limit",
"type": "main",
"index": 0
}
]
]
},
"AI - Generate Rerouting Suggestion": {
"main": [
[
{
"node": "JS - Parse Rerouting Output",
"type": "main",
"index": 0
}
]
]
},
"Schedule Trigger - Poll Crowd Data": {
"main": [
[
{
"node": "Fetch Live Crowd Density Data",
"type": "main",
"index": 0
},
{
"node": "Fetch Active Incident Reports",
"type": "main",
"index": 0
}
]
]
},
"Merge - Combine Density & Incidents": {
"main": [
[
{
"node": "JS - Merge Zone Data & Calculate Risk Scores",
"type": "main",
"index": 0
}
]
]
},
"JS - Merge Zone Data & Calculate Risk Scores": {
"main": [
[
{
"node": "Split Out - Zones",
"type": "main",
"index": 0
}
]
]
},
"JS - Filter Travelers in Zone & Format Alerts": {
"main": [
[
{
"node": "Split Out - Travelers",
"type": "main",
"index": 0
}
]
]
}
}
}
Credentials you'll need
Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.
anthropicApioAuth2Api
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow ingests crowd density and incident reports via webhook or a 5‑minute schedule, assesses risk with Python thresholds, uses OpenAI to generate a short rerouting recommendation, then sends an SMS via Twilio and logs each alert to a Google Sheets heatmap tracker.…
Source: https://n8n.io/workflows/16909/ — original creator credit. Request a take-down →
Related workflows
Workflows that share integrations, category, or trigger type with this one. All free to copy and import.
This workflow is for beauty salons who want consistent, high‑quality social media content without writing every post manually. It also suits agencies and automation builders who manage multiple beauty
The Multi-Model Agency Content Engine is a high-performance editorial system designed for agencies. It solves the "blank page" problem by alternating between real-world social proof and strategic expe
Who Is This For?
📺 Full walkthrough video: https://youtu.be/03mZE9tvELU
This n8n template builds an automated daily news digest powered by Claude AI.