If you're building an IoT platform — device management SaaS, industrial IoT middleware, connected product infrastructure, smart building software — you're managing thousands of devices, complex onboarding sequences, and real-time anomaly detection that Zapier and Make fundamentally can't handle.
Self-hosted n8n runs inside your VPC. Device telemetry, sensor readings, customer fleet data, and firmware deployment logs never leave your infrastructure — which matters for enterprise security reviews, GDPR Article 44-46, and IEC 62443 compliance.
Here are 5 production-ready n8n workflows built specifically for IoT platform companies. Every JSON is import-ready: go to Settings → Import Workflow in n8n and paste.
1. Device Connectivity Health Monitor
The problem: Devices go offline. Your customers don't know until they check manually — or until it causes a real problem. You need proactive alerting that catches connectivity failures before your customers do, without polling each device in a fragile cron job.
The workflow:
- Schedule Trigger fires every 5 minutes
- Google Sheets returns the list of active customer devices (device_id, customer_id, customer_email, last_seen_ts, expected_ping_interval_seconds)
-
Code node computes each device's offline status:
-
OFFLINE: now - last_seen_ts > expected_ping_interval_seconds × 3 -
DEGRADED: now - last_seen_ts > expected_ping_interval_seconds × 1.5 -
OK: within normal range
-
- Filter keeps only OFFLINE and DEGRADED devices
-
Slack (#platform-ops):
[OFFLINE] device_id for customer_name — last seen {N} min ago -
Postgres logs to
device_incidentstable with device_id, customer_id, status, detected_at - Gmail to customer contact (if OFFLINE > 15 min): "We've detected your device {device_id} has gone offline — our team is investigating"
{
"name": "Device Connectivity Health Monitor",
"nodes": [
{"id":"1","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"minutes","minutesInterval":5}]}},"position":[100,300]},
{"id":"2","type":"n8n-nodes-base.googleSheets","parameters":{"operation":"read","documentId":"YOUR_SHEET_ID","sheetName":"active_devices"},"position":[300,300]},
{"id":"3","type":"n8n-nodes-base.code","parameters":{"jsCode":"const now = Date.now() / 1000;\nreturn $input.all().map(item => {\n const d = item.json;\n const lag = now - d.last_seen_ts;\n const interval = d.expected_ping_interval_seconds || 60;\n let status = 'OK';\n if (lag > interval * 3) status = 'OFFLINE';\n else if (lag > interval * 1.5) status = 'DEGRADED';\n return { json: { ...d, status, lag_seconds: Math.round(lag) } };\n}).filter(i => i.json.status !== 'OK');"},"position":[500,300]},
{"id":"4","type":"n8n-nodes-base.slack","parameters":{"channel":"#platform-ops","text":"={{'[' + $json.status + '] device ' + $json.device_id + ' for ' + $json.customer_name + ' — last seen ' + Math.round($json.lag_seconds/60) + 'min ago'}}"},"position":[700,300]}
],
"connections":{"1":{"main":[[{"node":"2"}]]},"2":{"main":[[{"node":"3"}]]},"3":{"main":[[{"node":"4"}]]}}
}
2. New Customer Device Onboarding Drip
The problem: When a customer provisions their first device, you need to make them successful fast. Manual follow-up gets missed. A generic "welcome email" isn't enough. You need a timed sequence that walks them from first connection to active user in 8 days.
The workflow:
-
Webhook receives
device.provisionedevent from your platform (device_id, customer_id, customer_email, customer_name, api_key) - Gmail Day 0: "Your device is online — here's your API key and quickstart guide"
- Google Sheets logs (customer_id, device_id, provisioned_at, onboarding_step='day0')
- Wait 3 days
- Gmail Day 3: "Top 3 things customers do in their first week" — integration patterns, data export, alert config
- Wait 4 days
- Gmail Day 7: "Are you getting value? Here's how to set up your first automated alert" — links to docs + store template
- Google Sheets marks onboarding_complete = true
-
Slack (#customer-success):
Onboarding complete: {customer_name} — {device_count} devices active
{
"name": "Device Onboarding Drip",
"nodes": [
{"id":"1","type":"n8n-nodes-base.webhook","parameters":{"path":"device-provisioned","httpMethod":"POST"},"position":[100,300]},
{"id":"2","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{ $json.customer_email }}","subject":"Your device {{ $json.device_id }} is online — getting started","message":"Hi {{ $json.customer_name }},\n\nYour device is provisioned and online. Your API key: {{ $json.api_key }}\n\nQuickstart: docs.yourplatform.com/quickstart"},"position":[300,300]},
{"id":"3","type":"n8n-nodes-base.wait","parameters":{"amount":3,"unit":"days"},"position":[500,300]},
{"id":"4","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{ $json.customer_email }}","subject":"3 things to try this week with your IoT platform","message":"Hi {{ $json.customer_name }},\n\nHere are the top 3 things our customers set up in their first week: 1) Real-time anomaly alerts, 2) Data export to your data warehouse, 3) Multi-device dashboards. Full guide: docs.yourplatform.com/week1"},"position":[700,300]},
{"id":"5","type":"n8n-nodes-base.wait","parameters":{"amount":4,"unit":"days"},"position":[900,300]},
{"id":"6","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"={{ $json.customer_email }}","subject":"Set up your first automated alert in 5 minutes","message":"Hi {{ $json.customer_name }},\n\nThe most common thing customers automate first: automatic Slack alert when a device goes offline. Here's the exact workflow JSON: stripeai.gumroad.com/l/djazml"},"position":[1100,300]}
],
"connections":{"1":{"main":[[{"node":"2"}]]},"2":{"main":[[{"node":"3"}]]},"3":{"main":[[{"node":"4"}]]},"4":{"main":[[{"node":"5"}]]},"5":{"main":[[{"node":"6"}]]}}
}
3. Device Anomaly Alert & Classification Pipeline
The problem: Your platform ingests millions of telemetry events. Somewhere in that stream, devices are malfunctioning — temperature spikes, pressure drops, current anomalies, connection resets. You need to catch CRITICAL events in real time, route to the right team, and notify the customer — without human review of every event.
The workflow:
- Webhook receives telemetry event from your platform (device_id, customer_id, metric_name, value, threshold, unit, timestamp)
-
Code node classifies severity:
-
CRITICAL: value > threshold × 2.0 → page on-call + email customer -
HIGH: value > threshold × 1.5 → Slack alert + log -
MEDIUM: value > threshold × 1.1 → log + daily digest
-
- Switch routes by severity
- CRITICAL branch:
-
Slack (#noc-urgent):
[CRITICAL ANOMALY] device_id: {metric_name} = {value}{unit} (threshold: {threshold})@channel - Gmail to customer: "We've detected a critical anomaly on your device — our team is investigating"
-
Postgres inserts into
device_incidentswith severity, metric, value, customer_id
-
Slack (#noc-urgent):
- HIGH branch:
-
Slack (#platform-ops):
[HIGH] device_id: {metric_name} = {value} — above threshold - Postgres logs
-
Slack (#platform-ops):
- MEDIUM branch: Postgres log only
{
"name": "Device Anomaly Alert Pipeline",
"nodes": [
{"id":"1","type":"n8n-nodes-base.webhook","parameters":{"path":"telemetry-event","httpMethod":"POST"},"position":[100,300]},
{"id":"2","type":"n8n-nodes-base.code","parameters":{"jsCode":"const d = $json;\nconst ratio = d.value / d.threshold;\nlet severity;\nif (ratio >= 2.0) severity = 'CRITICAL';\nelse if (ratio >= 1.5) severity = 'HIGH';\nelse if (ratio >= 1.1) severity = 'MEDIUM';\nelse severity = 'OK';\nreturn [{ json: { ...d, severity, ratio: ratio.toFixed(2) } }];"},"position":[300,300]},
{"id":"3","type":"n8n-nodes-base.switch","parameters":{"dataType":"string","value1":"={{ $json.severity }}","rules":{"rules":[{"value2":"CRITICAL"},{"value2":"HIGH"},{"value2":"MEDIUM"}]}},"position":[500,300]},
{"id":"4","type":"n8n-nodes-base.slack","parameters":{"channel":"#noc-urgent","text":"=<!channel> [CRITICAL ANOMALY] {{ $json.device_id }}: {{ $json.metric_name }} = {{ $json.value }}{{ $json.unit }} (threshold: {{ $json.threshold }})"},"position":[700,200]}
],
"connections":{"1":{"main":[[{"node":"2"}]]},"2":{"main":[[{"node":"3"}]]},"3":{"main":[[{"node":"4"},{"node":"4"},{"node":"4"}]]}}
}
4. Firmware Update Rollout Tracker
The problem: You push a firmware update to 10,000 devices. You need to know: what percentage has applied it? Are there stuck devices? Did any devices fail the update and need rollback? Manual spot-checks don't scale.
The workflow:
-
Webhook receives
firmware.deployment.started(deployment_id, target_device_count, firmware_version, customer_id) - Google Sheets logs deployment start (deployment_id, target, started_at, status='in_progress')
- Wait 10 minutes
-
HTTP Request to your platform API:
GET /deployments/{deployment_id}/status→ returns {devices_updated, devices_failed, devices_pending} -
Code node computes
pct_updated = devices_updated / target_device_count -
IF pct_updated >= 0.95:
-
Slack (#platform-ops):
Firmware {firmware_version} rollout complete: {pct}% updated, {failed} failed - Google Sheets updates status='complete'
-
Slack (#platform-ops):
-
ELSE (still in progress):
- IF elapsed > 2 hours AND pct_updated < 0.60: Slack (#platform-ops) WARN: rollout stalled
- Wait 10 minutes → loop back to step 4
{
"name": "Firmware Rollout Tracker",
"nodes": [
{"id":"1","type":"n8n-nodes-base.webhook","parameters":{"path":"firmware-deployment","httpMethod":"POST"},"position":[100,300]},
{"id":"2","type":"n8n-nodes-base.wait","parameters":{"amount":10,"unit":"minutes"},"position":[300,300]},
{"id":"3","type":"n8n-nodes-base.httpRequest","parameters":{"method":"GET","url":"={{ 'https://api.yourplatform.com/deployments/' + $json.deployment_id + '/status'}}","authentication":"genericCredentialType","genericAuthType":"httpHeaderAuth"},"position":[500,300]},
{"id":"4","type":"n8n-nodes-base.code","parameters":{"jsCode":"const d = $json;\nconst pct = d.devices_updated / d.target_device_count;\nreturn [{ json: { ...d, pct_updated: pct, pct_str: (pct*100).toFixed(1)+'%' } }];"},"position":[700,300]},
{"id":"5","type":"n8n-nodes-base.if","parameters":{"conditions":{"number":[{"value1":"={{ $json.pct_updated }}","operation":"largerEqual","value2":0.95}]}},"position":[900,300]},
{"id":"6","type":"n8n-nodes-base.slack","parameters":{"channel":"#platform-ops","text":"=Firmware {{ $json.firmware_version }} rollout complete: {{ $json.pct_str }} updated, {{ $json.devices_failed }} failed"},"position":[1100,200]}
],
"connections":{"1":{"main":[[{"node":"2"}]]},"2":{"main":[[{"node":"3"}]]},"3":{"main":[[{"node":"4"}]]},"4":{"main":[[{"node":"5"}]]},"5":{"main":[[{"node":"6"}],[{"node":"2"}]]}}
}
5. Weekly IoT Platform KPI Dashboard
The problem: Your CEO, VP Engineering, and CS team all need a weekly snapshot: How many devices are active? What's the uptime SLA? How many new customers provisioned devices this week? How many incidents were triggered? No one wants to dig through dashboards manually.
The workflow:
- Schedule Trigger fires every Monday at 8AM
-
Postgres query #1:
SELECT COUNT(*) active_devices, COUNT(DISTINCT customer_id) active_customers, AVG(uptime_pct) avg_uptime FROM device_health WHERE last_seen_ts > NOW() - INTERVAL '7 days' -
Postgres query #2:
SELECT COUNT(*) new_provisioned, COUNT(*) incidents_critical, COUNT(*) incidents_high FROM device_events WHERE event_ts > NOW() - INTERVAL '7 days' - Merge both result sets
-
Code node builds HTML report with WoW% comparison (using
$getWorkflowStaticData('global')for last week's numbers) - Gmail to leadership BCC: full HTML report
-
Slack (#platform): one-liner:
Weekly: {active_devices} devices active across {active_customers} customers, {incidents_critical} critical incidents, {avg_uptime}% avg uptime - Code node saves this week's numbers to static data for next week's WoW comparison
{
"name": "Weekly IoT Platform KPI Report",
"nodes": [
{"id":"1","type":"n8n-nodes-base.scheduleTrigger","parameters":{"rule":{"interval":[{"field":"weeks","weeksInterval":1,"triggerAtDay":[1],"triggerAtHour":8}]}},"position":[100,300]},
{"id":"2","type":"n8n-nodes-base.postgres","parameters":{"operation":"executeQuery","query":"SELECT COUNT(*) active_devices, COUNT(DISTINCT customer_id) active_customers, ROUND(AVG(uptime_pct)::numeric,2) avg_uptime FROM device_health WHERE last_seen_ts > NOW() - INTERVAL '7 days'"},"position":[300,300]},
{"id":"3","type":"n8n-nodes-base.code","parameters":{"jsCode":"const d = $json;\nconst prev = $getWorkflowStaticData('global');\nconst wow = (cur, p) => p ? (((cur-p)/p)*100).toFixed(1)+'%' : 'N/A';\nconst html = `<h2>IoT Platform Weekly Report</h2><table><tr><th>Metric</th><th>This Week</th><th>WoW</th></tr><tr><td>Active Devices</td><td>${d.active_devices}</td><td>${wow(d.active_devices, prev.active_devices)}</td></tr><tr><td>Avg Uptime</td><td>${d.avg_uptime}%</td><td>-</td></tr></table>`;\nreturn [{ json: { ...d, html_report: html } }];"},"position":[500,300]},
{"id":"4","type":"n8n-nodes-base.gmail","parameters":{"operation":"send","to":"leadership@yourcompany.com","subject":"=IoT Platform Weekly — {{ $now.toFormat('MMM d') }}","message":"={{ $json.html_report }}","options":{"appendAttribution":false}},"position":[700,300]},
{"id":"5","type":"n8n-nodes-base.slack","parameters":{"channel":"#platform","text":"=Weekly: {{ $json.active_devices }} devices active across {{ $json.active_customers }} customers — {{ $json.avg_uptime }}% avg uptime"},"position":[900,300]}
],
"connections":{"1":{"main":[[{"node":"2"}]]},"2":{"main":[[{"node":"3"}]]},"3":{"main":[[{"node":"4"}]]},"4":{"main":[[{"node":"5"}]]}}
}
Why IoT platforms choose self-hosted n8n
If you're routing device telemetry through Zapier or Make:
| Concern | Zapier/Make | n8n self-hosted |
|---|---|---|
| Data egress | Telemetry transits US cloud servers | Stays inside your VPC |
| Enterprise security reviews | Data processor risk | Eliminated — no third-party processor |
| GDPR Article 44-46 | International transfer risk | Data residency in your region |
| IEC 62443 / NERC CIP | Non-compliant for OT environments | Air-gapped deployment supported |
| Event volume | Rate limits hit at scale | No per-task limits |
| Custom logic | Limited code nodes | Full JS/Python Code nodes |
Where to get these workflows
All 5 workflows above are available as ready-to-import JSON at FlowKit — stripeai.gumroad.com.
The store has 11 more automation templates covering:
- Email Auto-Responder ($15)
- AI Customer Support Bot ($29)
- Lead Capture to CRM ($19)
- Appointment Reminder System ($15)
- And more
Or grab the Complete Bundle — all templates at a discount.
Built these for your own platform? Drop your use case in the comments — I read every one.
Top comments (0)