Change Detection¶
Turn any monitor into a watcher. When the page you monitor - or the part you select - changes between runs, ScrapeNest notifies you by webhook and email and stores a before/after diff. Use it for price and stock monitoring, competitor and content changes, terms-of-service updates, and regulatory pages.
Change detection is an optional detection block on a monitor. Every run of that monitor is fingerprinted, compared to the previous version, and (on a real change) diffed and notified. A watched run is billed exactly like any other run - there is no separate charge for detection.
How it works¶
- The first evaluated run captures a reference version (the baseline) and sends a
schedule.monitor_activatedconfirmation - not a change alert. - Each later run is compared to the previous one. Only runs that actually delivered content are evaluated; a blocked or failed run is never treated as a change.
- The fingerprint is computed over normalized content (whitespace collapsed, scripts/styles and your ignored selectors removed), so rotating ads, timestamps, and session tokens do not cause false alerts.
- On a change, ScrapeNest records it in the change history, generates a diff, and - if the change clears your thresholds - sends
schedule.change_detectedby webhook and email.
Plan limits¶
| Plan | Change detection | Modes | Max monitors | History retained |
|---|---|---|---|---|
| Free | Not included | - | - | - |
| Starter | Included | text, selector, extract, json | 5 | 30 days |
| Pro | Included | text, selector, extract, json | 25 | 90 days |
| Business | Included | text, selector, extract, json | 100 | 180 days |
| Enterprise | Included | text, selector, extract, json | Unlimited | 365 days |
Enabling detection on a plan that does not include it (or a mode it does not allow) is rejected at creation time.
The detection block¶
| Field | Type | Description |
|---|---|---|
enabled |
bool | Turn change detection on/off for this monitor. |
mode |
string | What to fingerprint - see the modes below. |
selector |
string | CSS or XPath selector to watch. Required when mode is selector. |
ignore_selectors |
string[] | Selectors whose content is masked before fingerprinting (noisy regions). |
min_change_ratio |
number | Suppress alerts for changes smaller than this fraction (0-1) of the watched content. |
consecutive |
int | Alert only after this many consecutive changed runs (flap control). |
cooldown_seconds |
int | Suppress repeat alerts within this many seconds. |
notify.webhook |
bool | Emit the schedule.change_detected webhook (default true). |
notify.email |
string[] | Email addresses to notify. |
Modes¶
| Mode | What it watches | Best for |
|---|---|---|
text |
Visible text of the whole page (normalized) | General pages |
selector |
Only the nodes matching selector |
Watching one region (a price, a status) |
extract |
The structured data your extraction hooks produced - a field-level diff (for example, price 49 to 39). Uses the extraction rules already on the job; run-varying metadata is ignored. |
Precise, high-signal monitoring of specific fields |
json |
The response body as canonical JSON (keys sorted, so reordering is not a change) | Monitoring an API/JSON endpoint |
extract mode needs extraction hooks configured on the job. If none produced data, the detection health shows selector_missing (it needs attention).
Enable detection¶
from scrapenest import ScrapeNestClient
client = ScrapeNestClient(api_key="sn_live_...", base_url="https://api.scrapenest.com")
monitor = client.monitors.create(
name="competitor-pricing",
cron="0 * * * *",
job_type="standard",
target_url="https://competitor.example/pricing",
detection={
"enabled": True,
"mode": "selector",
"selector": ".price, #stock",
"ignore_selectors": [".ads", "time"],
"min_change_ratio": 0.05,
"notify": {"webhook": True, "email": ["alerts@acme.eu"]},
},
)
print(monitor.detection) # config + health (status, last_checked_at, last_change_at)
curl -X POST "https://api.scrapenest.com/api/v1/monitors" \
-H "X-API-Key: sn_live_..." \
-H "Content-Type: application/json" \
-d '{
"name": "competitor-pricing",
"cron": "0 * * * *",
"job_type": "standard",
"target_url": "https://competitor.example/pricing",
"detection": {
"enabled": true,
"mode": "selector",
"selector": ".price, #stock",
"ignore_selectors": [".ads", "time"],
"min_change_ratio": 0.05,
"notify": {"webhook": true, "email": ["alerts@acme.eu"]}
}
}'
Detection health¶
GET /api/v1/monitors/{id} returns the detection block including a health object so you can answer "is my monitor working?" without guessing:
status-ok, orselector_missingwhen your selector no longer matches the page (the monitor needs attention - the page structure likely changed).last_checked_at- when the monitor last evaluated a run.last_change_at- when it last detected a change.
Review changes¶
GET /api/v1/monitors/{id}/changes returns the change history, newest first.
Each change carries old_fingerprint, new_fingerprint, change_ratio, has_diff, and the notified_channels used (empty when a change was recorded but its notification was debounced).
Updating and turning it off¶
On update, omit detection to leave it unchanged. Changing the mode or selector resets the baseline (the next run re-captures a reference version). To turn detection off while keeping the monitor running, send "detection": {"enabled": false}.
Notifications¶
Subscribe to schedule.change_detected and schedule.monitor_activated, and/or list emails in notify.email. The change notification includes the change ratio and a short-lived signed link to the diff.
Digest emails¶
For high-frequency monitors, set notify.digest to daily or weekly to receive one summary email per period instead of one email per change. Webhooks still fire in real time on every change; only the email is batched.
Viewing the diff¶
Every detected change with a stored diff exposes it at GET /api/v1/monitors/{id}/changes/{changeId}/diff (also rendered in the console). Lines prefixed with - were removed and + were added. For extract/json monitors this is a field-level diff: