Website Change Detection: Page Diff Alerts avatar

Website Change Detection: Page Diff Alerts

Under maintenance

Pricing

from $2.00 / 1,000 page checkeds

Go to Apify Store
Website Change Detection: Page Diff Alerts

Website Change Detection: Page Diff Alerts

Under maintenance

Watch many web pages for changes and get the exact lines or links that were added or removed. Scope it to one part of a page, ignore timestamps, render JavaScript when needed, and get alerts by email, Slack, Discord or webhook. Failed checks are free.

Pricing

from $2.00 / 1,000 page checkeds

Rating

0.0

(0)

Developer

Plainsource

Plainsource

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

13 hours ago

Last modified

Share

Website Change Monitor is a website change detection Actor. Give it a list of URLs and run it on a schedule: it monitors website changes by comparing each page with the copy it saved on the previous run, and returns the exact lines or links that were added or removed. You can also get an alert by email, Slack, Discord or webhook, but only when something actually changed.

It works as a Visualping alternative for people who want the diff as data (a dataset, an API response or a webhook) and prefer to pay per page checked. It compares text and links, not pixels, so it is built for pricing pages, changelogs, terms of service, listings and feeds.

What data can Website Change Monitor extract?

Every watched page produces one row per run.

FieldWhat it contains
statusbaseline, changed, unchanged, selector_missing, failed, site_blocked, not_allowed or skipped
changeSummaryOne line such as "2 lines added, 1 line removed" or "5 new links, 6 links gone"
addedText, removedTextThe lines that appeared or disappeared (text mode). Up to 50 lines per row, each cut at 500 characters
addedLinks, removedLinksNew and removed links as { url, text } (links mode). Up to 100 per row
charactersAdded, charactersRemovedSize of the change
url, finalUrl, labelThe page you gave, where it ended up after redirects, and your own label
title, httpStatusPage title and HTTP status
selector, compareMode, renderJavaScriptThe settings used for this page
screenshotUrlLink to a JPEG of the changed area, when screenshots are on
textPreviewOn a baseline row, the first 1,000 characters of what is being watched
checkedAt, previousCheckAt, lastChangedAt, watchedSinceTimestamps for this check, the one before, the last change and the first check
errorThe reason a page could not be checked

The run also writes a summary to the OUTPUT record of its key-value store, with counts per status and the list of changed pages.

How to use Website Change Monitor

  1. Open the Actor on Apify and click Try for free.
  2. Paste the pages you want to watch into Pages to watch.
  3. Optional: set Part of the page to watch to a CSS selector such as main or #pricing, and add noisy elements or text patterns (dates, counters) to the ignore lists.
  4. Click Start. The first run saves a baseline for every page and reports each one with the status baseline.
  5. Create a schedule in Apify Console (hourly, daily or weekly) with the same input. Every later run compares with the previous one and returns changed or unchanged.
  6. Read the results in the dataset, or add email addresses or a Slack, Discord or webhook URL to be told when a page changes.

Saved versions live in a key-value store named website-change-monitor-state in your account, grouped by Watch list name. Use a different name for each task so separate lists keep separate history.

How much does it cost to monitor website changes?

You pay per event, not for compute time.

EventPrice
Actor start$0.002 per GB of run memory (the default is 1 GB, so $0.002 per run)
Page checked, plain HTTP$0.002
Page checked with JavaScript rendering$0.004
Change detected$0.005 per changed page, on top of the page check
Screenshot of a change$0.003

Email alerts and webhook messages have no event price. Pages that fail, are blocked, are disallowed by robots.txt or whose selector matches nothing are not charged.

Worked examples, from the prices above:

  • 1,000 pages checked once without JavaScript, no changes: $0.002 + 1,000 x $0.002 = $2.002.
  • 20 pages checked daily for 30 days, with about one change a week: 30 x ($0.002 + 20 x $0.002) + 4 x $0.005 = $1.28 a month.
  • The same 1,000 pages with JavaScript rendering: $0.002 + 1,000 x $0.004 = $4.002.

If a run reaches the maximum cost you set, the remaining pages are marked skipped, their saved versions are kept, and they are checked again on the next run.

