AutomationFlowsSlack & Telegram › Monitor Competitor Web Page Changes with Http and Slack or Discord

Monitor Competitor Web Page Changes with Http and Slack or Discord

ByRuben Fabioux @rubenfabioux on n8n.io

This workflow checks a list of competitor web pages every six hours, detects content changes by hashing cleaned page text, and posts a diff-style alert to a Discord or Slack channel via an incoming webhook. Runs every 6 hours on a schedule. Loads a configured list of target…

Cron / scheduled trigger★★★★☆ complexity10 nodesHTTP Request
Slack & Telegram Trigger: Cron / scheduled Nodes: 10 Complexity: ★★★★☆ Added:

This workflow corresponds to n8n.io template #17024 — we link there as the canonical source.

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": "oxxn77jFKXYea7h1",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Competitor Monitor for n8n (Free)",
  "tags": [],
  "nodes": [
    {
      "id": "cd698c35-4549-490a-9df9-085558321261",
      "name": "Sticky Main",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2512,
        1376
      ],
      "parameters": {
        "color": 3,
        "width": 480,
        "height": 592,
        "content": "## Competitor Monitor (Free)\nWatches competitor web pages and alerts you on **Discord/Slack** when they change - raw diff, no AI.\n\n**How it works**\nFetch each page -> detect text changes -> post the diff to your webhook.\n\n**How to set up**\nOpen the **Targets** node (yellow, section 1) and paste your webhook URL + the pages to watch. That is the only node to edit.\n\n**Customization**\nChange the schedule interval, or add more target pages.\n\n_Want AI-written summaries + CSS-selector targeting? See the Pro version:_\nhttps://rubenfabioux.vercel.app"
      },
      "typeVersion": 1
    },
    {
      "id": "e83d7a2a-dc15-49fa-ac56-f4436ee51650",
      "name": "Sticky Section 1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3040,
        1456
      ],
      "parameters": {
        "color": 5,
        "width": 500,
        "height": 400,
        "content": "## 1. Schedule & targets\nRuns automatically **every 6 hours** (editable), then loads your list of competitor pages and your webhook URL from the **Targets** node. Each target becomes one item in the flow."
      },
      "typeVersion": 1
    },
    {
      "id": "2785a936-37bc-4b8f-9321-f2f25d096088",
      "name": "Sticky Section 2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        3536,
        1456
      ],
      "parameters": {
        "color": 5,
        "width": 532,
        "height": 400,
        "content": "## 2. Check each page\nDownloads each page's HTML, strips it to plain text, **hashes** it, and compares it to the last run. First run stores a baseline; after that it builds a raw **line-by-line diff** of new/removed lines."
      },
      "typeVersion": 1
    },
    {
      "id": "a87a10ff-c216-4d23-af0a-617b4836b4a2",
      "name": "Sticky Section 3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        4064,
        1456
      ],
      "parameters": {
        "color": 5,
        "width": 500,
        "height": 400,
        "content": "## 3. Send alert\n**Alert Needed?** checks whether a change was found. If yes, the raw diff is posted to your **Discord/Slack webhook**. No change means nothing is sent."
      },
      "typeVersion": 1
    },
    {
      "id": "d8477f19-b371-44c4-8f42-d9354928adc9",
      "name": "Every 6 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        3104,
        1664
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4ebe5573-8beb-45b5-81f0-8c8887f47479",
      "name": "Targets",
      "type": "n8n-nodes-base.code",
      "position": [
        3328,
        1664
      ],
      "parameters": {
        "jsCode": "// ============================================================\n// Free version - raw diff alerts only. For AI-written summaries and\n// CSS selector targeting, see the Pro version:\n// https://rubenfabioux.vercel.app\n// ============================================================\n// CONFIGURATION - this is the ONLY place to edit\n// ============================================================\n\n// 1) Paste your Discord or Slack webhook URL here\nconst WEBHOOK_URL = \"PASTE_YOUR_WEBHOOK_URL_HERE\";\n\n// 2) List of competitor pages to watch\nconst TARGETS = [\n  { name: \"Competitor A - Pricing\", url: \"https://example.com/pricing\" },\n  { name: \"Competitor B - Blog\",    url: \"https://example.org/blog\" }\n];\n\n// ============================================================\nreturn TARGETS.map(t => ({ json: { ...t, webhookUrl: WEBHOOK_URL } }));"
      },
      "typeVersion": 2
    },
    {
      "id": "3e406933-5a67-439f-99db-9547ebd48b06",
      "name": "Fetch Page",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        3616,
        1664
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 20000,
          "response": {
            "response": {
              "responseFormat": "text"
            }
          }
        },
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0 Safari/537.36"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.9"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "e1f357c2-a55d-476d-94af-4c74c66cbb8b",
      "name": "Detect Changes",
      "type": "n8n-nodes-base.code",
      "position": [
        3824,
        1664
      ],
      "parameters": {
        "mode": "runOnceForEachItem",
        "jsCode": "// Get the matching target config\nconst target = $('Targets').item.json;\nconst sd = $getWorkflowStaticData('global');\nsd.pages = sd.pages || {};\n\n// --- Network error / site blocking the request ---\nif ($json.error) {\n  const msg = `\u26a0\ufe0f **${target.name}** - could not fetch the page.\\n${target.url}\\nError: ${$json.error.message || 'unknown'}`;\n  return { json: { ...target, notify: true, message: msg } };\n}\n\n// --- HTML cleanup -> plain text ---\nlet html = String($json.data || '');\nhtml = html\n  .replace(/<script[\\s\\S]*?<\\/script>/gi, ' ')\n  .replace(/<style[\\s\\S]*?<\\/style>/gi, ' ')\n  .replace(/<noscript[\\s\\S]*?<\\/noscript>/gi, ' ')\n  .replace(/<[^>]+>/g, '\\n');\nlet text = html\n  .replace(/&nbsp;/gi, ' ')\n  .replace(/&amp;/gi, '&')\n  .replace(/&#\\d+;/g, ' ')\n  .replace(/[ \\t]+/g, ' ');\nconst lines = text.split('\\n').map(l => l.trim()).filter(l => l.length > 2);\ntext = lines.join('\\n');\n\n// --- FNV-1a hash (pure JS, no dependency) ---\nlet h = 2166136261;\nfor (let i = 0; i < text.length; i++) {\n  h ^= text.charCodeAt(i);\n  h = Math.imul(h, 16777619);\n}\nconst hash = (h >>> 0).toString(16);\n\nconst prev = sd.pages[target.name];\nsd.pages[target.name] = { hash, text, lastChecked: new Date().toISOString() };\n\n// --- First run: store baseline, no alert ---\nif (!prev) {\n  return { json: { ...target, notify: false, firstRun: true } };\n}\n\n// --- No change ---\nif (prev.hash === hash) {\n  return { json: { ...target, notify: false } };\n}\n\n// --- Change detected: line-by-line diff ---\nconst oldLines = new Set(prev.text.split('\\n'));\nconst newLines = new Set(text.split('\\n'));\nconst added = [...newLines].filter(l => !oldLines.has(l)).slice(0, 15);\nconst removed = [...oldLines].filter(l => !newLines.has(l)).slice(0, 15);\n\n// Clean truncation: cut at the last word/line boundary before max, add ... if truncated\nfunction clip(s, max) {\n  s = String(s == null ? '' : s);\n  if (s.length <= max) return s;\n  var cut = s.slice(0, max - 1);\n  var nl = cut.lastIndexOf(String.fromCharCode(10));\n  var sp = cut.lastIndexOf(' ');\n  var b = Math.max(nl, sp);\n  if (b > max * 0.6) cut = cut.slice(0, b);\n  return cut.trimEnd() + String.fromCharCode(8230);\n}\n\nlet msg = `\ud83d\udd14 **${target.name}** has changed!\\n${target.url}\\n`;\nif (added.length) msg += `\\n\u2795 **New:**\\n${added.map(l => '\u2022 ' + l.slice(0, 120)).join('\\n')}\\n`;\nif (removed.length) msg += `\\n\u2796 **Removed:**\\n${removed.map(l => '\u2022 ' + l.slice(0, 120)).join('\\n')}`;\n\nreturn { json: { ...target, notify: true, message: clip(msg, 1900) } };"
      },
      "typeVersion": 2
    },
    {
      "id": "57a33f2c-f2b2-4f54-b11d-29a9a53e3e40",
      "name": "Alert Needed?",
      "type": "n8n-nodes-base.if",
      "position": [
        4112,
        1664
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 1,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "c1",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.notify }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2
    },
    {
      "id": "00f855f4-2a48-4938-84a0-8933c76cd9d4",
      "name": "Send Alert",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        4336,
        1664
      ],
      "parameters": {
        "url": "={{ $json.webhookUrl }}",
        "method": "POST",
        "options": {},
        "jsonBody": "={{ JSON.stringify({ content: String($json.message).slice(0, 2000), text: String($json.message).slice(0, 2000) }) }}",
        "sendBody": true,
        "specifyBody": "json"
      },
      "typeVersion": 4.2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "b6a83884-356c-465f-8c71-0f54d8f96781",
  "nodeGroups": [],
  "connections": {
    "Targets": {
      "main": [
        [
          {
            "node": "Fetch Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Page": {
      "main": [
        [
          {
            "node": "Detect Changes",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Alert Needed?": {
      "main": [
        [
          {
            "node": "Send Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 6 hours": {
      "main": [
        [
          {
            "node": "Targets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect Changes": {
      "main": [
        [
          {
            "node": "Alert Needed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}
Pro

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

About this workflow

This workflow checks a list of competitor web pages every six hours, detects content changes by hashing cleaned page text, and posts a diff-style alert to a Discord or Slack channel via an incoming webhook. Runs every 6 hours on a schedule. Loads a configured list of target…

Source: https://n8n.io/workflows/17024/ — original creator credit. Request a take-down →

More Slack & Telegram workflows → · Browse all categories →

Related workflows

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

Slack & Telegram

Auto Hunt. Uses httpRequest, googleSheets, rssFeedRead, telegram. Scheduled trigger; 78 nodes.

HTTP Request, Google Sheets, RSS Feed Read +1
Slack & Telegram

Auto-Hunt. Uses httpRequest, googleSheets, rssFeedRead, telegram. Scheduled trigger; 78 nodes.

HTTP Request, Google Sheets, RSS Feed Read +1
Slack & Telegram

debug. Uses httpRequest, slack, redis, mailgun. Scheduled trigger; 60 nodes.

HTTP Request, Slack, Redis +2
Slack & Telegram

Seller Follow-Up Engine (Enhanced). Uses httpRequest, slack. Scheduled trigger; 44 nodes.

HTTP Request, Slack
Slack & Telegram

This workflow runs daily at 9 AM, uses Perplexity to compile vaping industry news, optionally captures article screenshots via Browserless, and generates a HeyGen avatar video from a template. It then

Telegram, Google Drive, HTTP Request