Website Change Monitor – Diff & Webhook Alerts avatar

Website Change Monitor – Diff & Webhook Alerts

Pricing

Pay per event

Go to Apify Store
Website Change Monitor – Diff & Webhook Alerts

Website Change Monitor – Diff & Webhook Alerts

Watch any web page, or one part of it, and get a before/after diff when it changes. Alerts by Slack, Discord or webhook. Ignores clocks and "2 hours ago", double-checks each change, renders JavaScript when needed. $1 per 1,000 checks + $5 per 1,000 changes.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Joshua White

Joshua White

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Share

Website Change Monitor watches any web page, or just one part of it (a price, a "Sold out" badge, a changelog), and tells you exactly what changed: lines added and removed, a before/after view and optional screenshots. Webhook alerts go to any URL you give it (Zapier, Make, n8n, your own server), and Slack and Discord incoming-webhook URLs get a ready-formatted message. Clock times and "5 minutes ago" don't trigger false alerts, every change is double-checked, and pages that can't be checked are never charged.

Try it in one click: press Start. The first run saves a baseline snapshot of the prefilled page; from the second run on you get only what changed. Then schedule it (below).

How to monitor a web page for changes in 3 steps

  1. Paste the pages to watch into URLs to monitor. To watch only part of a page, add a CSS selector (right-click the part → Inspect → copy selector).
  2. Add a Slack or Discord webhook URL, or any webhook, for alerts.
  3. Run it once to save the baseline, then schedule it hourly, daily or weekly.

How much does website change monitoring cost?

$1 per 1,000 page checks plus $5 per 1,000 changes found, and $2 per 1,000 pages that need a browser. Checks that fail (blocked, timeouts, errors, selector not found) are free. Watching 10 pages daily costs about $0.30 a month plus a few cents per change. You can set a maximum cost per run, and the actor stops cleanly when it is reached.

What you can use a website change monitor for

  • Competitor pages: pricing, plans, feature lists, job openings, product launches.
  • Prices and stock: watch the price or the "Sold out" badge on a product page with one CSS selector.
  • Policies and docs: terms of service, privacy policies, API docs, changelogs, release pages.
  • Government and public notices: tenders, grant pages, permit lists, school or council announcements.
  • Your own sites: catch an accidental content change, a broken deploy, or an SEO tag that disappeared.
  • APIs and feeds: JSON endpoints and plain-text files (like robots.txt) are compared value by value.

Run it on a schedule

Save your input as a task (the "Save as a new task" button), then in Apify Console open Schedules → Create new, pick how often (for example daily at 9:00), and add the task. Every scheduled run compares with the snapshots from the run before. Keep the same Monitor name for the same set of pages, and use a different name for each separate monitor so their snapshots don't mix.

Input example

{
"urls": ["https://example.com/pricing", "https://example.com/changelog"],
"selector": "main",
"webhookUrl": "https://hooks.slack.com/services/T000/B000/XXXX",
"monitorName": "competitor-pages"
}

Different settings per page go in Pages (advanced):

{
"pages": [
{ "url": "https://shop.example.com/widget", "selector": ".price", "label": "Widget price" },
{ "url": "https://shop.example.com/widget", "selector": ".stock", "label": "Widget stock" },
{ "url": "https://example.com/docs", "selector": "//article", "renderJavaScript": "always" },
{ "url": "https://example.com/blog", "contentMode": "attribute", "selector": "article a", "attribute": "href" }
],
"ignorePatterns": ["Visitors: \\d+"],
"ignoreSelectors": [".cookie-banner", ".ads"]
}

What it compares

OptionWhat it watches
Visible text (default)The text a person sees, one line per paragraph, heading, list item or table row. Scripts, styles and hidden templates are ignored.
HTML markupThe HTML of the page or part, with comments, nonces, CSRF tokens and cache-buster query strings removed. Catches changed links and images too.
An attributeOne attribute of the selected elements, one value per line: href for a list of links, src for images, data-price, content of a meta tag.

