AutomationFlowsSlack & Telegram › Monitor Website Changes and Send Diff Alerts via Telegram and Email

Monitor Website Changes and Send Diff Alerts via Telegram and Email

ByTony Adijah @togo on n8n.io

This workflow is ideal for marketers, product managers, competitive intelligence teams, and anyone who needs to track changes on web pages — whether it's competitor pricing, job postings, policy updates, product pages, or any content that matters to your business.

Cron / scheduled trigger★★★★☆ complexity19 nodesHTTP RequestGoogle SheetsTelegramEmail Send
Slack & Telegram Trigger: Cron / scheduled Nodes: 19 Complexity: ★★★★☆ Added:
Monitor Website Changes and Send Diff Alerts via Telegram and Email — n8n workflow card showing HTTP Request, Google Sheets, Telegram integration

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

This workflow follows the Emailsend → Google Sheets 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": "qodlu90C8ezDmvwD",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Monitor website changes with diff detection and get instant Telegram and email alerts",
  "tags": [
    {
      "id": "mHQntpbppFUpZPGm",
      "name": "template",
      "createdAt": "2026-02-17T08:30:26.885Z",
      "updatedAt": "2026-02-17T08:30:26.885Z"
    }
  ],
  "nodes": [
    {
      "id": "48770788-5b55-45c5-a828-a402fc15ed72",
      "name": "Sticky Note - Main Overview",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -400,
        -208
      ],
      "parameters": {
        "width": 740,
        "height": 1080,
        "content": "## Monitor website changes with diff detection and get instant Telegram and email alerts\n\nAutomatically monitor any website for changes, detect exactly what changed with a line-by-line diff, and get instant alerts via Telegram and email \u2014 perfect for tracking competitor pages, pricing updates, policy changes, job postings, or any web content that matters to your business.\n\n### How it works\n1. **Schedule Trigger** checks your target URLs at a configurable interval (default: every 4 hours).\n2. **URL List** defines which pages to monitor \u2014 easily add or remove URLs without touching other nodes.\n3. **HTTP Request** fetches the current HTML content of each page.\n4. **Content Extractor** strips HTML tags, scripts, and styles to get clean readable text, plus extracts key metadata (title, meta description).\n5. **Load Previous Snapshot** reads the last saved version from Google Sheets for comparison.\n6. **Diff Engine** compares current vs. previous content line-by-line, calculates a change percentage, assigns a severity level, and generates a human-readable diff summary showing exactly what was added or removed.\n7. **Change Filter** only passes through pages that actually changed \u2014 no noise, no false alerts.\n8. **Save Snapshot** stores the new version in Google Sheets for the next comparison cycle.\n9. **Telegram + Email Alerts** send formatted notifications with the diff summary, change percentage, severity, and a direct link to the page.\n\n### Setup steps\n1. **URLs to Monitor** \u2014 Edit the \"URL List\" node and add your target URLs (one per item). Each URL needs a `name` (friendly label) and `url` (full URL). Optionally add a `selector` keyword.\n2. **Google Sheets** \u2014 Create a new spreadsheet with columns: `Name`, `url`, `selector`, `pageTitle`, `metaDescription`, `cleanText`, `contentHash`, `contentLength`, `fetchedAt`, `httpStatus`. Connect your Google Sheets OAuth credential and set the spreadsheet ID in both Sheet nodes.\n3. **Telegram** \u2014 Create a bot via @BotFather, get your Bot Token and Chat ID. Connect the Telegram credential and set your Chat ID.\n4. **Email** (optional) \u2014 Connect your SMTP or Gmail credential and set your from/to addresses.\n5. **Schedule** \u2014 Adjust the cron interval (every 1hr for critical, every 24hrs for low-priority).\n6. **First Run** \u2014 Run manually once to save baseline snapshots. Change detection begins from the second run.\n\n### Customization\n- Add CSS selector keywords to monitor only specific page sections (e.g., pricing tables).\n- Increase check frequency to every 15 minutes for critical monitoring.\n- Add Slack, Discord, or WhatsApp as additional alert channels.\n- Use AI (OpenAI, Ollama) to summarize changes instead of raw diff.\n- Monitor API endpoints (JSON) by adjusting the Content Extractor.\n- Add multiple sheets for different monitoring categories."
      },
      "typeVersion": 1
    },
    {
      "id": "0270a40a-7dbd-476e-bcb9-498a84723911",
      "name": "Sticky Note - Trigger",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        496,
        592
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 260,
        "content": "## \u23f0 Trigger + URL Config\nSchedule trigger fires at set interval, URL List defines pages to monitor"
      },
      "typeVersion": 1
    },
    {
      "id": "b56e9165-9022-46fc-98a0-ed8c0c98cc7b",
      "name": "Sticky Note - Fetch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1056,
        592
      ],
      "parameters": {
        "color": 6,
        "width": 520,
        "height": 260,
        "content": "## \ud83c\udf10 Fetch + Extract\nDownload page HTML and extract clean text content with metadata"
      },
      "typeVersion": 1
    },
    {
      "id": "aa5786f6-6a62-4ac0-ae94-be4013f95bbb",
      "name": "Sticky Note - Diff",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1616,
        592
      ],
      "parameters": {
        "color": 6,
        "width": 600,
        "height": 260,
        "content": "## \ud83d\udd0d Compare + Diff\nLoad previous snapshot from Sheets, compare content, calculate diff and severity"
      },
      "typeVersion": 1
    },
    {
      "id": "9df49e9f-7f1f-468b-85dd-0891a0ff97ed",
      "name": "Sticky Note - Alerts",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2240,
        496
      ],
      "parameters": {
        "color": 6,
        "width": 660,
        "height": 596,
        "content": "## \ud83d\udce3 Alert + Save\nSave new snapshot to Sheets, send Telegram and email alerts for detected changes"
      },
      "typeVersion": 1
    },
    {
      "id": "94bf7b88-d154-4aad-905d-7bb233cd53be",
      "name": "Sticky Note - Warning URLs",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        704,
        912
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 140,
        "content": "## \u26a0\ufe0f Add Your URLs\nEdit the \"\ud83d\udccb URL List\" node to add the websites you want to monitor. Add one item per URL with `name` and `url` fields."
      },
      "typeVersion": 1
    },
    {
      "id": "b182dead-db8f-4f37-a986-c3a47742a038",
      "name": "Sticky Note - Warning First Run",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1712,
        928
      ],
      "parameters": {
        "color": 3,
        "width": 300,
        "height": 140,
        "content": "## \u26a0\ufe0f First Run = Baseline\nThe first run saves snapshots only \u2014 no alerts will fire. Changes are detected from the second run onward."
      },
      "typeVersion": 1
    },
    {
      "id": "d64b65a1-5d3c-48d1-a21e-cba7a78bdb94",
      "name": "\u23f0 Every 4 Hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        544,
        720
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 4
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "4150789f-9e18-4bbe-9e6b-6900fd638b09",
      "name": "\ud83d\udccb URL List",
      "type": "n8n-nodes-base.code",
      "position": [
        784,
        720
      ],
      "parameters": {
        "jsCode": "// ============================================\n// URL LIST - Add your URLs to monitor here\n// ============================================\n// Each item needs:\n//   name: A friendly label for alerts\n//   url: The full URL to monitor\n//   selector: (optional) CSS-like keyword to focus on specific content\n// ============================================\n\nconst urlsToMonitor = [\n  {\n    name: 'Example - n8n Pricing',\n    url: 'https://n8n.io/pricing/',\n    selector: 'pricing'\n  },\n  {\n    name: 'Example - GitHub Trending',\n    url: 'https://github.com/trending',\n    selector: ''\n  },\n  {\n    name: 'Example - Hacker News',\n    url: 'https://news.ycombinator.com/',\n    selector: ''\n  }\n  // Add more URLs here:\n  // {\n  //   name: 'Competitor Pricing Page',\n  //   url: 'https://competitor.com/pricing',\n  //   selector: 'price'\n  // },\n];\n\nreturn urlsToMonitor.map(item => ({ json: item }));"
      },
      "typeVersion": 2
    },
    {
      "id": "dc406a5b-3e63-43e0-b28d-4aff3e14dfd4",
      "name": "\ud83c\udf10 Fetch Page",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        1104,
        720
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "options": {
          "timeout": 15000,
          "redirect": {
            "redirect": {
              "maxRedirects": 5
            }
          },
          "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/120.0.0.0 Safari/537.36"
            },
            {
              "name": "Accept",
              "value": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
            },
            {
              "name": "Accept-Language",
              "value": "en-US,en;q=0.5"
            }
          ]
        }
      },
      "typeVersion": 4.2
    },
    {
      "id": "31e3e410-d7ff-4203-b9fa-a892344f4885",
      "name": "\ud83d\udcc4 Extract Content",
      "type": "n8n-nodes-base.code",
      "position": [
        1344,
        720
      ],
      "parameters": {
        "jsCode": "// ============================================\n// CONTENT EXTRACTOR\n// Strips HTML, extracts clean text + metadata\n// ============================================\n\nconst urlData = $('\ud83d\udccb URL List').item.json;\nconst rawResponse = $json;\n\nlet rawHtml = '';\nif (typeof rawResponse === 'string') {\n  rawHtml = rawResponse;\n} else if (rawResponse.data) {\n  rawHtml = String(rawResponse.data);\n} else if (rawResponse.body) {\n  rawHtml = String(rawResponse.body);\n} else {\n  rawHtml = JSON.stringify(rawResponse);\n}\n\n// Extract metadata\nlet pageTitle = '';\nlet metaDescription = '';\n\ntry {\n  const titleMatch = rawHtml.match(/<title[^>]*>([^<]+)<\\/title>/i);\n  pageTitle = titleMatch ? titleMatch[1].trim() : '';\n  \n  const metaMatch = rawHtml.match(/<meta[^>]*name=[\"']description[\"'][^>]*content=[\"']([^\"']+)[\"']/i)\n    || rawHtml.match(/<meta[^>]*content=[\"']([^\"']+)[\"'][^>]*name=[\"']description[\"']/i);\n  metaDescription = metaMatch ? metaMatch[1].trim() : '';\n} catch (e) {}\n\n// Strip HTML to get clean text\nlet cleanText = rawHtml\n  // Remove script and style blocks entirely\n  .replace(/<script[^>]*>[\\s\\S]*?<\\/script>/gi, '')\n  .replace(/<style[^>]*>[\\s\\S]*?<\\/style>/gi, '')\n  .replace(/<noscript[^>]*>[\\s\\S]*?<\\/noscript>/gi, '')\n  // Remove SVG blocks\n  .replace(/<svg[^>]*>[\\s\\S]*?<\\/svg>/gi, '')\n  // Remove navigation and footer (usually noise)\n  .replace(/<nav[^>]*>[\\s\\S]*?<\\/nav>/gi, ' [NAV] ')\n  .replace(/<footer[^>]*>[\\s\\S]*?<\\/footer>/gi, ' [FOOTER] ')\n  // Convert block elements to newlines\n  .replace(/<\\/(div|p|h[1-6]|li|tr|section|article|blockquote)>/gi, '\\n')\n  .replace(/<br\\s*\\/?>/gi, '\\n')\n  .replace(/<hr\\s*\\/?>/gi, '\\n---\\n')\n  // Remove remaining HTML tags\n  .replace(/<[^>]+>/g, ' ')\n  // Decode HTML entities\n  .replace(/&nbsp;/g, ' ')\n  .replace(/&amp;/g, '&')\n  .replace(/&lt;/g, '<')\n  .replace(/&gt;/g, '>')\n  .replace(/&quot;/g, '\"')\n  .replace(/&#39;/g, \"'\")\n  .replace(/&#x27;/g, \"'\")\n  .replace(/&#x2F;/g, '/')\n  // Clean whitespace\n  .replace(/[ \\t]+/g, ' ')           // Collapse horizontal whitespace\n  .replace(/\\n\\s*\\n\\s*\\n/g, '\\n\\n')  // Max 2 newlines\n  .replace(/^\\s+|\\s+$/gm, '')        // Trim each line\n  .trim();\n\n// If selector keyword is provided, try to focus on relevant content\nconst selector = urlData.selector || '';\nlet focusedContent = cleanText;\n\nif (selector) {\n  const lines = cleanText.split('\\n');\n  const relevantLines = [];\n  let capturing = false;\n  let captureCount = 0;\n  \n  for (const line of lines) {\n    if (line.toLowerCase().includes(selector.toLowerCase())) {\n      capturing = true;\n      captureCount = 0;\n    }\n    if (capturing) {\n      relevantLines.push(line);\n      captureCount++;\n      if (captureCount > 50) capturing = false; // Capture 50 lines after keyword\n    }\n  }\n  \n  if (relevantLines.length > 0) {\n    focusedContent = relevantLines.join('\\n');\n  }\n}\n\n// Generate content hash for quick comparison\nlet contentHash = 0;\nconst hashInput = focusedContent.substring(0, 10000);\nfor (let i = 0; i < hashInput.length; i++) {\n  const char = hashInput.charCodeAt(i);\n  contentHash = ((contentHash << 5) - contentHash) + char;\n  contentHash = contentHash & contentHash; // Convert to 32-bit integer\n}\ncontentHash = Math.abs(contentHash).toString(36);\n\nreturn {\n  json: {\n    // URL info\n    name: urlData.name,\n    url: urlData.url,\n    selector: selector,\n    \n    // Extracted content\n    pageTitle: pageTitle,\n    metaDescription: metaDescription,\n    cleanText: focusedContent.substring(0, 50000), // Limit storage size\n    contentHash: contentHash,\n    contentLength: focusedContent.length,\n    \n    // Metadata\n    fetchedAt: new Date().toISOString(),\n    httpStatus: rawResponse.statusCode || rawResponse.status || 200\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "23a42e29-2815-4cad-90db-67afcb7cedda",
      "name": "\ud83d\udccb Load Previous Snapshot",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1664,
        720
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.name }}",
              "lookupColumn": "Name"
            },
            {
              "lookupValue": "={{ $json.url }}",
              "lookupColumn": "url"
            },
            {
              "lookupValue": "={{ $json.selector }}",
              "lookupColumn": "selector"
            },
            {
              "lookupValue": "={{ $json.pageTitle }}",
              "lookupColumn": "page title"
            },
            {
              "lookupValue": "={{ $json.metaDescription }}",
              "lookupColumn": "meta description"
            },
            {
              "lookupValue": "={{ $json.cleanText }}",
              "lookupColumn": "clean text"
            },
            {
              "lookupValue": "={{ $json.contentHash }}",
              "lookupColumn": "content hash"
            },
            {
              "lookupValue": "={{ $json.contentLength }}",
              "lookupColumn": "content lenght"
            },
            {
              "lookupValue": "={{ $json.fetchedAt }}",
              "lookupColumn": "fetched at"
            },
            {
              "lookupValue": "={{ $json.httpStatus }}",
              "lookupColumn": "http status"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 299160514,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=299160514",
          "cachedResultName": "website"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
          "cachedResultName": "Website Monitor"
        }
      },
      "typeVersion": 4.6,
      "alwaysOutputData": true
    },
    {
      "id": "e94c53d2-d0e0-44c6-bb51-92ec84550ac0",
      "name": "\ud83d\udd0d Diff Engine",
      "type": "n8n-nodes-base.code",
      "position": [
        1840,
        720
      ],
      "parameters": {
        "jsCode": "// ============================================\n// DIFF ENGINE\n// Compare current vs previous, generate diff\n// ============================================\n\nconst currentData = $('\ud83d\udcc4 Extract Content').item.json;\nconst allPreviousRows = $('\ud83d\udccb Load Previous Snapshot').all();\n\n// Find the previous snapshot for this specific URL\nlet previousContent = '';\nlet previousHash = '';\nlet lastChecked = '';\nlet isFirstRun = true;\n\nfor (const row of allPreviousRows) {\n  const rowUrl = (row.json.URL || row.json.url || '').trim();\n  if (rowUrl === currentData.url) {\n    previousContent = row.json.Content || row.json.content || '';\n    previousHash = row.json['Content Hash'] || row.json.contentHash || '';\n    lastChecked = row.json['Last Checked'] || row.json.lastChecked || '';\n    isFirstRun = false;\n    break;\n  }\n}\n\n// Quick hash comparison first\nif (!isFirstRun && previousHash === currentData.contentHash) {\n  return {\n    json: {\n      ...currentData,\n      hasChanged: false,\n      isFirstRun: false,\n      changePercentage: 0,\n      diffSummary: 'No changes detected.',\n      addedLines: [],\n      removedLines: [],\n      lastChecked: lastChecked,\n      comparedAt: new Date().toISOString()\n    }\n  };\n}\n\n// If first run, save baseline\nif (isFirstRun) {\n  return {\n    json: {\n      ...currentData,\n      hasChanged: false,\n      isFirstRun: true,\n      changePercentage: 0,\n      diffSummary: 'First run \u2014 baseline snapshot saved. Changes will be detected from the next run.',\n      addedLines: [],\n      removedLines: [],\n      lastChecked: 'Never',\n      comparedAt: new Date().toISOString()\n    }\n  };\n}\n\n// ============================================\n// DETAILED DIFF COMPARISON\n// ============================================\n\nconst prevLines = previousContent.split('\\n').map(l => l.trim()).filter(l => l.length > 0);\nconst currLines = currentData.cleanText.split('\\n').map(l => l.trim()).filter(l => l.length > 0);\n\n// Build sets for comparison\nconst prevSet = new Set(prevLines);\nconst currSet = new Set(currLines);\n\n// Find added lines (in current but not in previous)\nconst addedLines = currLines.filter(line => !prevSet.has(line));\n\n// Find removed lines (in previous but not in current)\nconst removedLines = prevLines.filter(line => !currSet.has(line));\n\n// Calculate change percentage\nconst totalLines = Math.max(prevLines.length, currLines.length, 1);\nconst changedLines = addedLines.length + removedLines.length;\nconst changePercentage = Math.round((changedLines / totalLines) * 100);\n\n// Generate human-readable diff summary\nlet diffSummary = '';\n\nif (addedLines.length > 0) {\n  diffSummary += `\\n\u2795 ADDED (${addedLines.length} lines):\\n`;\n  addedLines.slice(0, 20).forEach(line => {\n    diffSummary += `  + ${line.substring(0, 200)}\\n`;\n  });\n  if (addedLines.length > 20) {\n    diffSummary += `  ... and ${addedLines.length - 20} more added lines\\n`;\n  }\n}\n\nif (removedLines.length > 0) {\n  diffSummary += `\\n\u2796 REMOVED (${removedLines.length} lines):\\n`;\n  removedLines.slice(0, 20).forEach(line => {\n    diffSummary += `  - ${line.substring(0, 200)}\\n`;\n  });\n  if (removedLines.length > 20) {\n    diffSummary += `  ... and ${removedLines.length - 20} more removed lines\\n`;\n  }\n}\n\nif (!diffSummary) {\n  diffSummary = 'Hash changed but no line-level differences detected (possible whitespace or formatting change).';\n}\n\n// Determine change severity\nlet severity = 'low';\nif (changePercentage > 50) severity = 'critical';\nelse if (changePercentage > 20) severity = 'high';\nelse if (changePercentage > 5) severity = 'medium';\n\nreturn {\n  json: {\n    ...currentData,\n    hasChanged: true,\n    isFirstRun: false,\n    changePercentage: changePercentage,\n    severity: severity,\n    diffSummary: diffSummary.trim(),\n    addedCount: addedLines.length,\n    removedCount: removedLines.length,\n    addedLines: addedLines.slice(0, 30),\n    removedLines: removedLines.slice(0, 30),\n    previousContentLength: previousContent.length,\n    currentContentLength: currentData.cleanText.length,\n    lastChecked: lastChecked,\n    comparedAt: new Date().toISOString()\n  }\n};"
      },
      "typeVersion": 2
    },
    {
      "id": "f44c17be-0ca7-403a-8292-e58839c7e2cf",
      "name": "\ud83d\udd04 Changed?",
      "type": "n8n-nodes-base.if",
      "position": [
        2080,
        720
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "has-changed",
              "operator": {
                "type": "boolean",
                "operation": "true"
              },
              "leftValue": "={{ $json.hasChanged }}",
              "rightValue": true
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "e525973e-572e-4ba6-9d9b-206a59f4dc57",
      "name": "\ud83d\udcbe Save Snapshot",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2320,
        656
      ],
      "parameters": {
        "columns": {
          "value": {
            "Name": "={{ $json.name }}"
          },
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "url",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "selector",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "selector",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "page title",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "page title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "meta description",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "meta description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "clean text",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "clean text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "content hash",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "content hash",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "content lenght",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "content lenght",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fetched at",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "fetched at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "http status",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "http status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "pageTitle",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "pageTitle",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "metaDescription",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "metaDescription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cleanText",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cleanText",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contentHash",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "contentHash",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contentLength",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "contentLength",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fetchedAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "fetchedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "httpStatus",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "httpStatus",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hasChanged",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "hasChanged",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "isFirstRun",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "isFirstRun",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "changePercentage",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "changePercentage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "diffSummary",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "diffSummary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "addedLines",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "addedLines",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "removedLines",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "removedLines",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lastChecked",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lastChecked",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "comparedAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "comparedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": true
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 299160514,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=299160514",
          "cachedResultName": "website"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
          "cachedResultName": "Website Monitor"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "6ab2ff6a-4c6e-41d4-947a-4209a6bd46fe",
      "name": "\ud83d\udcf2 Telegram Alert",
      "type": "n8n-nodes-base.telegram",
      "position": [
        2576,
        592
      ],
      "parameters": {
        "text": "={{ (() => {\nconst d = $json;\nconst severityEmoji = {\n  'critical': '\ud83d\udd34',\n  'high': '\ud83d\udfe0',\n  'medium': '\ud83d\udfe1',\n  'low': '\ud83d\udfe2'\n};\nconst emoji = severityEmoji[d.severity] || '\ud83d\udd35';\n\nlet msg = `${emoji} *WEBSITE CHANGE DETECTED*\\n\\n`;\nmsg += `\ud83d\udcc4 *${d.name}*\\n`;\nmsg += `\ud83d\udd17 ${d.url}\\n\\n`;\nmsg += `\ud83d\udcca *Change: ${d.changePercentage}%* (${d.severity})\\n`;\nmsg += `\u2795 ${d.addedCount || 0} lines added\\n`;\nmsg += `\u2796 ${d.removedCount || 0} lines removed\\n\\n`;\nmsg += `\ud83d\udcdd *Diff Summary:*\\n`;\nmsg += '```\\n';\nmsg += (d.diffSummary || 'No details').substring(0, 2000);\nmsg += '\\n```\\n\\n';\nmsg += `\u23f0 Detected: ${d.comparedAt}\\n`;\nmsg += `\ud83d\udcc5 Last checked: ${d.lastChecked}`;\n\nreturn msg;\n})() }}",
        "chatId": "YOUR_TELEGRAM_CHAT_ID",
        "additionalFields": {
          "parse_mode": "Markdown",
          "disable_web_page_preview": true
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "09dd047f-76eb-400e-83da-2cddfbf387cd",
      "name": "\ud83d\udce7 Email Alert",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        2576,
        752
      ],
      "parameters": {
        "html": "=<div style=\"font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; max-width: 650px; margin: 0 auto; background: #f5f5f5; padding: 20px;\">\n  <div style=\"background: {{ $json.severity === 'critical' ? '#dc3545' : $json.severity === 'high' ? '#fd7e14' : $json.severity === 'medium' ? '#ffc107' : '#28a745' }}; padding: 20px 24px; border-radius: 12px 12px 0 0;\">\n    <h2 style=\"color: white; margin: 0; font-size: 20px;\">\ud83d\udd0d Website Change Detected</h2>\n    <p style=\"color: rgba(255,255,255,0.9); margin: 8px 0 0; font-size: 14px;\">{{ $json.severity.toUpperCase() }} severity \u2014 {{ $json.changePercentage }}% changed</p>\n  </div>\n  \n  <div style=\"background: white; padding: 24px; border: 1px solid #e0e0e0;\">\n    <h3 style=\"margin-top: 0; color: #333;\">{{ $json.name }}</h3>\n    <p style=\"margin: 4px 0;\"><strong>URL:</strong> <a href=\"{{ $json.url }}\" style=\"color: #0066cc;\">{{ $json.url }}</a></p>\n    <p style=\"margin: 4px 0;\"><strong>Page Title:</strong> {{ $json.pageTitle }}</p>\n    \n    <div style=\"display: flex; gap: 16px; margin: 16px 0;\">\n      <div style=\"background: #e8f5e9; padding: 12px 16px; border-radius: 8px; flex: 1; text-align: center;\">\n        <div style=\"font-size: 24px; font-weight: bold; color: #2e7d32;\">+{{ $json.addedCount || 0 }}</div>\n        <div style=\"font-size: 12px; color: #666;\">Lines Added</div>\n      </div>\n      <div style=\"background: #ffebee; padding: 12px 16px; border-radius: 8px; flex: 1; text-align: center;\">\n        <div style=\"font-size: 24px; font-weight: bold; color: #c62828;\">-{{ $json.removedCount || 0 }}</div>\n        <div style=\"font-size: 12px; color: #666;\">Lines Removed</div>\n      </div>\n      <div style=\"background: #e3f2fd; padding: 12px 16px; border-radius: 8px; flex: 1; text-align: center;\">\n        <div style=\"font-size: 24px; font-weight: bold; color: #1565c0;\">{{ $json.changePercentage }}%</div>\n        <div style=\"font-size: 12px; color: #666;\">Changed</div>\n      </div>\n    </div>\n    \n    <div style=\"background: #f8f9fa; padding: 16px; border-radius: 8px; border-left: 4px solid #6c757d; margin-top: 16px;\">\n      <h4 style=\"margin: 0 0 8px; color: #333;\">\ud83d\udcdd Diff Summary</h4>\n      <pre style=\"margin: 0; font-size: 13px; color: #555; white-space: pre-wrap; word-wrap: break-word; font-family: 'SF Mono', Monaco, monospace;\">{{ $json.diffSummary.substring(0, 3000) }}</pre>\n    </div>\n    \n    <div style=\"margin-top: 20px; text-align: center;\">\n      <a href=\"{{ $json.url }}\" style=\"display: inline-block; background: #0066cc; color: white; padding: 12px 32px; border-radius: 8px; text-decoration: none; font-weight: 600;\">View Page \u2192</a>\n    </div>\n  </div>\n  \n  <div style=\"padding: 12px; text-align: center;\">\n    <p style=\"margin: 0; color: #999; font-size: 12px;\">Detected at {{ $json.comparedAt }} \u00b7 Previous check: {{ $json.lastChecked }}</p>\n    <p style=\"margin: 4px 0 0; color: #999; font-size: 12px;\">Powered by n8n Website Monitor</p>\n  </div>\n</div>",
        "options": {},
        "subject": "={{ (() => {\nconst s = {'critical':'\ud83d\udd34','high':'\ud83d\udfe0','medium':'\ud83d\udfe1','low':'\ud83d\udfe2'};\nreturn `${s[$json.severity] || '\ud83d\udd35'} Website Changed: ${$json.name} (${$json.changePercentage}%)`;\n})() }}",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com"
      },
      "typeVersion": 2.1
    },
    {
      "id": "8c5ef23c-9c33-449b-b90c-2fad25cc69a7",
      "name": "\ud83d\udcbe Save (No Change)",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2320,
        832
      ],
      "parameters": {
        "columns": {
          "value": {},
          "schema": [
            {
              "id": "Name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "Name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "url",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "url",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "selector",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "selector",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "page title",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "page title",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "meta description",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "meta description",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "clean text",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "clean text",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "content hash",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "content hash",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "content lenght",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "content lenght",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fetched at",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "fetched at",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "http status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "http status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "name",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "name",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "pageTitle",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "pageTitle",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "metaDescription",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "metaDescription",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "cleanText",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "cleanText",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contentHash",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "contentHash",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "contentLength",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "contentLength",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "fetchedAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "fetchedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "httpStatus",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "httpStatus",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "hasChanged",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "hasChanged",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "isFirstRun",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "isFirstRun",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "changePercentage",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "changePercentage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "diffSummary",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "diffSummary",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "addedLines",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "addedLines",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "removedLines",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "removedLines",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "lastChecked",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "lastChecked",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "comparedAt",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "comparedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "autoMapInputData",
          "matchingColumns": [
            "Name"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 299160514,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit#gid=299160514",
          "cachedResultName": "website"
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": "YOUR_GOOGLE_SHEET_ID",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/YOUR_GOOGLE_SHEET_ID/edit",
          "cachedResultName": "Website Monitor"
        }
      },
      "typeVersion": 4.6
    },
    {
      "id": "0e16d10a-b095-411b-a4f7-4552b0de7958",
      "name": "\ud83d\udcdd Log Result",
      "type": "n8n-nodes-base.code",
      "position": [
        2576,
        928
      ],
      "parameters": {
        "jsCode": "// Log entry for change history\nconst d = $json;\n\nreturn {\n  json: {\n    status: d.isFirstRun ? 'baseline_saved' : (d.hasChanged ? 'change_detected' : 'no_change'),\n    url: d.url,\n    name: d.name,\n    changePercentage: d.changePercentage || 0,\n    severity: d.severity || 'none',\n    addedCount: d.addedCount || 0,\n    removedCount: d.removedCount || 0,\n    checkedAt: d.fetchedAt,\n    message: d.isFirstRun \n      ? `\u2705 Baseline saved for ${d.name}` \n      : (d.hasChanged \n        ? `\ud83d\udd04 ${d.changePercentage}% change detected on ${d.name}` \n        : `\u2713 No changes on ${d.name}`)\n  }\n};"
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "5f721126-5edd-484e-aade-2ea8a2598349",
  "connections": {
    "\ud83d\udccb URL List": {
      "main": [
        [
          {
            "node": "\ud83c\udf10 Fetch Page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd04 Changed?": {
      "main": [
        [
          {
            "node": "\ud83d\udcbe Save Snapshot",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "\ud83d\udcbe Save (No Change)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83c\udf10 Fetch Page": {
      "main": [
        [
          {
            "node": "\ud83d\udcc4 Extract Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udd0d Diff Engine": {
      "main": [
        [
          {
            "node": "\ud83d\udd04 Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\u23f0 Every 4 Hours": {
      "main": [
        [
          {
            "node": "\ud83d\udccb URL List",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcbe Save Snapshot": {
      "main": [
        [
          {
            "node": "\ud83d\udcf2 Telegram Alert",
            "type": "main",
            "index": 0
          },
          {
            "node": "\ud83d\udce7 Email Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcc4 Extract Content": {
      "main": [
        [
          {
            "node": "\ud83d\udccb Load Previous Snapshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udcbe Save (No Change)": {
      "main": [
        [
          {
            "node": "\ud83d\udcdd Log Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "\ud83d\udccb Load Previous Snapshot": {
      "main": [
        [
          {
            "node": "\ud83d\udd0d Diff Engine",
            "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 is ideal for marketers, product managers, competitive intelligence teams, and anyone who needs to track changes on web pages — whether it's competitor pricing, job postings, policy updates, product pages, or any content that matters to your business.

Source: https://n8n.io/workflows/13455/ — 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

This automated n8n workflow monitors real-time cryptocurrency prices using CoinGecko API and sends smart alerts when price conditions are met. It supports multi-coin tracking, dynamic conditions, and

Google Sheets, HTTP Request, Email Send +2
Slack & Telegram

Monitor Indian (NSE/BSE) and US stock markets with intelligent price alerts, cooldown periods, and multi-channel notifications (Email + Telegram). Automatically tracks price movements and sends alerts

Google Sheets, HTTP Request, Email Send +1
Slack & Telegram

⚠️ Heads up: this is satire. The "Hell Yeah!" workflow is a parody of "automate your whole life with AI agents" grindset content. The API endpoints are fictional and the function nodes are illustrativ

HTTP Request, Salesforce, Telegram +4
Slack & Telegram

This workflow continuously monitors the Meta Ads Library for new creatives from a specific competitor pages, logs them into Google Sheets, and sends a concise Telegram notification with the number of

HTTP Request, Telegram, Google Sheets +1
Slack & Telegram

Enhance financial oversight with this automated n8n workflow. Triggered every 5 minutes, it fetches real-time bank transactions via an API, enriches and transforms the data, and applies smart logic to

HTTP Request, Email Send, Google Sheets +1