{
  "name": "PolyDoc - Website change monitor (screenshot on change)",
  "nodes": [
    {
      "id": "d1111111-1111-4111-8111-111111111111",
      "name": "Every 6 hours",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        240,
        320
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "hours",
              "hoursInterval": 6
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "d2222222-2222-4222-8222-222222222222",
      "name": "Settings",
      "type": "n8n-nodes-base.set",
      "position": [
        440,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "set-url",
              "name": "url",
              "type": "string",
              "value": "https://example.com"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "d3333333-3333-4333-8333-333333333333",
      "name": "Fetch page",
      "type": "n8n-nodes-base.httpRequest",
      "position": [
        640,
        320
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "method": "GET",
        "options": {}
      },
      "typeVersion": 4.2
    },
    {
      "id": "d4444444-4444-4444-8444-444444444444",
      "name": "Detect change",
      "type": "n8n-nodes-base.code",
      "position": [
        840,
        320
      ],
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "  const res = $input.first().json;\n  const html = res.data ?? (typeof res === 'string' ? res : JSON.stringify(res));\n  const normalized = String(html)\n    .replace(/<script[\\s\\S]*?<\\/script>/gi, '')\n    .replace(/<style[\\s\\S]*?<\\/style>/gi, '')\n    .replace(/<!--[\\s\\S]*?-->/g, '')\n    .replace(/\\s+/g, ' ')\n    .trim();\n\n  // djb2-xor hash: no imports, works in the sandboxed Code node.\n  let hash = 5381;\n  for (let i = 0; i < normalized.length; i++) {\n    hash = ((hash * 33) ^ normalized.charCodeAt(i)) >>> 0;\n  }\n  const current = String(hash);\n\n  const store = $getWorkflowStaticData('global');\n  const previous = store.lastHash;\n  store.lastHash = current;\n\n  return [\n    {\n      json: {\n        url: $('Settings').first().json.url,\n        changed: previous !== current,\n        firstRun: previous === undefined,\n        previousHash: previous ?? null,\n        currentHash: current,\n      },\n    },\n  ];"
      },
      "typeVersion": 2
    },
    {
      "id": "d5555555-5555-4555-8555-555555555555",
      "name": "Changed?",
      "type": "n8n-nodes-base.if",
      "position": [
        1040,
        320
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 2,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "loose"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "cond-changed",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.changed }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.2
    },
    {
      "id": "d6666666-6666-4666-8666-666666666666",
      "name": "Screenshot page",
      "type": "n8n-nodes-polydoc.polyDoc",
      "position": [
        1280,
        320
      ],
      "parameters": {
        "url": "={{ $json.url }}",
        "operation": "screenshot",
        "sourceType": "url",
        "deliveryMode": "download",
        "additionalFields": {
          "filename": "snapshot.png"
        },
        "screenshotOptions": {
          "fullPage": true,
          "imageType": "png",
          "viewportWidth": 1280,
          "viewportHeight": 800
        },
        "binaryPropertyName": "data"
      },
      "typeVersion": 1
    },
    {
      "id": "d7777777-7777-4777-8777-777777777777",
      "name": "Email screenshot",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        1480,
        320
      ],
      "parameters": {
        "text": "=The watched page changed since the last check.\n\nURL: {{ $('Detect change').first().json.url }}\n\nThe current full-page screenshot is attached.",
        "options": {
          "attachments": "data"
        },
        "subject": "=Change detected: {{ $('Detect change').first().json.url }}",
        "toEmail": "you@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "typeVersion": 2.1
    },
    {
      "id": "d8888888-8888-4888-8888-888888888888",
      "name": "No change",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1280,
        700
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "d9999999-9999-4999-8999-999999999999",
      "name": "Read me first",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        200,
        -260
      ],
      "parameters": {
        "width": 1380,
        "height": 360,
        "content": "## Website change monitor with screenshots\n\n**What it does:** Polls a web page on a schedule, detects whether its content changed since the last run, and captures a full-page PolyDoc screenshot **only when it changed**, so you never spend a screenshot on an unchanged page. The snapshot is emailed to you.\n\n**Who it's for:** Anyone tracking competitor pages, pricing, docs, or status pages who wants a visual record of every change.\n\n### Setup\n1. Set the **url** to watch in the *Settings* node.\n2. Add your **PolyDoc API** credential on *Screenshot page* (free key at dashboard.polydoc.tech).\n3. Add an **SMTP** credential on *Email screenshot*, or swap it for Slack / S3 / Google Drive.\n\nStart free: https://polydoc.tech"
      },
      "typeVersion": 1
    },
    {
      "id": "da111111-1111-4111-8111-111111111111",
      "name": "Section: Watch",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        200,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 400,
        "height": 260,
        "content": "## 1. Watch on a schedule\n\nRuns every 6 hours. The URL to watch lives in **Settings**, so you edit it in one place."
      },
      "typeVersion": 1
    },
    {
      "id": "db222222-2222-4222-8222-222222222222",
      "name": "Section: Detect",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        620,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 620,
        "height": 260,
        "content": "## 2. Detect a real change\n\nFetch the page, strip scripts, styles and whitespace, hash it, and compare to the last run (stored in workflow static data). Only a genuine content change continues."
      },
      "typeVersion": 1
    },
    {
      "id": "dc333333-3333-4333-8333-333333333333",
      "name": "Section: Capture",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1260,
        180
      ],
      "parameters": {
        "color": 4,
        "width": 420,
        "height": 260,
        "content": "## 3. Capture & send\n\nPolyDoc renders a full-page PNG of the changed page, which is emailed as an attachment."
      },
      "typeVersion": 1
    },
    {
      "id": "de555555-5555-4555-8555-555555555555",
      "name": "Setup: URL",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        400,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 180,
        "height": 110,
        "content": "**Set the URL** you want to watch here."
      },
      "typeVersion": 1
    },
    {
      "id": "df666666-6666-4666-8666-666666666666",
      "name": "Note: first run",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        660,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 380,
        "height": 130,
        "content": "First run and every manual test count as a change, so the screenshot branch runs right away. State then persists between scheduled runs."
      },
      "typeVersion": 1
    },
    {
      "id": "e0777777-7777-4777-8777-777777777777",
      "name": "Setup: PolyDoc key",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1240,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 190,
        "height": 110,
        "content": "**Add your PolyDoc API credential.** Free key at dashboard.polydoc.tech"
      },
      "typeVersion": 1
    },
    {
      "id": "e1888888-8888-4888-8888-888888888888",
      "name": "Setup: email",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1450,
        470
      ],
      "parameters": {
        "color": 7,
        "width": 210,
        "height": 110,
        "content": "**Add an SMTP credential**, or swap this node for Slack / S3 / Google Drive."
      },
      "typeVersion": 1
    },
    {
      "id": "e2999999-9999-4999-8999-999999999999",
      "name": "Note: no change",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1420,
        700
      ],
      "parameters": {
        "color": 7,
        "width": 250,
        "height": 110,
        "content": "**Nothing changed.** No screenshot is taken, the run ends quietly."
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "connections": {
    "Changed?": {
      "main": [
        [
          {
            "node": "Screenshot page",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Settings": {
      "main": [
        [
          {
            "node": "Fetch page",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Fetch page": {
      "main": [
        [
          {
            "node": "Detect change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Detect change": {
      "main": [
        [
          {
            "node": "Changed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Every 6 hours": {
      "main": [
        [
          {
            "node": "Settings",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Screenshot page": {
      "main": [
        [
          {
            "node": "Email screenshot",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}