{
  "name": "Monitor competitor pages for price and content changes and post AI summaries to Slack",
  "nodes": [
    {
      "id": "fb8ff0d6-adcc-47fe-b59e-67431d35f6d0",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -608,
        -304
      ],
      "parameters": {
        "width": 480,
        "height": 880,
        "content": "## Monitor competitor pages for price and content changes and post AI summaries to Slack\n\n### How it works\n\nThis workflow runs on a morning schedule, expands a configured list of competitor page URLs, fetches each page, and extracts a trimmed text snapshot. It compares the current snapshot against a previous version stored in Google Sheets, then uses an AI agent to summarize detected changes and posts the summary to Slack. Whether or not an alert is sent, it saves the latest snapshot for the next comparison cycle.\n\n### Setup steps\n\n- Configure the schedule trigger for the desired monitoring time and timezone.\n- Edit the settings node with the competitor pageUrls list and the watchFor criteria, such as prices, product copy, or key content sections.\n- Connect Google Sheets credentials and point the lookup/save nodes to a sheet that stores each URL and its latest snapshot text.\n- Connect OpenAI credentials for the GPT-5 Mini chat model used by the AI agent.\n- Connect Slack credentials and choose the destination channel for change alerts.\n\n### Customization\n\nAdjust the watched URLs, snapshot trimming length, comparison logic, AI summarization prompt, Slack message format, and Google Sheets schema to match the competitors and content types you care about."
      },
      "typeVersion": 1
    },
    {
      "id": "a9a7e227-09ea-4fbe-9fe3-ede23333c960",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -48,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 304,
        "content": "## Schedule and page setup\n\nStarts the daily run, defines the monitored page URLs and watch criteria, then creates one workflow item per page."
      },
      "typeVersion": 1
    },
    {
      "id": "4bad6ccf-fe65-4a88-b73a-b2e3c73e9475",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        736,
        -128
      ],
      "parameters": {
        "color": 7,
        "width": 720,
        "height": 304,
        "content": "## Fetch and extract text\n\nDownloads each competitor page, extracts visible HTML text, and normalizes it into a compact snapshot containing the URL and page text."
      },
      "typeVersion": 1
    },
    {
      "id": "bdf9b0c6-8852-4fa0-99d5-ded53c77f90b",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1520,
        -160
      ],
      "parameters": {
        "color": 7,
        "width": 448,
        "height": 320,
        "content": "## Compare with previous\n\nRetrieves the stored snapshot for the same page from Google Sheets and checks whether the current page content has changed."
      },
      "typeVersion": 1
    },
    {
      "id": "72535966-5c6b-43d0-8914-b7f49f5df560",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2080,
        -304
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 560,
        "content": "## Summarize and alert\n\nFor changed pages, uses the OpenAI chat model through the AI agent to summarize the differences, then sends the summary to Slack."
      },
      "typeVersion": 1
    },
    {
      "id": "0ab8e3c3-ad63-4832-96c0-290638b4f833",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2640,
        -208
      ],
      "parameters": {
        "color": 7,
        "width": 240,
        "height": 368,
        "content": "## Save latest snapshot\n\nStores the current snapshot in Google Sheets after the comparison, ensuring the next run has an up-to-date baseline."
      },
      "typeVersion": 1
    },
    {
      "id": "m1",
      "name": "Every Morning at 8am",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        0,
        0
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "days",
              "triggerAtHour": 8,
              "triggerAtMinute": 0
            }
          ]
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "m2",
      "name": "Set Page and Watch Parameters",
      "type": "n8n-nodes-base.set",
      "position": [
        260,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "s1",
              "name": "pageUrls",
              "type": "array",
              "value": "={{ [\"https://competitor-a.com/pricing\", \"https://competitor-b.com/pricing\"] }}"
            },
            {
              "id": "s2",
              "name": "watchFor",
              "type": "string",
              "value": "price changes, new or removed plans, and availability or promotions"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "m3",
      "name": "Split Page URLs",
      "type": "n8n-nodes-base.splitOut",
      "position": [
        520,
        0
      ],
      "parameters": {
        "include": "allOtherFields",
        "options": {
          "destinationFieldName": "url"
        },
        "fieldToSplitOut": "pageUrls"
      },
      "typeVersion": 1
    },
    {
      "id": "m4",
      "name": "Fetch Page Content",
      "type": "n8n-nodes-base.httpRequest",
      "onError": "continueRegularOutput",
      "position": [
        780,
        0
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "method": "GET",
        "options": {
          "response": {
            "response": {
              "responseFormat": "text",
              "outputPropertyName": "data"
            }
          }
        }
      },
      "typeVersion": 4.4
    },
    {
      "id": "m5",
      "name": "Extract Page Text",
      "type": "n8n-nodes-base.html",
      "position": [
        1040,
        0
      ],
      "parameters": {
        "options": {
          "trimValues": true,
          "cleanUpText": true
        },
        "operation": "extractHtmlContent",
        "sourceData": "json",
        "dataPropertyName": "data",
        "extractionValues": {
          "values": [
            {
              "key": "pageText",
              "cssSelector": "body",
              "returnValue": "text"
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "m6",
      "name": "Build Snapshot Data",
      "type": "n8n-nodes-base.set",
      "position": [
        1300,
        0
      ],
      "parameters": {
        "mode": "manual",
        "assignments": {
          "assignments": [
            {
              "id": "p1",
              "name": "url",
              "type": "string",
              "value": "={{ $('Split Page URLs').item.json.url }}"
            },
            {
              "id": "p2",
              "name": "pageText",
              "type": "string",
              "value": "={{ ($json.pageText || '').toString().slice(0, 3000) }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "m7",
      "name": "Read Old Snapshot from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1560,
        0
      ],
      "parameters": {
        "options": {
          "returnFirstMatch": true
        },
        "resource": "sheet",
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $json.url }}",
              "lookupColumn": "URL"
            }
          ]
        },
        "operation": "read",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "combineFilters": "AND"
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "m8",
      "name": "Check for Content Change",
      "type": "n8n-nodes-base.if",
      "position": [
        1820,
        0
      ],
      "parameters": {
        "conditions": {
          "options": {
            "version": 2,
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "operator": {
                "type": "string",
                "operation": "exists",
                "singleValue": true
              },
              "leftValue": "={{ $json.Content }}",
              "rightValue": ""
            },
            {
              "operator": {
                "type": "string",
                "operation": "notEquals"
              },
              "leftValue": "={{ $('Build Snapshot Data').item.json.pageText }}",
              "rightValue": "={{ $json.Content }}"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "m9",
      "name": "Content Change Summary Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        2120,
        -140
      ],
      "parameters": {
        "text": "=Watch focus: {{ $('Set Page and Watch Parameters').item.json.watchFor }}\n\nOLD VERSION:\n{{ $('Read Old Snapshot from Sheets').item.json.Content }}\n\nNEW VERSION:\n{{ $('Build Snapshot Data').item.json.pageText }}",
        "options": {
          "systemMessage": "You monitor competitor web pages. Compare the OLD and NEW version of the page text and describe, in 1-3 short bullet points, what meaningfully changed \u2014 focusing on the watch focus (prices, plans, availability, promotions). Ignore cosmetic or navigation noise. If nothing meaningful changed, reply exactly: No significant change. Be concise and factual."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "m10",
      "name": "OpenAI GPT-5 Mini",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        2120,
        120
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-5-mini"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "m11",
      "name": "Post Summary to Slack",
      "type": "n8n-nodes-base.slack",
      "position": [
        2400,
        -140
      ],
      "parameters": {
        "text": "=\ud83d\udd14 *Change detected* on {{ $('Build Snapshot Data').item.json.url }}\n\n{{ $('Content Change Summary Agent').item.json.output }}",
        "select": "channel",
        "resource": "message",
        "channelId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "operation": "post",
        "messageType": "text",
        "otherOptions": {
          "includeLinkToWorkflow": false
        }
      },
      "typeVersion": 2.4
    },
    {
      "id": "m12",
      "name": "Update Snapshot in Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2680,
        0
      ],
      "parameters": {
        "columns": {
          "value": {
            "URL": "={{ $('Build Snapshot Data').item.json.url }}",
            "Content": "={{ $('Build Snapshot Data').item.json.pageText }}",
            "Last Checked": "={{ $now.toFormat('yyyy-MM-dd HH:mm') }}"
          },
          "mappingMode": "defineBelow"
        },
        "options": {},
        "resource": "sheet",
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": ""
        },
        "documentId": {
          "__rl": true,
          "mode": "list",
          "value": ""
        }
      },
      "typeVersion": 4.7
    }
  ],
  "connections": {
    "Split Page URLs": {
      "main": [
        [
          {
            "node": "Fetch Page Content",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Extract Page Text": {
      "main": [
        [
          {
            "node": "Build Snapshot Data",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI GPT-5 Mini": {
      "ai_languageModel": [
        [
          {
            "node": "Content Change Summary Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Fetch Page Content": {
      "main": [
        [
          {
            "node": "Extract Page Text",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Snapshot Data": {
      "main": [
        [
          {
            "node": "Read Old Snapshot from Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every Morning at 8am": {
      "main": [
        [
          {
            "node": "Set Page and Watch Parameters",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Post Summary to Slack": {
      "main": [
        [
          {
            "node": "Update Snapshot in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Content Change": {
      "main": [
        [
          {
            "node": "Content Change Summary Agent",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Update Snapshot in Sheets",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Content Change Summary Agent": {
      "main": [
        [
          {
            "node": "Post Summary to Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Old Snapshot from Sheets": {
      "main": [
        [
          {
            "node": "Check for Content Change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set Page and Watch Parameters": {
      "main": [
        [
          {
            "node": "Split Page URLs",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}