JSON responses are compared value by value (key order doesn't count as a change), and plain text line by line.

Fewer false alerts

  • Noise filters. Clock times and relative times ("2 hours ago", "yesterday") are ignored by default. You can also ignore dates or all numbers, add your own regular expressions, or remove parts of the page (cookie banners, ads, "related posts") with CSS selectors. Only the noisy part of a line is masked; the rest of it is still compared.
  • Double-check. When a change is seen, the page is loaded again 3 seconds later. If it is back to the saved version (an A/B test, a rotating banner, a half-finished deploy), you don't get an alert.
  • Minimum change size. Ignore changes smaller than a set number of characters. They still count toward the next alert.

JavaScript pages

Render JavaScript: auto (the default) loads each page over plain HTTP first, which is fast and cheap. It opens the page in a headless Chrome only when it needs to: when the page is blocked, when your selector isn't in the plain HTML, or when the page is an empty JavaScript app shell. Once a page needs the browser, it keeps using it, so snapshots stay comparable. Set always to render every page, or off to never use the browser.

Screenshots (optional) capture the watched part, or the first screen of the page. Each change links a before and an after screenshot.

Output

The dataset gets one row per changed page, plus rows for new baselines and for pages that couldn't be checked (turn on Save unchanged pages to get a row for every page).

{
"url": "https://shop.example.com/widget",
"label": "Widget price",
"selector": ".price",
"status": "changed",
"changed": true,
"summary": "1 line added, 1 line removed",
"diff": "- $19.99\n+ $17.49",
"added": ["$17.49"],
"removed": ["$19.99"],
"before": "$19.99",
"after": "$17.49",
"previousCheckAt": "2026-10-04T09:00:03.120Z",
"checkedAt": "2026-10-05T09:00:02.871Z",
"pageTitle": "Widget – Example Shop",
"statusCode": 200,
"method": "http",
"screenshotBeforeUrl": null,
"screenshotAfterUrl": null,
"webhook": "sent",
"warning": null,
"errorCode": null,
"error": null
}

status is one of:

StatusMeaning
changedThe content differs from the last snapshot. The snapshot is updated and notifications are sent.
baselineFirst check of this page (or of this selector): the snapshot was saved.
unchangedNo change (only saved to the dataset if you ask).
selector-missingThe page loaded, but nothing matched your selector. The snapshot is kept. If it used to match, you get one notification: either the part you watch was removed, or the page layout changed and the selector needs updating.
blockedThe site refused automated visitors (HTTP 401/403/429/503, a bot-check page) or its robots.txt disallows the page.
errorTimeout, DNS failure, HTTP error, or a response that isn't a web page, JSON or text.

For blocked and error rows, errorCode and error say what happened in plain words. The saved snapshot is never overwritten by a failed check, and you aren't charged for it. The run itself still succeeds, so changes on your other pages are reported. If you'd rather have the run fail (to use Apify's failed-run alerts), turn on Fail the run if any page can't be checked.

The run's OUTPUT record has a summary: how many pages changed, were unchanged, new, or couldn't be checked, and the list of changed URLs.

Notifications

  • Any webhook URL gets a JSON POST per change with every field above, plus event: "page.changed" (or "page.check_failed"). Failed deliveries are retried, and each row records whether its webhook was sent. This is the tested path: generic webhooks have been delivered live.
  • Slack: paste an incoming-webhook URL (https://hooks.slack.com/...) and each change is sent as a Slack-formatted message with the diff.
  • Discord: paste a channel webhook URL (https://discord.com/api/webhooks/...) for a Discord-formatted message. The Slack and Discord message formats are unit-tested; delivery to a live Slack or Discord channel hasn't been tested by us yet.
  • Email (beta, not yet tested end to end): one email per run listing every change, sent with Apify's own Send Email actor from your account. Some developer-only Apify plans can't run other public actors; if so, the run summary says the email failed. A webhook to an email automation (Zapier, Make) is the reliable route for email today.

How it behaves on the web

  • One visit per page per run (plus a second look when a change is double-checked), and pages on the same site are loaded one at a time with a pause between them.
  • It respects robots.txt for each page it checks. A page that robots.txt disallows is reported as blocked (ROBOTS_DISALLOWED) and isn't loaded.
  • It identifies itself honestly with the user agent WebsiteChangeMonitorBot. It doesn't try to get around bot protection; a page behind a bot wall is reported as blocked.
  • No logins and nothing behind a paywall: it reads what any visitor can see.

More tools from oldjard

Use it from an AI agent or the API

  • Minimal input: {"urls": ["https://www.python.org/downloads/"]}. The first run saves a baseline; later runs with the same monitorName report what changed.
  • Cost: $0.001 per page checked, plus $0.005 per change found and $0.002 per page opened in a browser. Failed checks are free. 10 pages checked once ≈ $0.01.
  • Run time (our runs): 2–7 s for 1–3 pages fetched over plain HTTP; 20–23 s for 5 pages when one of them is opened in a browser.
  • Results: the default dataset, one row per page check (status, diff, before/after); the run summary is the OUTPUT record in the key-value store.
  • Works over the Apify MCP server (search-actors, then call-actor).

FAQ

Where are the snapshots kept? In a key-value store in your Apify account named change-monitor-<monitor name>. Only the latest snapshot per page is kept (and the latest two screenshots). Delete the store to start fresh.

I changed the selector. What happens? A new selector is a new check: the first run saves a new baseline for it.

Can I watch hundreds of pages? Yes. Pages run in parallel (5 at a time by default), and pages on the same site take turns.

Does it see content that loads after scrolling or clicking? No. It sees the page as it first loads (after JavaScript runs, in browser mode).

Something it got wrong? Open an issue on the Issues tab with the URL and selector, and what you expected.