Input

The only required field is Pages to watch. This example watches two pages with their own selectors, emails the changes and posts them to Slack:

{
"pages": [
{ "url": "https://www.python.org/downloads/", "selector": ".download-list-widget", "label": "Python releases" },
{ "url": "https://nodejs.org/en/blog", "selector": "main", "compareMode": "links", "label": "Node.js blog" }
],
"ignorePatterns": ["Updated: .*"],
"notificationEmails": ["you@example.com"],
"emailSubjectPrefix": "[Releases]",
"webhookUrl": "https://hooks.slack.com/services/XXX/YYY/ZZZ",
"webhookFormat": "slack",
"stateKey": "release-pages"
}

All settings:

FieldDefaultWhat it does
Pages to watch (urls)noneList of page URLs.
Pages with their own settings (pages)noneJSON list for pages that need their own selector, compareMode, label, ignoreSelectors, ignorePatterns, renderJavaScript, waitForSelector, waitSecs or screenshotSelector.
Part of the page to watch (selector)whole pageA CSS selector such as main, #pricing or .release-list. A smaller area means fewer false alerts from menus, footers and banners.
What to compare (compareMode)texttext reports added and removed lines. links reports new and removed links.
Ignore elements (ignoreSelectors)noneCSS selectors removed before comparing, such as .ad or time.
Ignore text matching (ignorePatterns)noneRegular expressions deleted before comparing, such as \d+ (minutes|hours) ago. Matching ignores case.
Minimum change size (minChangedCharacters)1Smaller changes are not reported. The saved version is kept, so small edits add up until they count.
Render JavaScript (renderJavaScript)falseLoads pages in Chrome. Needed for pages that build their content in the browser.
Wait for element (waitForSelector)emptyWith JavaScript rendering, waits up to 20 s for this selector before reading the page.
Extra wait (waitSecs)0With JavaScript rendering, seconds to wait after the page loads (0 to 30).
Screenshot changed pages (screenshotOnChange)falseSaves a JPEG of the changed area and adds its link to the row.
Screenshot area (screenshotSelector)the watched partCSS selector of the element to capture.
Email alerts to (notificationEmails)noneUp to 5 addresses. One email per run, sent only when at least one page changed.
Email subject prefix (emailSubjectPrefix)emptyText put in front of the subject, up to 60 characters.
Webhook URL (webhookUrl)emptySends a POST to this https URL when at least one page changed.
Webhook format (webhookFormat)jsonjson (full rows), slack or discord (a readable message).
Also notify about pages that failed (notifyOnErrors)falseAlso sends the email and webhook message when pages could not be checked, and lists those pages.
Only output changed pages (onlyChanges)falseOff: one row per page every run. On: rows only for changed pages.
Watch list name (stateKey)defaultSaved versions are kept per watch list.
Respect robots.txt (respectRobotsTxt)trueSkips pages the site's robots.txt disallows. Turn off only for sites you own or may monitor.
Pages checked at once (maxConcurrency)51 to 20. Requests to the same site stay at least 1 s apart, and at most 2 pages render in Chrome at a time.
Page timeout (timeoutSecs)30Seconds to wait for each page (5 to 120) before reporting it as failed.

Output

This is a real row from a test run on 2026-10-28. It watched the links in the Hacker News "newest" list (selector .athing .titleline, compareMode set to links), and two runs about 40 seconds apart found changes. The addedLinks and removedLinks lists are cut to two entries here; the real row had more.

