This workflow corresponds to n8n.io template #17902 — we link there as the canonical source.
This workflow follows the Google Sheets → Slack 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": "uRJUIbLFPc0IqnTz",
"meta": {
"templateCredsSetupCompleted": true
},
"name": "Dead Man's Switch for n8n Workflows",
"tags": [],
"nodes": [
{
"id": "08cce57e-9551-4771-a4a9-97b275a82d5c",
"name": "Receive heartbeat ping",
"type": "n8n-nodes-base.webhook",
"position": [
-336,
-112
],
"parameters": {
"path": "heartbeat",
"options": {},
"httpMethod": "POST",
"responseMode": "responseNode"
},
"typeVersion": 2.1
},
{
"id": "3b75082f-d922-4ab0-8245-dc87ae836ab5",
"name": "Set receiver config",
"type": "n8n-nodes-base.set",
"position": [
-144,
-112
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "5e9f8320-78e0-4ba4-a2da-f677dd9ed14f",
"name": "shared_secret",
"type": "string",
"value": "your-secret-here"
}
]
}
},
"typeVersion": 3.5
},
{
"id": "1ce3054c-9d9b-4580-a4b7-5b9d0f8bea16",
"name": "Validate shared secret",
"type": "n8n-nodes-base.if",
"position": [
32,
-112
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "e660a356-21f3-4fcb-bee7-b5d11d27bebf",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $('Receive heartbeat ping').first().json.headers['x-heartbeat-secret'] }}",
"rightValue": "={{ $('Set receiver config').first().json.shared_secret }}"
}
]
}
},
"typeVersion": 2.3
},
{
"id": "0ac72dbf-f9ee-4dda-a8bc-de276696efb4",
"name": "Look up job in registry",
"type": "n8n-nodes-base.googleSheets",
"position": [
464,
-128
],
"parameters": {
"options": {},
"filtersUI": {
"values": [
{
"lookupValue": "={{ $('Receive heartbeat ping').first().json.body.job_id }}",
"lookupColumn": "job_id"
}
]
},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7,
"alwaysOutputData": true
},
{
"id": "04104f85-1588-4fe5-8e40-5096c7fd4103",
"name": "Is job registered?",
"type": "n8n-nodes-base.if",
"position": [
624,
-128
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "addea103-f30d-44b9-8c6a-0647bca2d383",
"operator": {
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.job_id }}",
"rightValue": "={{ $('Receive heartbeat ping').first().json.body.job_id }}"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "386881ae-e3de-4c11-aaa2-a111efb1c6f3",
"name": "Record heartbeat",
"type": "n8n-nodes-base.googleSheets",
"position": [
1056,
-144
],
"parameters": {
"columns": {
"value": {
"job_id": "={{ $('Receive heartbeat ping').first().json.body.job_id }}",
"status": "OK",
"last_alert_at": "={{ '' }}",
"last_heartbeat": "={{ $now.toUTC().toISO() }}"
},
"schema": [
{
"id": "job_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "job_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_name",
"type": "string",
"display": true,
"required": false,
"displayName": "job_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "expected_every_minutes",
"type": "string",
"display": true,
"required": false,
"displayName": "expected_every_minutes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "grace_minutes",
"type": "string",
"display": true,
"required": false,
"displayName": "grace_minutes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_heartbeat",
"type": "string",
"display": true,
"required": false,
"displayName": "last_heartbeat",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_alert_at",
"type": "string",
"display": true,
"required": false,
"displayName": "last_alert_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "active",
"type": "string",
"display": true,
"required": false,
"displayName": "active",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alert_channel",
"type": "string",
"display": true,
"required": false,
"displayName": "alert_channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"job_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "9a77fbe4-3199-4c00-aecf-f20bffb9ea11",
"name": "Return 200 OK",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
1552,
-80
],
"parameters": {
"options": {},
"respondWith": "json",
"responseBody": "={{ JSON.stringify({ ok: true, job_id: $('Receive heartbeat ping').first().json.body.job_id, recorded_at: $now.toUTC().toISO() }) }}"
},
"typeVersion": 1.5
},
{
"id": "b1574e08-c3da-484c-ac7e-175bc4af63c1",
"name": "Return 404 unknown job",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
768,
-48
],
"parameters": {
"options": {
"responseCode": 404
},
"respondWith": "text",
"responseBody": "Unknown job_id"
},
"typeVersion": 1.5
},
{
"id": "f69a3180-f89a-4690-8d7d-89df7ca14747",
"name": "Return 401 unauthorized",
"type": "n8n-nodes-base.respondToWebhook",
"position": [
208,
-32
],
"parameters": {
"options": {
"responseCode": 401
},
"respondWith": "text",
"responseBody": "Unauthorized"
},
"typeVersion": 1.5
},
{
"id": "a7ad71d0-4aa8-4a8c-8e36-42268403616b",
"name": "Check every 15 minutes",
"type": "n8n-nodes-base.scheduleTrigger",
"position": [
-336,
464
],
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 15
}
]
}
},
"typeVersion": 1.3
},
{
"id": "e9c137f2-eeae-4e94-8a93-9c9a16d473ee",
"name": "Set checker config",
"type": "n8n-nodes-base.set",
"position": [
-160,
464
],
"parameters": {
"options": {},
"assignments": {
"assignments": [
{
"id": "5b9143a0-dea8-439b-a660-e85624d0e3be",
"name": "default_grace_minutes",
"type": "string",
"value": "5"
},
{
"id": "c033c4fd-76e9-45b6-972c-7862c3ce38c7",
"name": "re_alert_after_minutes",
"type": "string",
"value": "60"
},
{
"id": "4d017e2a-7fe6-48b4-9232-a6a022ab23f7",
"name": "default_alert_channel",
"type": "string",
"value": "#your-alert-channel"
}
]
}
},
"typeVersion": 3.5
},
{
"id": "5b3121ef-0449-44a1-a41d-525e21bb788a",
"name": "Get all monitored jobs",
"type": "n8n-nodes-base.googleSheets",
"position": [
16,
464
],
"parameters": {
"options": {},
"sheetName": {
"__rl": true,
"mode": "name",
"value": "jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "117e5310-c157-435d-a437-3f31c6e3016f",
"name": "Keep active jobs only",
"type": "n8n-nodes-base.filter",
"position": [
208,
464
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "fd18f7b7-e45f-4cfd-b4ea-16a89af8fb78",
"operator": {
"name": "filter.operator.equals",
"type": "string",
"operation": "equals"
},
"leftValue": "={{ $json.active }}",
"rightValue": "TRUE"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "f54d7c39-b63a-477f-9da1-a871d873b804",
"name": "Flag jobs past their deadline",
"type": "n8n-nodes-base.code",
"position": [
448,
464
],
"parameters": {
"jsCode": "const cfg = $('Set checker config').first().json;\nconst now = Date.now();\nconst reAlertMs = Number(cfg.re_alert_after_minutes) * 60000;\nconst out = [];\n\nfunction humanize(ms) {\n const m = Math.floor(ms / 60000);\n if (m < 60) return `${m}m`;\n const h = Math.floor(m / 60);\n if (h < 24) return `${h}h ${m % 60}m`;\n return `${Math.floor(h / 24)}d ${h % 24}h`;\n}\n\nfor (const item of $input.all()) {\n const j = item.json;\n const everyMs = Number(j.expected_every_minutes) * 60000;\n const graceMin = Number(j.grace_minutes) || Number(cfg.default_grace_minutes);\n const deadlineMs = everyMs + graceMin * 60000;\n\n const lastSeen = j.last_heartbeat ? Date.parse(j.last_heartbeat) : null;\n const lastAlert = j.last_alert_at ? Date.parse(j.last_alert_at) : null;\n\n const overdue = lastSeen === null || (now - lastSeen) > deadlineMs;\n if (!overdue) continue;\n\n if (lastAlert && (now - lastAlert) < reAlertMs) continue;\n\n out.push({\n json: {\n ...j,\n alert_type: lastSeen === null ? 'NEVER_SEEN' : 'MISSED',\n last_seen_human: lastSeen === null ? 'never' : humanize(now - lastSeen),\n overdue_by_human: lastSeen === null ? 'n/a' : humanize(now - lastSeen - deadlineMs),\n alert_channel: j.alert_channel || cfg.default_alert_channel,\n checked_at: new Date(now).toISOString(),\n },\n });\n}\n\n return out;"
},
"typeVersion": 2
},
{
"id": "9ccb4d64-e3de-4004-a071-ae0390e11e25",
"name": "Any jobs overdue?",
"type": "n8n-nodes-base.if",
"position": [
608,
464
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "strict"
},
"combinator": "and",
"conditions": [
{
"id": "a01eb885-86d2-425e-9fe9-7e81f7af36fc",
"operator": {
"type": "number",
"operation": "gt"
},
"leftValue": "={{ $input.all().length }}",
"rightValue": 0
}
]
}
},
"typeVersion": 2.3
},
{
"id": "800672e9-f596-481e-9257-a1a6f4707f8c",
"name": "Nothing overdue",
"type": "n8n-nodes-base.noOp",
"position": [
768,
592
],
"parameters": {},
"typeVersion": 1
},
{
"id": "1d77a9dd-bb1d-4471-a00d-f596ca7ee5c6",
"name": "Loop over overdue jobs",
"type": "n8n-nodes-base.splitInBatches",
"position": [
1104,
448
],
"parameters": {
"options": {},
"batchSize": 10
},
"typeVersion": 3
},
{
"id": "9dc1a218-b96d-49bc-993e-3bd69d40eee6",
"name": "Post missed-run alert",
"type": "n8n-nodes-base.slack",
"position": [
1104,
640
],
"parameters": {
"text": "={{ $json.alert_type === 'NEVER_SEEN' ? ':red_circle: *' + $json.job_name + '* has never checked in' : ':warning: *' + $json.job_name + '* missed its check-in' }} Job ID: `{{ $json.job_id }}` Last seen: {{ $json.last_seen_human }} Overdue by: {{ $json.overdue_by_human }} Expected every {{ $json.expected_every_minutes }} min",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#your-alert-channel"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "bd44a99b-7df6-4e56-b96d-e18b533b8f67",
"name": "Mark job as alerted",
"type": "n8n-nodes-base.googleSheets",
"position": [
1328,
640
],
"parameters": {
"columns": {
"value": {
"job_id": "={{ $('Loop over overdue jobs').item.json.job_id }}",
"status": "={{ $('Loop over overdue jobs').item.json.alert_type }}",
"last_alert_at": "={{ $now.toUTC().toISO() }}"
},
"schema": [
{
"id": "job_id",
"type": "string",
"display": true,
"removed": false,
"required": false,
"displayName": "job_id",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "job_name",
"type": "string",
"display": true,
"required": false,
"displayName": "job_name",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "expected_every_minutes",
"type": "string",
"display": true,
"required": false,
"displayName": "expected_every_minutes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "grace_minutes",
"type": "string",
"display": true,
"required": false,
"displayName": "grace_minutes",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_heartbeat",
"type": "string",
"display": true,
"required": false,
"displayName": "last_heartbeat",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "status",
"type": "string",
"display": true,
"required": false,
"displayName": "status",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "last_alert_at",
"type": "string",
"display": true,
"required": false,
"displayName": "last_alert_at",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "active",
"type": "string",
"display": true,
"required": false,
"displayName": "active",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "alert_channel",
"type": "string",
"display": true,
"required": false,
"displayName": "alert_channel",
"defaultMatch": false,
"canBeUsedToMatch": true
},
{
"id": "row_number",
"type": "number",
"display": true,
"removed": true,
"readOnly": true,
"required": false,
"displayName": "row_number",
"defaultMatch": false,
"canBeUsedToMatch": true
}
],
"mappingMode": "defineBelow",
"matchingColumns": [
"job_id"
],
"attemptToConvertTypes": false,
"convertFieldsToString": false
},
"options": {},
"operation": "update",
"sheetName": {
"__rl": true,
"mode": "name",
"value": "jobs"
},
"documentId": {
"__rl": true,
"mode": "id",
"value": "YOUR_SHEET_ID"
}
},
"credentials": {
"googleSheetsOAuth2Api": {
"name": "<your credential>"
}
},
"typeVersion": 4.7
},
{
"id": "5954ca11-5dc4-458c-9bd5-06c7b2135ad9",
"name": "Was job previously alerting?",
"type": "n8n-nodes-base.if",
"position": [
1200,
-144
],
"parameters": {
"options": {},
"conditions": {
"options": {
"version": 3,
"leftValue": "",
"caseSensitive": true,
"typeValidation": "loose"
},
"combinator": "and",
"conditions": [
{
"id": "4bf9602e-bd88-46b7-8b3f-5e4b3b643e20",
"operator": {
"type": "string",
"operation": "notEquals"
},
"leftValue": "={{ $('Look up job in registry').first().json.status }}",
"rightValue": "OK"
}
]
},
"looseTypeValidation": true
},
"typeVersion": 2.3
},
{
"id": "b6a06f8d-50f6-461c-8242-7c11c17097f4",
"name": "Send recovery notice",
"type": "n8n-nodes-base.slack",
"position": [
1392,
-144
],
"parameters": {
"text": "=:white_check_mark: *{{ $('Look up job in registry').first().json.job_name }}* is checking in again",
"select": "channel",
"channelId": {
"__rl": true,
"mode": "name",
"value": "#your-alert-channel"
},
"otherOptions": {
"includeLinkToWorkflow": false
}
},
"credentials": {
"slackApi": {
"name": "<your credential>"
}
},
"typeVersion": 2.5
},
{
"id": "2e3510c3-1d1d-499b-808a-72e332a6516d",
"name": "All alerts sent",
"type": "n8n-nodes-base.noOp",
"position": [
1328,
448
],
"parameters": {},
"typeVersion": 1
},
{
"id": "489301c0-a903-4b27-9316-df5e1dfce2f4",
"name": "Sticky Note",
"type": "n8n-nodes-base.stickyNote",
"position": [
-992,
96
],
"parameters": {
"color": "#EDF028",
"width": 576,
"height": 848,
"content": "## Monitor job heartbeats and get alerted when a run goes missing\n\nMost automations tell you when something happened. This one tells you\nwhen something *didn't*. It is a dead man's switch: your jobs check in,\nand silence is the alarm.\n\n### How it works\nEach monitored job sends a POST to this workflow's webhook when it\nfinishes successfully. The workflow records the timestamp against that\njob in a Google Sheet.\n\nSeparately, a schedule runs every 15 minutes, reads the sheet, and\ncompares each job's last check-in against how often it is supposed to\nrun, plus a grace period. Anything past its deadline triggers a Slack\nalert. Jobs that have never checked in are flagged too. When a missing\njob checks back in, you get a recovery notice.\n\nRepeat alerts are suppressed for a configurable window, so a job that\nis down all weekend sends a handful of messages, not hundreds.\n\n### Setup\n1. Copy the sheet template and format all columns as plain text.\n2. Add your Google Sheets and Slack credentials.\n3. Open **Set receiver config** and **Set checker config** and fill in\n your shared secret, sheet ID, and default alert channel.\n4. Add one row per job you want to watch.\n5. Activate, then have each job POST to the webhook on success.\n\n### Customization\nAdjust grace periods, alert channels, and the re-alert window per job\nin the sheet. Swap Slack for email, Telegram, or PagerDuty in one node."
},
"typeVersion": 1
},
{
"id": "486b8b66-2a5e-4454-9ef2-7a9b09161987",
"name": "Sticky Note1",
"type": "n8n-nodes-base.stickyNote",
"position": [
-624,
-224
],
"parameters": {
"color": "#AA0E0E",
"width": 208,
"height": 272,
"content": "## Set a shared secret\nThis webhook is public. Set `shared_secret` in **Set receiver config**\nand send it as an `x-heartbeat-secret` header, or anyone can spoof a\ncheck-in and silence your alerts."
},
"typeVersion": 1
},
{
"id": "d572e677-368a-4260-a59d-bffc0a0622a6",
"name": "Sticky Note2",
"type": "n8n-nodes-base.stickyNote",
"position": [
-384,
-272
],
"parameters": {
"color": "#616161",
"width": 768,
"height": 384,
"content": "## 1. Receive and authenticate\nJobs POST here when they finish successfully. The shared secret keeps\nstrangers out of your registry."
},
"typeVersion": 1
},
{
"id": "9e530a64-6171-4c6f-94f3-5104fd3c555d",
"name": "Sticky Note3",
"type": "n8n-nodes-base.stickyNote",
"position": [
416,
-272
],
"parameters": {
"color": "#616161",
"width": 528,
"height": 384,
"content": "## 2. Find the job\nMatches the incoming job_id against the sheet. Unregistered jobs get a 404 rather than a silent failure."
},
"typeVersion": 1
},
{
"id": "fd27d8de-60d7-4b95-9a76-57e83ae5138c",
"name": "Sticky Note4",
"type": "n8n-nodes-base.stickyNote",
"position": [
1008,
-272
],
"parameters": {
"color": "#616161",
"width": 736,
"height": 384,
"content": "## 3. Record and confirm\nTimestamps the check-in. If the job was previously in alert, posts a\nrecovery notice before replying."
},
"typeVersion": 1
},
{
"id": "44a95a8b-92d6-406c-90fe-0e38dcfb70ef",
"name": "Sticky Note5",
"type": "n8n-nodes-base.stickyNote",
"position": [
-384,
320
],
"parameters": {
"color": "#616161",
"width": 752,
"height": 336,
"content": "## 4. Read the registry\nRuns on a schedule and loads every job. Rows marked inactive are\nskipped, so you can mute a job during maintenance."
},
"typeVersion": 1
},
{
"id": "be5ad6ad-be51-4b12-ba4b-e7b224d2083b",
"name": "Sticky Note6",
"type": "n8n-nodes-base.stickyNote",
"position": [
416,
320
],
"parameters": {
"color": "#616161",
"width": 528,
"height": 464,
"content": "## 5. Find overdue jobs\nCompares each job's last check-in against its interval plus grace.\nJobs that never checked in are caught too. Recently alerted jobs stay quiet."
},
"typeVersion": 1
},
{
"id": "35bcb8d6-e654-4cb9-b910-ea78a9a94027",
"name": "Sticky Note7",
"type": "n8n-nodes-base.stickyNote",
"position": [
1024,
320
],
"parameters": {
"color": "#616161",
"width": 544,
"height": 608,
"content": "## 6. Alert and mark\nBatched Slack alerts, then writes back to the sheet so the same job\ndoes not alert repeatedly."
},
"typeVersion": 1
}
],
"active": true,
"settings": {
"binaryMode": "separate",
"availableInMCP": false,
"executionOrder": "v1"
},
"versionId": "c7a223d0-c2fd-449a-9b63-9174c1eaf19c",
"nodeGroups": [],
"connections": {
"Record heartbeat": {
"main": [
[
{
"node": "Was job previously alerting?",
"type": "main",
"index": 0
}
]
]
},
"Any jobs overdue?": {
"main": [
[
{
"node": "Loop over overdue jobs",
"type": "main",
"index": 0
}
],
[
{
"node": "Nothing overdue",
"type": "main",
"index": 0
}
]
]
},
"Is job registered?": {
"main": [
[
{
"node": "Record heartbeat",
"type": "main",
"index": 0
}
],
[
{
"node": "Return 404 unknown job",
"type": "main",
"index": 0
}
]
]
},
"Set checker config": {
"main": [
[
{
"node": "Get all monitored jobs",
"type": "main",
"index": 0
}
]
]
},
"Set receiver config": {
"main": [
[
{
"node": "Validate shared secret",
"type": "main",
"index": 0
}
]
]
},
"Send recovery notice": {
"main": [
[
{
"node": "Return 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Keep active jobs only": {
"main": [
[
{
"node": "Flag jobs past their deadline",
"type": "main",
"index": 0
}
]
]
},
"Post missed-run alert": {
"main": [
[
{
"node": "Loop over overdue jobs",
"type": "main",
"index": 0
},
{
"node": "Mark job as alerted",
"type": "main",
"index": 0
}
]
]
},
"Check every 15 minutes": {
"main": [
[
{
"node": "Set checker config",
"type": "main",
"index": 0
}
]
]
},
"Get all monitored jobs": {
"main": [
[
{
"node": "Keep active jobs only",
"type": "main",
"index": 0
}
]
]
},
"Loop over overdue jobs": {
"main": [
[
{
"node": "All alerts sent",
"type": "main",
"index": 0
}
],
[
{
"node": "Post missed-run alert",
"type": "main",
"index": 0
}
]
]
},
"Receive heartbeat ping": {
"main": [
[
{
"node": "Set receiver config",
"type": "main",
"index": 0
}
]
]
},
"Validate shared secret": {
"main": [
[
{
"node": "Look up job in registry",
"type": "main",
"index": 0
}
],
[
{
"node": "Return 401 unauthorized",
"type": "main",
"index": 0
}
]
]
},
"Look up job in registry": {
"main": [
[
{
"node": "Is job registered?",
"type": "main",
"index": 0
}
]
]
},
"Was job previously alerting?": {
"main": [
[
{
"node": "Send recovery notice",
"type": "main",
"index": 0
}
],
[
{
"node": "Return 200 OK",
"type": "main",
"index": 0
}
]
]
},
"Flag jobs past their deadline": {
"main": [
[
{
"node": "Any jobs overdue?",
"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.
googleSheetsOAuth2ApislackApi
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
About this workflow
This workflow implements a dead man’s switch for scheduled jobs by recording heartbeat check-ins via a web-hook to Google Sheets and alerting to Slack when a job misses its expected run window, with optional recovery notifications when the job starts checking in again. Receives…
Source: https://n8n.io/workflows/17902/ — 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 a complete, production-ready solution for recovering abandoned carts in Shopify stores using a multi-channel, multi-touch approach. It automates personalized follow-ups via Email, SMS
Turn every sales meeting into a coaching opportunity. This workflow automatically analyzes tldv meeting recordings using OpenAI (GPT-4) to provide instant, actionable feedback to your sales team.
Automated video processing system that monitors S3 for new uploads, generates thumbnails and preview clips, extracts metadata, transcodes to multiple formats, and distributes to CDN with webhook notif
Eliminate manual data entry from your accounts payable process. This workflow transforms raw invoice scans into structured financial records by combining UploadToURL for hosting, AWS Textract for OCR
Connect Fireflies and WayinVideo to this workflow once and every recorded sales call automatically generates a set of training clips delivered to your Slack channel. The moment Fireflies finishes tran