The first version of page-change monitoring is always a cron job: fetch the page, hash the HTML, compare with yesterday. It fires on every rotating ad, CSRF token and "3 minutes ago" timestamp, so within a week nobody reads the alerts. Then the change that mattered — a competitor's price, a clause in a supplier's terms, a product going out of stock — arrives on a day the alert channel is muted.
Useful monitoring compares what a person would notice, not the bytes. This guide sets that up with domscout monitors: a scheduled render of the page, a comparison of the parts you choose, and a signed webhook when one of them changes.
What a monitor compares
Each run renders the page in Chromium and compares it with the previous run on the checks you enable:
checkMarkdown(on by default): the page's readable content, extracted as Markdown. Markup churn that does not change the text does not count.checkLinks(on by default): the set of links on the page.checkTitle(on by default): the document title.checkStatusCode(on by default): the HTTP status, so a page that starts returning 404 is a change.checkDom(off by default): element structure and position, for layout changes the text does not show. Tune it withdomIgnoreSelectors,domIgnoreAttributes,domIgnoreTextPatternsanddomPositionTolerancePx.
Turn off the checks that are noisy for a given page. A pricing page's footer links change for reasons unrelated to price, so checkLinks: false is often the right call there.
Create a monitor
Monitors are available on Pro and above. Create one with POST /monitors:
The response contains the monitor and, because a webhook URL was set, a signing secret:
Store the secret now. No later API read returns notifyWebhookSecret. If you lose it, reveal it from the monitor in the dashboard, or call POST /monitors/{monitorId}/rotate-secret for a new one.
intervalMinutes defaults to 1440 (daily) and can be at most 43200 (30 days). The minimum depends on the plan:
- Pro: up to 10 active monitors, checking at most every 60 minutes.
- Business: up to 100, at most every 15 minutes.
- Enterprise: up to 500, at most every 5 minutes.
Monitors are strict about their input in a way the rest of the API is not. A body with a field the API does not recognise is refused with 400 MONITOR_UNKNOWN_FIELD rather than silently ignored. That catches the expensive mistake: {"paused": true} is not a field, and a monitor that ignored it would keep running and billing. To pause, send PATCH /monitors/{monitorId} with {"status": "paused"}.
Test it without waiting an hour
POST /monitors/{monitorId}/run queues one extra check immediately without moving the schedule. Then read the outcome:
Each run reports status, changeDetected, changedFields and the captureId of the saved capture. The first run has nothing to compare against, so a change is only possible from the second run on.
The webhook you receive
When a run detects a change, the API sends a POST to notifyWebhookUrl:
changedFields names what changed, from title, status_code, markdown, links and dom. The two capture IDs let you fetch both versions with GET /capture/{captureId} and show a before and after.
Three headers come with it:
x-domscout-id: the delivery ID.x-domscout-timestamp: an ISO 8601 UTC timestamp such as2026-09-16T13:00:04.512Z. Not Unix seconds.x-domscout-signature: a hex HMAC-SHA256 of the timestamp, a dot, and the raw request body, keyed with the monitor's secret.
Verify the signature
Always verify before acting on a webhook, and compute the HMAC over the body exactly as it arrived. Parsing the JSON and serialising it again can reorder keys or change whitespace, and the signature will not match.
Node.js (Express)
Python (Flask)
The timestamp check is what makes a captured request useless to an attacker later. The signature alone would stay valid forever.
Delivery, retries and duplicates
A delivery that does not get a successful response is retried after 1, 5, 15 and 60 minutes, and every attempt is recorded. That means your endpoint can see the same event more than once, so make the handler idempotent: record payload.monitorRunId and skip a run you have already processed.
When something goes wrong on your side, you do not need to wait for the next change:
GET /webhooks/deliverieslists recent deliveries.GET /webhooks/deliveries/{deliveryId}shows each attempt and the status your endpoint returned.POST /webhooks/deliveries/{deliveryId}/replaysends it again, signed, without re-rendering the page.
If your endpoint redirects, end the redirect on the same origin you registered. The signature header is only sent to that origin, so a redirect to another host arrives unsigned, deliberately.
What it costs
Creating, reading, updating, pausing and deleting monitors is free. Each run, scheduled or on demand, is billed as one capture priced from the monitor's captureParams; a plain run is 1 credit. A page checked hourly is about 720 runs a month, so choose the interval from how quickly you need to know, not from how often the page might change. See pricing for plan quotas.
Next steps
- The monitors overview summarises checks, history and plans.
- Monitors are also manageable from the dashboard, with run history and diffs for each change.
- For a one-off comparison instead of a schedule, capture the page twice with the Markdown endpoint and diff the results yourself.