← Blog

Website change monitoring API — get notified when any page changes

September 10, 2026 · 5 min read

A surprising share of business logic is just “tell me when this page changes.” A competitor’s pricing page. A government tender listing. A “back in stock” section. An API status page. The naive version — reload the page in a loop and diff the HTML — breaks fast: layouts shift, timestamps and A/B tests change, ads rotate, and suddenly everything looks “changed”.

This post shows what a change-monitoring API needs to get right, and how to build it in one request with OtterSnap’s /v1/watch.

The three hard parts nobody mentions

1. The noise problem. Byte-comparing HTML is useless — every load differs. The fix is hashing a normalized reading of the page: render it in a real browser, strip scripts and styles, and hash the extracted text (title + Markdown body). Pixel-identical rendering isn’t the goal; meaningful change detection is.

2. The delivery problem. Polling means someone has to receive the result. Webhooks are the right answer (your server gets a POST), but they must be verifiable — otherwise anyone who learns the URL can fake a “price changed” alert. Sign the payload with HMAC-SHA256 and include the signature in a header.

3. The farming problem. Re-rendering pages on a schedule means running a headless-browser fleet: memory spikes, crashed tabs, slow sites. If you’re not in the rendering business, you don’t want this machine.

One request instead of all that

OtterSnap renders the page, normalizes it, hashes it, and watches it — you just pick the interval and the delivery channel:

curl -X POST https://api.ottersnap.com/v1/watch \
  -H "Authorization: Bearer otter_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://example.com/pricing",
    "interval_minutes": 60,
    "mode": "text",
    "webhook_url": "https://yourapp.com/hooks/ottersnap"
  }'

The first (baseline) check runs within a minute. After that, every time the content changes you get a POST like:

X-OtterSnap-Signature: sha256=7f3a91d0…
X-OtterSnap-Event: change

{
  "event": "change",
  "job_id": "b1e07c2e-…",
  "url": "https://example.com/pricing",
  "mode": "text",
  "detected_at": "2026-09-10T03:12:44.181Z",
  "previous_hash": "9d1c4ab2…",
  "new_hash": "7f3a91d0…"
}

Verifying it is three lines of code:

// use the webhook_secret returned when you created the job
const raw = await req.text(); // raw body, BEFORE JSON.parse
const expected = "sha256=" +
  crypto.createHmac("sha256", secret).update(raw).digest("hex");
if (req.headers["x-ottersnap-signature"] !== expected) return res.status(401).end();

Modes and selectors

No webhook receiver? Pass notify_email instead and OtterSnap emails you on every change.

Plans and limits

Free accounts run 2 monitors checking every 60 minutes; paid plans scale to 50 monitors at 15-minute checks. Every check costs 1 render credit (100/month free). A monitor that fails five times in a row pauses itself and emails you — no runaway alerting.

Try it

Grab a free key at ottersnap.com/#getkey, or play with the API first in the interactive playground. Full parameter reference lives in the docs.