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 →
{
"name": "Uptime Monitor with Slack and Telegram Alerts",
"nodes": [
{
"parameters": {
"content": "## Uptime Monitor with Alerts\n\nSchedule-driven HTTP health check across multiple targets. Detects state changes (up to down, down to up) and alerts to Slack + Telegram only on transitions, not on every cycle.\n\nNo memory, no LLM. Free to run.",
"height": 240,
"width": 380,
"color": 6
},
"id": "note-intro",
"name": "Sticky Note - Intro",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-200,
-100
]
},
{
"parameters": {
"content": "### >> SET ME <<\n\n1. Set `MONITOR_TARGETS` env var as JSON array, e.g.:\n `[{\"name\":\"api\",\"url\":\"https://api.example.com/health\",\"expect_status\":200},{\"name\":\"site\",\"url\":\"https://example.com\",\"expect_status\":200}]`\n2. Set `SLACK_OPS_WEBHOOK` for Slack alerts.\n3. Set `TELEGRAM_BOT_TOKEN` + `TELEGRAM_CHAT_ID` for Telegram alerts (optional).\n4. Adjust schedule cadence in the Schedule Trigger (default every 5 minutes).",
"height": 280,
"width": 380,
"color": 5
},
"id": "note-setup",
"name": "Sticky Note - Setup",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
-200,
200
]
},
{
"parameters": {
"rule": {
"interval": [
{
"field": "minutes",
"minutesInterval": 5
}
]
}
},
"id": "uptime-1-trigger",
"name": "Schedule Trigger",
"type": "n8n-nodes-base.scheduleTrigger",
"typeVersion": 1.2,
"position": [
240,
60
]
},
{
"parameters": {
"jsCode": "// Read targets from MONITOR_TARGETS env. Expected JSON array of:\n// {name, url, expect_status, timeout_ms?}\n\nconst raw = $env.MONITOR_TARGETS || '[]';\nlet targets;\ntry {\n targets = JSON.parse(raw);\n} catch (e) {\n throw new Error('MONITOR_TARGETS env must be valid JSON array, got: ' + raw.slice(0, 100));\n}\n\nif (!Array.isArray(targets) || targets.length === 0) {\n return [{ json: { skipped: true, reason: 'no targets configured' } }];\n}\n\n// Each target becomes one item in the next node\nreturn targets.map(t => ({ json: {\n name: t.name || t.url,\n url: t.url,\n expectStatus: t.expect_status || 200,\n timeoutMs: t.timeout_ms || 10000,\n} }));"
},
"id": "uptime-2-load-targets",
"name": "Load Targets",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
440,
60
]
},
{
"parameters": {
"method": "GET",
"url": "={{ $json.url }}",
"options": {
"timeout": 10000,
"redirect": {
"redirect": {
"followRedirects": true,
"maxRedirects": 3
}
}
}
},
"id": "uptime-3-check",
"name": "HTTP Health Check",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
640,
60
],
"onError": "continueErrorOutput",
"retryOnFail": true,
"maxTries": 3,
"waitBetweenTries": 2000
},
{
"parameters": {
"jsCode": "// Health check returned 2xx (or matched expected status). State = up.\n\nconst input = $input.first();\nconst target = ($('Load Targets').first() && $('Load Targets').first().json) || {};\nconst statusCode = (input.json && input.json.statusCode) || 200;\n\nreturn [{ json: {\n name: target.name || 'unknown',\n url: target.url || 'unknown',\n state: 'up',\n statusCode,\n checkedAt: new Date().toISOString(),\n} }];"
},
"id": "uptime-4a-up",
"name": "Mark Up",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
840,
-60
]
},
{
"parameters": {
"jsCode": "// Health check failed (network error, non-2xx, timeout). State = down.\n\nconst input = $input.first();\nconst target = ($('Load Targets').first() && $('Load Targets').first().json) || {};\nconst err = (input.json && input.json.error) || {};\n\nreturn [{ json: {\n name: target.name || 'unknown',\n url: target.url || 'unknown',\n state: 'down',\n statusCode: (input.json && input.json.statusCode) || 0,\n errorMessage: err.message || 'request failed',\n checkedAt: new Date().toISOString(),\n} }];"
},
"id": "uptime-4b-down",
"name": "Mark Down",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
840,
200
]
},
{
"parameters": {
"jsCode": "// State-change detection: alert only on transitions.\n// We persist the last known state per target in $getWorkflowStaticData.\n// Same target hitting the same state on 5 consecutive cycles = no alert spam.\n\nconst input = $input.first().json;\nconst data = $getWorkflowStaticData('global');\ndata.lastStates = data.lastStates || {};\nconst lastStates = data.lastStates;\n\nconst key = input.name;\nconst prevState = lastStates[key] || null;\nconst currState = input.state;\n\nlet transition = null;\nif (prevState && prevState !== currState) {\n transition = `${prevState}-to-${currState}`;\n}\n\nlastStates[key] = currState;\n\nreturn [{ json: {\n ...input,\n prevState,\n transition,\n shouldAlert: !!transition,\n} }];"
},
"id": "uptime-5-state-change",
"name": "State Change Detector",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1040,
60
]
},
{
"parameters": {
"conditions": {
"options": {
"caseSensitive": true,
"typeValidation": "loose",
"version": 2
},
"combinator": "and",
"conditions": [
{
"leftValue": "={{ $json.shouldAlert }}",
"rightValue": true,
"operator": {
"type": "boolean",
"operation": "true"
}
}
]
}
},
"id": "uptime-6-if-alert",
"name": "Should Alert?",
"type": "n8n-nodes-base.if",
"typeVersion": 2.2,
"position": [
1240,
60
]
},
{
"parameters": {
"method": "POST",
"url": "={{ $env.SLACK_OPS_WEBHOOK }}",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ text: ($json.state === 'down' ? ':rotating_light: ' : ':white_check_mark: ') + ($json.name || 'target') + ' is now ' + ($json.state || 'unknown').toUpperCase() + ' (was ' + ($json.prevState || 'unknown') + '). URL: ' + ($json.url || '') + ($json.errorMessage ? '. Error: ' + $json.errorMessage : '') }) }}",
"options": {}
},
"id": "uptime-7a-slack",
"name": "Slack Alert",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1440,
-60
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"method": "POST",
"url": "=https://api.telegram.org/bot{{ $env.TELEGRAM_BOT_TOKEN }}/sendMessage",
"sendBody": true,
"specifyBody": "json",
"jsonBody": "={{ JSON.stringify({ chat_id: $env.TELEGRAM_CHAT_ID, text: ($json.state === 'down' ? '\ud83d\udea8 ' : '\u2705 ') + ($json.name || 'target') + ' is now ' + ($json.state || 'unknown').toUpperCase() + ' (was ' + ($json.prevState || 'unknown') + '). ' + ($json.url || '') + ($json.errorMessage ? ' Error: ' + $json.errorMessage : ''), parse_mode: 'HTML' }) }}",
"options": {}
},
"id": "uptime-7b-telegram",
"name": "Telegram Alert",
"type": "n8n-nodes-base.httpRequest",
"typeVersion": 4.2,
"position": [
1440,
180
],
"onError": "continueErrorOutput"
},
{
"parameters": {
"jsCode": "// Fallback for alert delivery failure. Log structured error, do not crash workflow.\nconst err = ($input.first().json && $input.first().json.error) || {};\nreturn [{ json: {\n ok: false,\n fallback: true,\n errorMessage: err.message || 'alert delivery failed',\n errorName: err.name || 'AlertDeliveryError',\n loggedAt: new Date().toISOString(),\n} }];"
},
"id": "uptime-err-fallback",
"name": "Error Fallback",
"type": "n8n-nodes-base.code",
"typeVersion": 2,
"position": [
1640,
360
]
},
{
"parameters": {
"content": "## Production Patterns\n\n- **Retry-with-backoff:** HTTP Health Check has `retryOnFail=true`, 3 attempts, 2s wait between. Catches transient blips.\n- **State-change-only alerting:** alerts fire on transitions (up to down, down to up), not on every cycle. Persists state in `$getWorkflowStaticData`.\n- **Error branch (always on):** Slack + Telegram delivery failure does not crash workflow. Falls through to structured error log.\n- **Schedule trigger throttle:** built-in. n8n schedule trigger does not retry on missed runs.",
"height": 280,
"width": 380,
"color": 7
},
"id": "note-production-patterns",
"name": "Sticky Note - Production Patterns",
"type": "n8n-nodes-base.stickyNote",
"typeVersion": 1,
"position": [
840,
-260
]
}
],
"connections": {
"Schedule Trigger": {
"main": [
[
{
"node": "Load Targets",
"type": "main",
"index": 0
}
]
]
},
"Load Targets": {
"main": [
[
{
"node": "HTTP Health Check",
"type": "main",
"index": 0
}
]
]
},
"HTTP Health Check": {
"main": [
[
{
"node": "Mark Up",
"type": "main",
"index": 0
}
],
[
{
"node": "Mark Down",
"type": "main",
"index": 0
}
]
]
},
"Mark Up": {
"main": [
[
{
"node": "State Change Detector",
"type": "main",
"index": 0
}
]
]
},
"Mark Down": {
"main": [
[
{
"node": "State Change Detector",
"type": "main",
"index": 0
}
]
]
},
"State Change Detector": {
"main": [
[
{
"node": "Should Alert?",
"type": "main",
"index": 0
}
]
]
},
"Should Alert?": {
"main": [
[
{
"node": "Slack Alert",
"type": "main",
"index": 0
},
{
"node": "Telegram Alert",
"type": "main",
"index": 0
}
]
]
},
"Slack Alert": {
"main": [
[],
[
{
"node": "Error Fallback",
"type": "main",
"index": 0
}
]
]
},
"Telegram Alert": {
"main": [
[],
[
{
"node": "Error Fallback",
"type": "main",
"index": 0
}
]
]
}
},
"settings": {
"executionOrder": "v1"
}
}
For the full experience including quality scoring and batch install features for each workflow upgrade to Pro
How this works
Ensure your essential websites stay online and receive instant notifications when issues arise, saving you from costly downtime and manual checks. This workflow performs automated health checks on your specified URLs and sends alerts via Slack and Telegram if any site becomes unresponsive, empowering web developers, IT teams, and business owners to maintain reliable digital presence without constant monitoring. The key step involves the HTTP request node that probes each URL for a valid response, triggering notifications only on detected changes in status.
Use this workflow for monitoring a handful of critical sites like your company homepage or API endpoints, especially in small to medium setups where real-time alerts prevent minor glitches from escalating. Avoid it for high-volume monitoring requiring advanced analytics or custom dashboards, as it focuses on basic uptime detection rather than detailed performance metrics. Common variations include adding email alerts via Gmail integration or expanding checks to include response time thresholds for more granular oversight.
About this workflow
Uptime Monitor with Slack and Telegram Alerts. Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 13 nodes.
Source: https://github.com/studiomeyer-io/n8n-workflows/blob/main/templates/03-uptime-monitor-with-alerts/workflow.json — 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.
WF-Main - XHS 主控制器. Uses scheduleTrigger, httpRequest, executeWorkflow, noOp. Scheduled trigger; 21 nodes.
Dm-Profile-Visitors. Uses httpRequest, googleSheets. Scheduled trigger; 21 nodes.
RSS to Multi-Channel Social (X / LinkedIn / Discord). Uses stickyNote, scheduleTrigger, httpRequest. Scheduled trigger; 19 nodes.
YouTube Channel to Notion. Uses stickyNote, scheduleTrigger, httpRequest, noOp. Scheduled trigger; 18 nodes.
Automate Droplet Snapshots On Digitalocean. Uses httpRequest, stickyNote. Scheduled trigger; 17 nodes.