AutomationFlowsWeb Scraping › Notify (channel-abstracted)

Notify (channel-abstracted)

Notify (channel-abstracted). Uses executeWorkflowTrigger, httpRequest. Event-driven trigger; 6 nodes.

Event trigger★★★★☆ complexity6 nodesExecute Workflow TriggerHTTP Request
Web Scraping Trigger: Event Nodes: 6 Complexity: ★★★★☆ Added:

This workflow follows the Execute Workflow Trigger → 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 →

Download .json
{
  "id": "aicp-notify",
  "name": "Notify (channel-abstracted)",
  "meta": {
    "description": "Reusable notifier. Call via Execute Workflow with { severity, title, message, source }. Routes to Slack / Teams / Email / generic webhook based on the NOTIFY_CHANNEL env var, so callers never hardcode a channel."
  },
  "nodes": [
    {
      "parameters": {
        "workflowInputs": {
          "values": [
            {
              "name": "severity",
              "type": "string"
            },
            {
              "name": "title",
              "type": "string"
            },
            {
              "name": "message",
              "type": "string"
            },
            {
              "name": "source",
              "type": "string"
            },
            {
              "name": "action",
              "type": "string"
            },
            {
              "name": "metric",
              "type": "string"
            },
            {
              "name": "runUrl",
              "type": "string"
            }
          ]
        }
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000001",
      "name": "When called",
      "type": "n8n-nodes-base.executeWorkflowTrigger",
      "typeVersion": 1.1,
      "position": [
        200,
        300
      ]
    },
    {
      "parameters": {
        "jsCode": "// Assemble the payload once, here, in the shape the contract requires\n// (ai-standards/references/notification-taxonomy.md \u2014 \"The message contract\").\n// Downstream branches only reshape it to each platform's wire format.\n//\n// BLUF: title states the OUTCOME, then the action line, then the one number,\n// then supporting detail, then provenance small and last. The reader is scanning\n// a phone and deciding in ~2s whether this needs them.\nconst i = $input.first().json || {};\nconst NL = String.fromCharCode(10);\n\nconst severity = (i.severity || 'info').toLowerCase();\nconst title = (i.title || 'Notification').trim();\nconst detail = (i.message || '').trim();\nconst source = i.source || 'n8n';\nconst metric = (i.metric || '').trim();\n\n// Severity is DEFINED, not felt \u2014 same three words everywhere, or the colours\n// stop meaning anything. Each maps to exactly one reader obligation.\nconst OBLIGATION = { error: 'Act now', warn: 'Decide this week', info: 'No action' };\n// A caller may state the action explicitly (\"Act by 2026-08-24\"); otherwise it is\n// derived, so a lane that forgets still emits a contract-shaped message rather\n// than leaving the reader to infer urgency from tone.\nconst action = (i.action || '').trim() || OBLIGATION[severity] || 'No action';\nconst emoji = severity === 'error' ? '\\uD83D\\uDD34'\n            : severity === 'warn'  ? '\\uD83D\\uDFE1'\n            : '\\uD83D\\uDFE2';\n\n// Detail lives behind a link \u2014 but assume the link is never opened. Most of our\n// detail surfaces are localhost-bound and unreachable from the phone the alert\n// was read on, so the MESSAGE must carry the decision and the link only carries\n// the evidence.\nconst base = ($env.N8N_BASE_URL || 'http://localhost:5678').replace(/\\/+$/, '');\nconst runUrl = i.runUrl || (base + '/home/executions');\nconst runLabel = i.runUrl ? 'View run' : 'Open executions';\n\n// Slack hard limits: header plain_text 150, section text 3000. Truncate loudly\n// rather than letting Slack reject the whole message.\nconst clip = (s, n) => (s.length > n ? s.slice(0, n - 20) + NL + '\u2026 (truncated)' : s);\n\nconst blocks = [\n  { type: 'header',\n    text: { type: 'plain_text', text: clip(`${emoji} ${title}`, 150), emoji: true } },\n  { type: 'section',\n    text: { type: 'mrkdwn', text: `*${action}*` } },\n];\n\nif (metric) {\n  blocks.push({ type: 'section', fields: [\n    { type: 'mrkdwn', text: `*Key figure*${NL}${clip(metric, 600)}` },\n    { type: 'mrkdwn', text: `*Severity*${NL}\\`${severity}\\`` },\n  ] });\n}\nif (detail) {\n  blocks.push({ type: 'section', text: { type: 'mrkdwn', text: clip(detail, 2800) } });\n}\n// Provenance last and small. Naming the lane is what lets a noisy alert be\n// traced to a workflow and fixed rather than muted.\nblocks.push({ type: 'context', elements: [\n  { type: 'mrkdwn', text: `lane: \\`${source}\\`  \u00b7  <${runUrl}|${runLabel}>` } ]});\n\n// Fallback text: what a phone notification shows, and what Teams/webhook use.\n// It carries the decision on its own \u2014 title plus action, nothing else.\nconst text = `${emoji} ${title} \u2014 ${action}`;\n// Long form for channels without Block Kit.\nconst longText = [`${emoji} *${title}* (${severity})`, `*${action}*`,\n                  metric, detail, `_lane: ${source} \u00b7 ${runUrl}_`]\n                 .filter(Boolean).join(NL);\n\nreturn [{ json: { severity, title, message: detail, source, action, metric,\n                  runUrl, text, longText, blocks } }];"
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000002",
      "name": "Format message",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        420,
        300
      ]
    },
    {
      "parameters": {
        "rules": {
          "values": [
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "conditions": [
                  {
                    "leftValue": "={{ $env.NOTIFY_CHANNEL }}",
                    "rightValue": "slack",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "slack"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "conditions": [
                  {
                    "leftValue": "={{ $env.NOTIFY_CHANNEL }}",
                    "rightValue": "teams",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "teams"
            },
            {
              "conditions": {
                "options": {
                  "caseSensitive": false
                },
                "conditions": [
                  {
                    "leftValue": "={{ $env.NOTIFY_CHANNEL }}",
                    "rightValue": "webhook",
                    "operator": {
                      "type": "string",
                      "operation": "equals"
                    }
                  }
                ]
              },
              "outputKey": "webhook"
            }
          ]
        },
        "options": {
          "fallbackOutput": "extra"
        }
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000003",
      "name": "Route by NOTIFY_CHANNEL",
      "type": "n8n-nodes-base.switch",
      "typeVersion": 3.2,
      "position": [
        640,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.SLACK_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ JSON.stringify({ text: $json.text, blocks: $json.blocks }) }}",
        "options": {}
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000004",
      "name": "Slack",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        140
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.TEAMS_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"type\": \"message\", \"attachments\": [ { \"contentType\": \"application/vnd.microsoft.card.adaptive\", \"content\": { \"type\": \"AdaptiveCard\", \"version\": \"1.4\", \"body\": [ { \"type\": \"TextBlock\", \"weight\": \"Bolder\", \"text\": $json.title }, { \"type\": \"TextBlock\", \"wrap\": true, \"text\": $json.message }, { \"type\": \"TextBlock\", \"isSubtle\": true, \"spacing\": \"None\", \"text\": ('severity: ' + $json.severity + ' \\u00b7 source: ' + $json.source) } ] } } ] } }}",
        "options": {}
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000005",
      "name": "Teams",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        300
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $env.NOTIFY_WEBHOOK_URL }}",
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={{ { \"severity\": $json.severity, \"title\": $json.title, \"message\": $json.message, \"source\": $json.source } }}",
        "options": {}
      },
      "id": "aaaaaaaa-0000-0000-0000-000000000006",
      "name": "Generic webhook",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        900,
        460
      ]
    }
  ],
  "connections": {
    "When called": {
      "main": [
        [
          {
            "node": "Format message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Format message": {
      "main": [
        [
          {
            "node": "Route by NOTIFY_CHANNEL",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Route by NOTIFY_CHANNEL": {
      "main": [
        [
          {
            "node": "Slack",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Teams",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generic webhook",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Generic webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "settings": {},
  "active": false
}
Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

Notify (channel-abstracted). Uses executeWorkflowTrigger, httpRequest. Event-driven trigger; 6 nodes.

Source: https://github.com/jgobuilds/ai-control-plane-public/blob/main/n8n-workflows/notify.subworkflow.json — original creator credit. Request a take-down →

More Web Scraping workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

Web Scraping

02_LLM_Pipeline v1.0. Uses executeWorkflowTrigger, httpRequest, seaTable. Event-driven trigger; 65 nodes.

Execute Workflow Trigger, HTTP Request, Sea Table
Web Scraping

This template is a powerful, reusable utility for managing stateful, long-running processes. It allows a main workflow to be paused indefinitely at "checkpoints" and then be resumed by external, async

HTTP Request, Execute Workflow Trigger
Web Scraping

Upload files from any source to your account Kommo or AmoCRM with a simple and reusable workflow. It can split a large file into small ones and upload chunks. Works for Kommo and amoCRM There are 3 re

HTTP Request, Execute Workflow Trigger, Stop And Error
Web Scraping

Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.

HTTP Request, GitHub, Execute Workflow Trigger +1
Web Scraping

Remixed Backup your workflows to GitHub from Solomon's work. Check out his templates.

Execute Workflow Trigger, HTTP Request, GitHub