Docs / Monitors / Content change monitors

Content change monitors

Set up content change monitoring — baselines, diffs and screenshots, CSS selector scoping, ignore patterns, JS rendering, and how to tune out noisy false positives.
Last updated · 8 min read

A Content Change monitor fetches a page on a schedule, normalizes it to comparable text, hashes it, and compares the hash to the last stored snapshot. When the content differs, the check fails and records exactly what changed — a line-by-line diff, and a screenshot in visual mode.

Uptime checks can't see a defaced homepage: it returns 200 OK, fast, wrong everything. Change monitors cover that blind spot. For the wider background — rendered DOM vs raw HTML, injection attacks, tuning strategy — read Website change detection: catch defacement before your visitors do.

What to use it for

  • Defacement and injected content — an attacker replacing your page, or a compromised plugin quietly adding spam links or a malicious script. The diff shows the injected text the moment it appears.
  • Unauthorized edits — a CMS auto-update rewriting a template, a marketing tool "optimizing" a landing page, a teammate editing production directly.
  • Catching a broken deploy — a release meant for staging that lands on live, or a template change that silently drops the pricing table.
  • Watching a page you don't own — a competitor's pricing page, a supplier's terms of service, a dependency's changelog. Keep the interval polite (a few times a day) and respect the site's robots.txt and terms; CompleteStatus enforces both.

Create a content change monitor

  1. Go to /monitors and create a monitor (or open /monitors/create directly).
  2. Fill in the shared fields — project, name, Type: Content Change, the target URL, check interval, and the ownership attestation. See HTTP uptime monitors for what each shared field does and the interval limits per plan.
  3. Configure the change-specific fields below and save. The first check always passes — it records the baseline snapshot that every later check is compared against. The monitor page shows "Baseline snapshot recorded" until the second check runs.

For interval, match the page's tempo: 5–15 minutes for a homepage or checkout you'd treat a defacement of as an emergency; hourly or daily for a terms page or a competitor page that rarely moves.

Configuration fields

Comparison mode — text vs visual

  • Text — hashes the normalized text content of the page. Cheapest, and sufficient when the diff itself is all the evidence you need.
  • Visual — the same text-hash comparison, plus a full-page screenshot captured when a change is detected, stored alongside the diff.

In both modes the comparison is the text hash — visual mode changes what evidence is captured, not what triggers. Pick visual for pages where you'll want to see the change (a defaced homepage, a broken layout); pick text for terms pages and competitor pages where the diff lines are enough. Screenshots need the Chromium renderer on the worker; if it isn't available, the diff still stands — you just don't get the image.

Render JS with headless Chrome

Off by default: the check fetches the raw HTML the server returns. That's cheap, fast, and the right target for server-rendered sites — it also records the HTTP status and time-to-first-byte on each check.

Turn rendering on for single-page apps and client-rendered pages, where the raw HTML is a near-empty shell and every meaningful word is written by JavaScript. The trade-offs:

  • Each check launches a headless Chromium render — noticeably slower and heavier than a plain fetch, so prefer a more relaxed interval.
  • A rendered fetch has no HTTP status code or TTFB on the result.
  • If the renderer is unavailable on the worker, the check records an OK result with an explanatory note instead of comparing — deliberately, because diffing an unrendered shell against a rendered baseline would produce a giant spurious change. The note appears in the monitor's content panel.

Rule of thumb: view-source on your page. If the text you care about is in the source, leave rendering off.

CSS selector (optional)

Scope the comparison to one region of the page instead of the whole document — the single most effective noise reducer. With #main-content, only that element's text is hashed; edits to the header, footer, nav and "latest posts" sidebar are invisible to the check.

The selector is a single compound selector: a tag and/or #id/.class parts. Valid: #main-content, .pricing-table, article, article.post, div#content.wide. Descendant combinators (#main .content) and commas are not supported. If the selector doesn't match anything on a given fetch — or uses unsupported syntax — the check falls back to comparing the whole page rather than failing.

