{
  "id": "oc1Ugtl0k5jdeLWX",
  "name": "Notify Slack on workflow errors, with a Discord fallback",
  "tags": [],
  "nodes": [
    {
      "id": "pub002-sticky-main",
      "name": "Sticky Note: Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        -1136
      ],
      "parameters": {
        "width": 2900,
        "height": 520,
        "content": "## Error notifier with dedup and a fallback channel\n\nAttach this workflow as the \"Error Workflow\" (Settings \u2192 Error Workflow) on any of your other n8n workflows. When one of them fails, this workflow receives the error via n8n's built-in Error Trigger, skips duplicate notifications for the same execution, and posts a readable summary to Slack. If the Slack post itself fails (bad URL, Slack outage, etc.), it automatically falls back to Discord instead of failing silently.\n\n### Before you activate\n1. Create a Slack Incoming Webhook (no Slack app/OAuth needed) and paste its URL into the \"Set: Slack webhook URL\" node.\n2. Create a Discord Incoming Webhook (Channel Settings \u2192 Integrations \u2192 Webhooks) and paste its URL into the \"Set: Discord webhook URL\" node.\n3. Activate this workflow, then open the workflow(s) you want monitored \u2192 Settings \u2192 Error Workflow \u2192 select this workflow.\n\n### Note on deduplication\nDeduplication uses n8n's workflow static data, which is only persisted for executions triggered automatically (not manual test runs) \u2014 this is expected and matches how Error Workflows actually run in production."
      },
      "typeVersion": 1
    },
    {
      "id": "pub002-sticky-step1",
      "name": "Sticky Note: Step 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -896,
        -528
      ],
      "parameters": {
        "width": 650,
        "height": 220,
        "content": "Step 1 \u2014 Detect & de-duplicate\n\nReceives the error from n8n's Error Trigger, extracts the workflow name, failing node, error message and execution URL, and checks whether this exact execution was already notified (skips repeat notifications from multi-failure scenarios)."
      },
      "typeVersion": 1
    },
    {
      "id": "pub002-sticky-step2",
      "name": "Sticky Note: Step 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        -528
      ],
      "parameters": {
        "width": 650,
        "height": 220,
        "content": "Step 2 \u2014 Notify Slack\n\nPosts a formatted error summary to Slack via an Incoming Webhook. If the request fails for any reason, control passes to the fallback step instead of the notification being silently lost."
      },
      "typeVersion": 1
    },
    {
      "id": "pub002-sticky-step3",
      "name": "Sticky Note: Step 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        656,
        -528
      ],
      "parameters": {
        "width": 650,
        "height": 220,
        "content": "Step 3 \u2014 Fallback to Discord\n\nOnly runs if the Slack notification failed. Posts the same error summary to a Discord channel via an Incoming Webhook, so a broken Slack integration never means you hear nothing about a failure."
      },
      "typeVersion": 1
    },
    {
      "id": "pub002-error-trigger",
      "name": "Error Trigger",
      "type": "n8n-nodes-base.errorTrigger",
      "position": [
        -800,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "pub002-check-dup",
      "name": "Check duplicate & summarize error",
      "type": "n8n-nodes-base.code",
      "notes": "Uses $getWorkflowStaticData('global') to remember which execution IDs have already been notified. Static data only persists for automatically-triggered executions (not manual test runs) \u2014 confirmed against docs.n8n.io, 2026-08-12. That's fine here since Error Workflows only ever run automatically in real use.",
      "position": [
        -560,
        0
      ],
      "parameters": {
        "jsCode": "const data = $input.first().json;\nconst staticData = $getWorkflowStaticData('global');\nif (!Array.isArray(staticData.notifiedExecutionIds)) {\n  staticData.notifiedExecutionIds = [];\n}\n\nconst executionId = String((data.execution && data.execution.id) || '');\nconst alreadyNotified = Boolean(executionId) && staticData.notifiedExecutionIds.includes(executionId);\n\nif (!alreadyNotified && executionId) {\n  staticData.notifiedExecutionIds.push(executionId);\n  // Keep static data well under the 64KB cap by only remembering the most recent 200 execution IDs.\n  if (staticData.notifiedExecutionIds.length > 200) {\n    staticData.notifiedExecutionIds = staticData.notifiedExecutionIds.slice(-200);\n  }\n}\n\nconst workflowName = (data.workflow && data.workflow.name) || 'Unknown workflow';\nconst errorMessage = (data.execution && data.execution.error && data.execution.error.message) || 'No error message provided';\nconst lastNode = (data.execution && data.execution.lastNodeExecuted) || 'Unknown node';\nconst mode = (data.execution && data.execution.mode) || 'unknown';\nconst executionUrl = (data.execution && data.execution.url) || '';\n\nreturn [{\n  json: { alreadyNotified, executionId, workflowName, errorMessage, lastNode, mode, executionUrl }\n}];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "pub002-if-dup",
      "name": "Already notified?",
      "type": "n8n-nodes-base.if",
      "position": [
        -320,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "pub002-cond-dup",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.alreadyNotified === true }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "pub002-noop-skip",
      "name": "Skip: already notified for this execution",
      "type": "n8n-nodes-base.noOp",
      "position": [
        -64,
        304
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "pub002-set-slack-url",
      "name": "Set: Slack webhook URL",
      "type": "n8n-nodes-base.set",
      "notes": "Placeholder value \u2014 not a real webhook URL. Create your own Slack Incoming Webhook and paste the URL here before activating.",
      "position": [
        -64,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-slack-url",
              "name": "slackWebhookUrl",
              "type": "string",
              "value": "https://hooks.slack.com/services/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"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "pub002-build-slack",
      "name": "Build Slack message",
      "type": "n8n-nodes-base.code",
      "position": [
        208,
        0
      ],
      "parameters": {
        "jsCode": "const config = $input.first().json;\nconst err = $('Check duplicate & summarize error').first().json;\n\nconst lines = [\n  `:rotating_light: *${err.workflowName}* failed`,\n  `*Node:* ${err.lastNode}`,\n  `*Error:* ${err.errorMessage}`\n];\nif (err.executionUrl) lines.push(`*Execution:* ${err.executionUrl}`);\nlines.push(`*Mode:* ${err.mode}`);\n\nreturn [{ json: { slackWebhookUrl: config.slackWebhookUrl, slackRequestBody: { text: lines.join('\\n') } } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "pub002-notify-slack",
      "name": "Notify Slack",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        448,
        0
      ],
      "parameters": {
        "url": "={{ $json.slackWebhookUrl }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $json.slackRequestBody }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "pub002-if-slack-ok",
      "name": "Slack notify succeeded?",
      "type": "n8n-nodes-base.if",
      "notes": "Checks the actual response body, not just the absence of a thrown error. Slack's Incoming Webhook returns the literal text \"ok\" on success (confirmed via a real test send, 2026-08-12). An invalid/malformed webhook URL does NOT reliably produce an HTTP error \u2014 it can redirect to Slack's own developer docs page (HTTP 200, HTML body), which a check that only looks for $json.error would wrongly treat as success. This was an actual bug found during testing.",
      "position": [
        688,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "pub002-cond-slack-ok",
              "operator": {
                "type": "string",
                "operation": "equals"
              },
              "leftValue": "={{ $json.error ? 'error' : $json.data }}",
              "rightValue": "ok"
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "pub002-noop-done-slack",
      "name": "Done: Slack notified",
      "type": "n8n-nodes-base.noOp",
      "position": [
        928,
        -144
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "pub002-set-discord-url",
      "name": "Set: Discord webhook URL",
      "type": "n8n-nodes-base.set",
      "notes": "Placeholder value \u2014 not a real webhook URL. In Discord: Channel Settings \u2192 Integrations \u2192 Webhooks \u2192 New Webhook \u2192 Copy Webhook URL, then paste it here before activating.",
      "position": [
        928,
        160
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "cfg-discord-url",
              "name": "discordWebhookUrl",
              "type": "string",
              "value": "https://discord.com/api/webhooks/1536816493516300288/-DvmQxU_DYfaXCxuhLxVMGVAvGjNSVPlhr6PSdhO7rZybRbfT5FZGUAFaCJeaomu8LNV"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "pub002-build-discord",
      "name": "Build Discord message",
      "type": "n8n-nodes-base.code",
      "position": [
        1168,
        160
      ],
      "parameters": {
        "jsCode": "const config = $input.first().json;\nconst err = $('Check duplicate & summarize error').first().json;\n\nconst lines = [\n  `\ud83d\udea8 **${err.workflowName}** failed`,\n  `Node: ${err.lastNode}`,\n  `Error: ${err.errorMessage}`\n];\nif (err.executionUrl) lines.push(`Execution: ${err.executionUrl}`);\nlines.push(`Mode: ${err.mode}`);\n\nreturn [{ json: { discordWebhookUrl: config.discordWebhookUrl, discordRequestBody: { content: lines.join('\\n') } } }];\n"
      },
      "typeVersion": 2
    },
    {
      "id": "pub002-notify-discord",
      "name": "Notify Discord (fallback)",
      "type": "n8n-nodes-base.httpRequest",
      "notes": "If this also fails, the execution list in n8n itself remains the last resort record of the failure \u2014 there is no further fallback beyond Discord in this template.",
      "position": [
        1408,
        160
      ],
      "parameters": {
        "url": "={{ $json.discordWebhookUrl }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ $json.discordRequestBody }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2,
      "continueOnFail": true
    },
    {
      "id": "pub002-noop-done-discord",
      "name": "Done: Discord fallback notified",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1648,
        160
      ],
      "parameters": {},
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "c57f15b0-a54a-4488-a41d-c6179d0b1bc4",
  "nodeGroups": [],
  "connections": {
    "Notify Slack": {
      "main": [
        [
          {
            "node": "Slack notify succeeded?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Error Trigger": {
      "main": [
        [
          {
            "node": "Check duplicate & summarize error",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Already notified?": {
      "main": [
        [
          {
            "node": "Skip: already notified for this execution",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set: Slack webhook URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Slack message": {
      "main": [
        [
          {
            "node": "Notify Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Discord message": {
      "main": [
        [
          {
            "node": "Notify Discord (fallback)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Slack webhook URL": {
      "main": [
        [
          {
            "node": "Build Slack message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Slack notify succeeded?": {
      "main": [
        [
          {
            "node": "Done: Slack notified",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set: Discord webhook URL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set: Discord webhook URL": {
      "main": [
        [
          {
            "node": "Build Discord message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Notify Discord (fallback)": {
      "main": [
        [
          {
            "node": "Done: Discord fallback notified",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check duplicate & summarize error": {
      "main": [
        [
          {
            "node": "Already notified?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}