Website change monitoring
Compare two URLs pixel by pixel, get a similarity score and an overlay showing what moved. The hard part is not the comparison — it is ignoring the half of the page that changes on its own.
One call
curl -X POST https://api.getsnap.dev/v1/diff \
-H "X-API-Key: sk_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{
"before": { "url": "https://example.com/pricing" },
"after": { "url": "https://staging.example.com/pricing" }
}'
You get back three images and a number:
{
"before_url": "https://cdn.getsnap.dev/...",
"after_url": "https://cdn.getsnap.dev/...",
"diff_url": "https://cdn.getsnap.dev/...",
"similarity": 99.42,
"changed_pixels": 5344,
"total_pixels": 921600,
"width": 1280, "height": 720,
"credits_charged": 2
}
The diff overlay is red highlights on transparency, so you can lay it over the "after" image and see what moved rather than hunting for it. Both sides are captured at identical viewport, device and blocking settings, because two images of different sizes cannot be compared pixel by pixel at all.
The problem is noise, not detection
Detecting that two images differ is easy. The reason most visual monitoring gets switched off after a fortnight is that it reports a change every single run, and the person receiving the alerts stops reading them.
The culprits are nearly always the same handful of things:
- A clock, or a relative timestamp reading 2 minutes ago
- A carousel or rotating testimonial on a timer
- An ad slot, which is different every single load
- A "trusted by 4,318 developers" counter
- Anything with a CSS animation that has not settled
None of those are the change you care about, and no threshold setting will separate them from one, because a moving carousel can shift more pixels than a changed price. The fix is to remove them before the comparison, not to tune the comparison afterwards:
{
"before": { "url": "https://example.com/pricing" },
"after": { "url": "https://staging.example.com/pricing" },
"css": ".carousel, .ad-slot, .live-counter, time { visibility: hidden !important }",
"remove_popups": true,
"block_ads": true,
"wait_for_network_idle": true
}
css is injected into both captures before the screenshot is
taken. remove_popups and block_ads are on by default,
which removes two of the five on their own.
Each side also takes its own wait_for_selector,
extra_delay_ms and css, which matters when the two
environments differ — a staging build that is slower to render, or a
banner that only exists in production.
Choosing a threshold
There is no universal number, and anyone who gives you one has not run this against a real page. The honest method is to measure first: compare the page against itself for a week, see what the quiet days score, and set the alert below that. A page that reads 99.8 on a day when nothing happened should not alert at 99.9.
| Option | Does | Default |
|---|---|---|
threshold | how different two pixels must be to count, 0–1 | 0.1 |
anti_alias | ignore anti-aliasing artefacts | true |
include_aa | count anti-aliased pixels as changes | false |
full_page | compare the whole document, not one screen | false |
Anti-aliasing is worth understanding before you turn it off.
Text edges are rendered with slightly different sub-pixel values between runs,
so a naive comparison counts thousands of changed pixels on a page where
nothing changed at all. Those are ignored by default. Set
include_aa only if you are specifically watching for font
rendering changes.
Full page, and the mismatch error
full_page compares the whole document rather than the first
screen. It is the right choice for a terms page and the wrong one for a feed,
because a full-page capture is as tall as the content — and if the
content grew between the two captures, the two images are different sizes.
That returns a dimension_mismatch error rather than a meaningless
score, which is deliberate: a comparison between a 4,000px image and a 4,600px
one has no answer worth giving. If the length of the page changes, compare at
a fixed viewport instead.
What this is actually used for
- Catching the deploy that moved something. Compare production against staging before release and look at the overlay. Faster than a human scrolling two tabs, and it does not get bored on the eleventh page.
- Competitor pricing. Run it daily against the same URL and alert when the score drops. Pair it with a schedule and the whole thing is two API calls and a webhook handler.
- Third-party breakage. An embedded widget that silently stopped rendering is invisible in logs and obvious in an overlay.
- Evidence that nothing changed. Sometimes the useful output is 100.0 — a published policy page that is provably identical to the one captured eighteen months ago.
What it costs
Two credits for a fresh comparison, one per page captured. If either side hits the screenshot cache that side is free, so a daily check against an unchanged baseline often costs one credit rather than two. The free plan's 100 a month covers a daily comparison with room to spare.
There is also a no-signup tool that runs the same endpoint, if you would rather see the overlay before writing any code.
Frequently asked questions
Why does it report a change every run?
Something on the page moves on its own — a clock, a carousel, an ad, a relative timestamp. Hide them with injected CSS rather than raising the threshold, because a moving carousel can shift more pixels than a changed price.
What score counts as a real change?
Measure your own page for a week and set the alert below its quiet-day score. There is no number that is right for every page.
What is a dimension mismatch?
The two captures came back different sizes, usually because a full-page capture grew. Compare at a fixed viewport when the content length moves.
Does anti-aliasing cause false positives?
It would, which is why anti-aliased pixels are ignored by default. Turn that off only if font rendering is what you are watching.
Run one comparison and look at the overlay
100 captures a month, free and permanent, no card. Every option on this page works on the free plan — we meter credits, not features.
Get a free API key