{
"url": "https://news.ycombinator.com/newest",
"label": null,
"status": "changed",
"changeSummary": "5 new links, 6 links gone",
"addedText": [],
"removedText": [],
"addedLinks": [
{
"url": "https://chrispattle.com/avatars",
"text": "Show HN: I made my own version of Google Doodles"
},
{
"url": "https://news.ycombinator.com/from?site=chrispattle.com",
"text": "chrispattle.com"
}
],
"removedLinks": [
{
"url": "https://ao.news/live/n/US",
"text": "Show HN: Breaking abroad, not yet in [your country]"
},
{
"url": "https://news.ycombinator.com/from?site=ao.news",
"text": "ao.news"
}
],
"charactersAdded": 252,
"charactersRemoved": 276,
"title": "New Links | Hacker News",
"finalUrl": "https://news.ycombinator.com/newest",
"httpStatus": 200,
"selector": ".athing .titleline",
"compareMode": "links",
"renderJavaScript": false,
"screenshotUrl": null,
"textPreview": null,
"checkedAt": "2026-10-28T22:05:56.178Z",
"previousCheckAt": "2026-10-28T22:05:19.977Z",
"lastChangedAt": "2026-10-28T22:05:56.178Z",
"watchedSince": "2026-10-28T22:05:19.977Z",
"error": null
}

Statuses:

  • baseline: first check, the version is saved.
  • changed: see the added and removed fields.
  • unchanged: nothing changed, or the change was below the minimum size.
  • selector_missing: the selector matched nothing, so the layout may have changed. Not charged.
  • failed, site_blocked, not_allowed, skipped: the reason is in error.

What people use website change detection for

  • Competitor pricing and plan pages.
  • Changelogs, release pages and status pages.
  • Terms of service, privacy policies and other legal pages.
  • Product, stock or job listing pages. Use Links only to see which items are new.
  • RSS and Atom feeds, JSON endpoints and plain-text files.
  • Documentation pages that your own product depends on.

FAQ

How does it tell what changed?

It saves the text (or the links) of the watched part of each page, then compares the next fetch with that copy line by line. Lines that only moved position are not reported as a change. A saved page text is cut at 400,000 characters.

Can it detect visual changes like a new image or colour?

No. The comparison is on text and links. It can save a screenshot when a change is found, but a change that only affects pixels will not be detected.

Why do I get selector_missing or "no text"?

The page probably builds its content with JavaScript. Turn on Render JavaScript, and if needed set Wait for element to the selector you watch.

I get alerts for changes I do not care about. What can I do?

Narrow Part of the page to watch, add the noisy parts to Ignore elements, or add an Ignore text matching pattern for counters and dates. You can also raise Minimum change size.

How do email alerts work?

The run starts Apify's apify/send-mail Actor in your account with one message listing every changed page (up to 30), each change summary, the first 5 added and removed lines, the screenshot link and a link to the run. If nothing changed, no email is sent. On the Apify free plan, apify/send-mail can only send to your account's own email address, so check the run log and the emailError field of the OUTPUT record if nothing arrives, and check your spam folder.

How do I schedule it?

Create a task with your pages, then add it to a schedule in Apify Console (Schedules, then Create new). Each run compares with the previous one.

How do I send changes to Slack, Discord, Make, Zapier or n8n?

For Slack, create an incoming webhook, paste its URL into Webhook URL and set the format to Slack. For Discord, create a channel webhook and set the format to Discord. For Make, Zapier or n8n, use the json format to post full rows to their webhook trigger, or use the task's Integrations tab in Apify Console. Turn on Only output changed pages so integrations that act on new dataset rows only fire for real changes.

Can I call it from code or an AI agent?

Yes. Use the Apify API or the Apify MCP server (https://mcp.apify.com) with the same input. With the JavaScript client: client.actor("fernandosord/website-change-monitor").call({ urls: ["https://example.com/pricing"] }).

How do I start over?

Change the Watch list name. The next run saves a new baseline.

Is it polite to the sites it visits?

Robots.txt is respected by default, and requests to one site are at least 1 second apart (more if the site sets a Crawl-delay). The Actor identifies itself as PlainsourceChangeMonitor.

What does it not do?

  • No proxies, logins or bot-check workarounds. Pages that block automated visitors are reported as site_blocked and are not charged.
  • LinkedIn, Facebook, Instagram, X and TikTok are not supported, because their terms forbid automated access.
  • Private and local addresses (such as localhost and 10.x.x.x) are refused, including through redirects.
  • Up to 2,000 pages per run, and pages up to 10 MB.
  • Screenshots are stored in the run's default key-value store, which follows your account's data retention period.

Found a bug or need an option? Open an issue on this Actor's Issues tab.

Other Actors by this developer