Blog 6 min read

n8n Workflow: Screenshot a List of URLs Every Morning and Post Changes to Slack

Build an n8n workflow that checks a list of web pages every morning, detects which ones changed, and posts a full-page screenshot of each changed page to Slack. Uses built-in n8n nodes, works on n8n Cloud and self-hosted.

This workflow checks a list of web pages every morning, works out which ones changed since yesterday, and posts a full-page screenshot of each changed page to a Slack channel. It uses only built-in n8n nodes, so it runs on n8n Cloud and on self-hosted instances, and it needs no browser on your n8n server.

The shape is six nodes:

Schedule Trigger → Code (the URL list) → HTTP Request (page text) → Code (compare with yesterday) → HTTP Request (screenshot, only for changed pages) → Slack (post it)

Page rendering happens in SnapRender: real Chromium, cookie banners and ads removed, a fresh render on every call. A free key covers 200 calls a month, enough for six pages checked daily.

Try it on your URL

Live capture, no signup. You get the screenshot and the exact API call that produced it.

1. Credentials

Create one credential and reuse it in both HTTP Request nodes. In n8n, add a Header Auth credential:

  • Name: X-API-Key
  • Value: your SnapRender API key

Header Auth keeps the key out of node parameters and out of exported workflow JSON.

Or skip the setup entirely

Everything in this guide is one GET request with SnapRender. No browser to babysit, no timeouts to tune.

200 screenshots a month free. First render in under a minute.

2. Schedule Trigger

Add a Schedule Trigger node set to run daily at 07:00 in your timezone, or use the cron expression 0 7 * * 1-5 for weekdays only.

3. Code node: the list of pages

The URL list lives in a Code node so you can edit it in one place. Each item carries the URL and the CSS selector that holds the content worth watching; scoping to main or a pricing table keeps navigation and footer changes from triggering alerts.

const pages = [
  { url: 'https://example.com/pricing', selector: 'main' },
  { url: 'https://example.com/legal/terms', selector: 'article' },
  { url: 'https://competitor.example/pricing', selector: 'main' },
];
return pages.map((p) => ({ json: p }));

If the list belongs to someone who does not edit workflows, replace this node with a Google Sheets node that reads a sheet with url and selector columns.

4. HTTP Request: get the rendered text

Add an HTTP Request node:

  • Method: GET
  • URL: https://app.snap-render.com/v1/extract
  • Authentication: Generic Credential Type, Header Auth, the credential from step 1
  • Send Query Parameters: on
    • url = {{ $json.url }}
    • type = text
    • selector = {{ $json.selector }}

The node runs once per item and returns JSON with the page's visible text in content. Under the node's Settings, turn on Continue On Fail so one unreachable page does not stop the rest.

5. Code node: what changed since yesterday?

This node keeps one fingerprint per URL in workflow static data, which n8n persists between production runs. Set the node's mode to Run Once for All Items.

const crypto = require('crypto');
const memory = $getWorkflowStaticData('global');
memory.fingerprints = memory.fingerprints || {};
const pages = $('Code').all(); // the URL list node, to recover each page's url

const changed = [];
$input.all().forEach((item, i) => {
  const url = pages[i].json.url;
  const text = (item.json.content || '').replace(/\s+/g, ' ').trim();
  if (!text) return; // failed or empty extract: skip, do not overwrite the fingerprint
  const fp = crypto.createHash('sha256').update(text).digest('hex');
  const before = memory.fingerprints[url];
  memory.fingerprints[url] = fp;
  if (before && before !== fp) changed.push({ json: { url } });
});
return changed;

The first production run only records fingerprints. From the second run on, the node outputs one item per changed page and nothing for unchanged ones, so the rest of the workflow runs only when there is something to report.

Two notes. If your Code node is named differently, change $('Code') to its name. On self-hosted n8n, require('crypto') needs the NODE_FUNCTION_ALLOW_BUILTIN=crypto environment variable; n8n Cloud allows it already.

Static data is saved only for production executions, the ones triggered by the schedule after the workflow is activated. Manual test runs do not persist it, which is why the workflow looks like it never remembers anything until you activate it.

6. HTTP Request: screenshot each changed page

Add a second HTTP Request node after the comparison:

  • Method: GET
  • URL: https://app.snap-render.com/v1/screenshot
  • Authentication: the same Header Auth credential
  • Query Parameters: url = {{ $json.url }}, format = png, full_page = true
  • Options, Response, Response Format: File

The image arrives as binary data in the item, usually under the property name data.

7. Slack: post the screenshot

Add a Slack node with the operation that uploads a file, point it at the binary property data, choose the channel, and use a message such as:

Page changed: {{ $json.url }}

Each changed page becomes one Slack message with the full-page capture attached. If nothing changed, the workflow ends quietly after step 5 and posts nothing.

Variations

Send a digest instead. Add an Aggregate node before Slack to combine all changed URLs into one message, and attach screenshots only for the first few.

Archive every day, alert on change. Branch after step 4: one branch saves a daily screenshot of every page to Google Drive or S3, the other runs the comparison and alerts. That gives you a dated history and a quiet channel.

Use the community node on self-hosted n8n. The n8n-nodes-snaprender package adds a SnapRender node with Take Screenshot, Extract Content, Batch Screenshots and Sign URL operations, so steps 4 and 6 become one node each. Install it from Settings, Community Nodes on self-hosted instances. n8n Cloud allows only verified community nodes, which is why this guide uses HTTP Request.

What it costs

Each run makes one extract call per page, plus one screenshot call per changed page.

Pages Schedule Calls a month (approx.) SnapRender plan
6 daily 180 plus changes Free
30 weekdays 660 plus changes Starter, $9
100 daily 3,000 plus changes Growth, $29

Every row above is one GET request away. 200 free renders a month, no card.

Start free

n8n's own execution limits depend on your n8n plan; this workflow counts as one execution per scheduled run, however many pages it checks.

Related guides

To check what a page's capture looks like before building the workflow, paste it into the free screenshot tool.

Frequently asked questions

Can n8n take website screenshots?

Yes. An HTTP Request node calling a screenshot API returns the image as binary data, which later nodes can upload to Slack, Google Drive or S3. On self-hosted n8n you can also install the n8n-nodes-snaprender community node.

How do I remember the previous run in an n8n workflow?

Use workflow static data in a Code node: $getWorkflowStaticData('global') returns an object that persists between production executions. It is not saved during manual test runs, so activate the workflow to see it work.

Does this work on n8n Cloud?

Yes. The workflow uses only built-in nodes (Schedule Trigger, Code, HTTP Request, If, Slack), which are available on n8n Cloud and self-hosted instances alike.

Or skip the setup entirely

200 screenshots a month free. First render in under a minute.

Grab a free API key