{
  "id": "00JvXPQ97EJUyIlv",
  "name": "Landing Page Defacement / Hack Monitor",
  "tags": [],
  "nodes": [
    {
      "id": "ba27497e-e526-4868-9387-b81c2ee04afc",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1664,
        64
      ],
      "parameters": {
        "width": 480,
        "height": 832,
        "content": "## Landing Page Defacement / Hack Monitor\n\n### How it works\n\nThis workflow runs every 15 minutes and compares the live landing page against a stored known-good baseline image. It checks for API or comparison errors, then treats a mismatch of 30% or more as a possible defacement or hijack. Alerts go to Discord and email in parallel so the incident can't be missed, and comparison failures are reported to Discord so the monitor never breaks silently.\n\n### Setup steps\n\n- Install the `n8n-nodes-snapshot-site` community node (Settings \u2192 Community nodes \u2192 Install \u2192 `n8n-nodes-snapshot-site`) \u2014 verified by n8n.\n- Add a Snapshot Site credential; set `beforeUrl` to a stored baseline screenshot URL and `afterUrl` to the live page \u2014 full URLs including `https://` (never set both `beforeUrl` and `beforeOptions.imageUrl` \u2014 the API rejects that combination).\n- Add a Discord Webhook credential and an SMTP credential; set the target channel and recipients.\n- Tune the schedule interval and the 30% alert percentage to your risk tolerance.\n\n### Customization\n\nLower the alert percentage to over-alert rather than risk missing an incident, add more parallel alert channels (SMS, PagerDuty) off the true branch, and refresh the baseline image after every intentional design change to avoid false alarms."
      },
      "typeVersion": 1
    },
    {
      "id": "ba70c21e-1bf7-469f-8793-ec7298aaf7db",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1120,
        288
      ],
      "parameters": {
        "color": 7,
        "width": 528,
        "height": 352,
        "content": "## Schedule and compare\n\nStarts the workflow every 15 minutes and compares the live page against the stored baseline image."
      },
      "typeVersion": 1
    },
    {
      "id": "63423018-86d2-40d9-ac00-ee948e4fec78",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -512,
        272
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 544,
        "content": "## Handle compare errors\n\nChecks whether the comparison returned an API or comparison error and sends a Discord failure notification on the lower error branch."
      },
      "typeVersion": 1
    },
    {
      "id": "0d74b19a-d1f4-40a3-a6c8-ace280c3aac6",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        0,
        0
      ],
      "parameters": {
        "color": 7,
        "width": 480,
        "height": 656,
        "content": "## Report defacement result\n\nEvaluates successful comparison results; on an abnormal change it alerts Discord AND email in parallel, otherwise it ends quietly."
      },
      "typeVersion": 1
    },
    {
      "id": "9b2edd7c-9718-45db-bcac-2c3a0f4522f0",
      "name": "When Every 15 Minutes",
      "type": "n8n-nodes-base.scheduleTrigger",
      "position": [
        -1072,
        432
      ],
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "minutes",
              "minutesInterval": 15
            }
          ]
        }
      },
      "typeVersion": 1.2
    },
    {
      "id": "c2f87c9b-9cbb-44ca-a12a-da062ded9ef9",
      "name": "Compare Baseline vs Live",
      "type": "n8n-nodes-snapshot-site.snapshotSite",
      "onError": "continueErrorOutput",
      "maxTries": 3,
      "position": [
        -752,
        432
      ],
      "parameters": {
        "afterUrl": "https://example.com",
        "beforeUrl": "https://your-storage.example.com/baselines/homepage-baseline.png",
        "operation": "compare",
        "threshold": 0.3,
        "afterOptions": {},
        "beforeOptions": {},
        "requestOptions": {}
      },
      "credentials": {
        "snapshotSiteApi": {
          "name": "<your credential>"
        }
      },
      "retryOnFail": true,
      "typeVersion": 1,
      "waitBetweenTries": 5000
    },
    {
      "id": "a4e522e4-aba8-4c6c-8673-1be94d43366e",
      "name": "Check for API Error",
      "type": "n8n-nodes-base.if",
      "position": [
        -464,
        432
      ],
      "parameters": {
        "conditions": {
          "boolean": [
            {
              "value1": "={{ $json.error === true }}",
              "value2": true
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "2c8167c5-4b26-462d-a8c7-82ee98adf9e2",
      "name": "Check for Abnormal Change",
      "type": "n8n-nodes-base.if",
      "position": [
        48,
        352
      ],
      "parameters": {
        "conditions": {
          "number": [
            {
              "value1": "={{$json.summary.mismatchPercentage}}",
              "value2": 30,
              "operation": "largerEqual"
            }
          ]
        }
      },
      "typeVersion": 1
    },
    {
      "id": "7be5e10c-9029-4e31-98ff-18132b8e89a6",
      "name": "Notify Defacement on Discord",
      "type": "n8n-nodes-base.discord",
      "position": [
        320,
        144
      ],
      "parameters": {
        "content": "=\ud83d\udea8 **Possible defacement/hack detected** on {{ $json.after.source }} \u2014 mismatch **{{ $json.summary.mismatchPercentage }}%**\nDiff: {{ $json.diff.link }}\nInvestigate immediately.",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    },
    {
      "id": "8534fce8-149a-47d9-8f00-4f747a48da52",
      "name": "Notify Defacement by Email",
      "type": "n8n-nodes-base.emailSend",
      "position": [
        320,
        336
      ],
      "parameters": {
        "text": "=Mismatch: {{ $json.summary.mismatchPercentage }}%\nDiff image: {{ $json.diff.link }}\nLive page: {{ $json.after.source }}\nBaseline image: {{ $json.before.link }}",
        "options": {},
        "subject": "\ud83d\udea8 Possible defacement detected",
        "toEmail": "user@example.com",
        "fromEmail": "user@example.com",
        "emailFormat": "text"
      },
      "credentials": {
        "smtp": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2.1
    },
    {
      "id": "5fa84c05-7d4d-4fd4-9fd7-7b221a2128e1",
      "name": "No Change",
      "type": "n8n-nodes-base.noOp",
      "position": [
        320,
        512
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "9d5317de-9df0-4b18-8b1e-602c1b9e0fb0",
      "name": "Notify Comparison Failure on Discord",
      "type": "n8n-nodes-base.discord",
      "position": [
        -192,
        624
      ],
      "parameters": {
        "content": "=\ud83d\udd34 Compare call failed: {{ $json.error?.message || $json.message }}",
        "options": {},
        "authentication": "webhook"
      },
      "credentials": {
        "discordWebhookApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 2
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "a442ce74-fd42-4b25-8f82-4a860831cdf9",
  "nodeGroups": [],
  "connections": {
    "Check for API Error": {
      "main": [
        [
          {
            "node": "Notify Comparison Failure on Discord",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Check for Abnormal Change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When Every 15 Minutes": {
      "main": [
        [
          {
            "node": "Compare Baseline vs Live",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Compare Baseline vs Live": {
      "main": [
        [
          {
            "node": "Check for API Error",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Notify Comparison Failure on Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Check for Abnormal Change": {
      "main": [
        [
          {
            "node": "Notify Defacement on Discord",
            "type": "main",
            "index": 0
          },
          {
            "node": "Notify Defacement by Email",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "No Change",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}