Ignore patterns

Regular expressions stripped from the text before hashing. This is how you silence content that legitimately differs on every load. The patterns run against the page's visible text (after HTML tags are removed) and are case-sensitive. Add one per row; typical examples:

  • Timestamps: Last updated:? [^.]+ or \d{2}:\d{2}:\d{2}
  • Tokens that survive into visible text: token=[a-z0-9]+ or csrf-token="[^"]+"
  • A cart counter: \d+ items? in your cart
  • "Live" counters: \d+ (people|customers) (viewing|online)

Note that normalization already does a lot for you: <script> and <style> blocks are removed, all HTML tags (and their attributes — including most CSRF meta tags and nonces) are stripped, and whitespace is collapsed. You only need ignore patterns for noise that appears in the visible text.

What happens when a change is detected

When the hash differs from the last snapshot, the check fails with the error "Page content changed." and stores a new snapshot containing the normalized content, a unified diff against the previous snapshot, and (when the renderer is available) a screenshot. The monitor detail page gets a Content change panel showing:

  • A Changed / Unchanged badge, the current and previous content hashes, and a summary of lines added and removed.
  • A diff viewer with a unified view (git-style +/ lines) and a side-by-side previous/current view.
  • Previous and current screenshots, when captured.

Two things follow from how snapshots work, and they shape the alerting behavior:

  • The changed content immediately becomes the new baseline. There is no manual "reset baseline" button, and you don't need one: the next check compares against the new content, so once the page settles, the check passes again on its own.
  • Incidents follow the standard confirmation rule — like every CompleteStatus check type, two consecutive failing checks flip the monitor to Down and open an incident (with the diff and screenshot on the triggering result). A one-off change that settles shows as a single failed check with its full diff on the monitor page; a page that is still changing at the next check — active defacement, or an untamed dynamic page — confirms Down and opens the incident. When a later check passes, the incident resolves automatically and recovery alerts go out.

Acknowledging works on the incident itself: open it from the monitor page or /incidents and acknowledge it to mark that a human is on it (acknowledgements are audit-logged and feed the MTTA figure in reports). Route the notifications by wiring the monitor's alert rules to your channels — see Alert channels. A homepage change probably deserves the channel that pages someone; a competitor-page diff belongs in a quiet Slack channel.

The last 50 snapshots are kept per monitor; older ones are pruned automatically.

Troubleshooting noisy diffs

The failure mode of change detection is not missing changes — it's alerting on everything until you stop reading the alerts. If your monitor fails on most checks (which, per the confirmation rule above, is exactly what opens repeated incidents):

  1. Read the diff first. The unified view tells you precisely which text is churning — a timestamp, a rotating testimonial, a "trending now" list.
  2. Scope with a selector. If the churn is outside the content you care about, point the monitor at #main-content (or equivalent) and the rest of the page stops existing.
  3. Add an ignore pattern per offender. Each dynamic fragment inside your selected region gets its own regex row. Treat every false positive as one rule to add.
  4. Check the rendering setting. A server that A/B-tests or personalizes raw HTML per request will churn constantly; conversely, an SPA monitored without rendering compares an empty shell. Match the setting to how the page is built.
  5. Expect a tuning week. The first days surface every dynamic element you forgot the page had. Once two fetches of an unchanged page produce identical text, the monitor goes quiet — which is what makes the eventual real alert believable.

If what you actually want is "this exact string must (or must not) be present" rather than "tell me when anything changes", a Keyword monitor is the simpler, quieter tool.

Related guides

Ready to try it?
10 monitors, security grading and email-authentication checks on the free tier — commercial use allowed.
Start free Run a free check
Uptime is table stakes. We watch the rest — security headers, email authentication, certs and DNS, with the fix attached.
Start free
Company
© 2026 CompleteStatus. All rights reserved. CompleteStatus — operated in the United States · support@completestatus.com