ScreenshotNeo

BlogHow-to

How to Use Browshot to Monitor Website Visual Changes

Use Browshot to capture consistent screenshots, then add a scheduler, image comparison, and alerts to detect meaningful website changes.

By the ScreenshotNeo team4 October 202614 min read

To monitor visual changes with Browshot, capture the same page repeatedly using the same browser instance, viewport, page state, and wait settings. Save each image with its capture metadata, compare it with a baseline, and send meaningful differences for review. Browshot provides screenshot capture and API workflow features; the recurring schedule, visual comparison, and alert routing are separate parts of the monitoring system you build around it.

This distinction matters: a screenshot is evidence of how a page looked at one moment. It does not, by itself, tell you whether the page changed or whether the change matters. Browshot also documents a hosted ad-monitoring service for custom arrangements, but the general workflow below describes a developer-operated monitor. See the Browshot API documentation, features and pricing, and the documentation for browser automation.

1. Decide what counts as a visual change

Before scheduling captures, define the page, viewport, and region you care about. Monitoring a whole page catches broad layout changes but is more sensitive to rotating content and content below the fold. Monitoring a CSS target narrows the image to a component such as a pricing panel or banner.

  • Page set: list exact URLs, including meaningful query parameters. Avoid URLs whose parameters contain tracking IDs or random values unless those variations are part of what you monitor.
  • View: choose a desktop or mobile browser instance, screen dimensions, and either screen or full-page capture.
  • Page state: decide whether to monitor the anonymous page, a logged-in state, a selected tab, or a particular interaction path.
  • Noise policy: identify expected changes such as timestamps, rotating ads, user-specific content, or personalized recommendations. Exclude or mask these regions in your comparison stage where appropriate.
  • Baseline: make the first accepted capture the baseline. Record when it was approved and the capture configuration that produced it.

Keep the capture configuration stable. A browser, viewport, delay, locale, login state, or responsive breakpoint change can alter a screenshot even when the site itself did not change. Treat a deliberate configuration change as a new baseline.

2. Create a Browshot API key and choose an instance

Create an account and obtain an API key for API access. Browshot offers free, premium, and private browser instances. The right instance depends on whether you need a particular device, browser behavior, concurrency, or private capacity. The feature page describes the available instance categories and published credit model; check it before estimating current account limits or costs.

Find an instance ID available to your account in the Browshot dashboard or instance API. Do not copy the example ID below blindly: instance IDs depend on Browshot’s current instance catalog and your access.

For occasional manual checks, the dashboard may be enough. For a repeatable monitor, the API is more suitable because your scheduler can send the same request each time. Browshot describes its Simple API as easier but slower than the complete API; use the complete API when you need asynchronous status checks, hooks, batching, or greater control.

3. Capture a baseline with the complete API

The complete API creates a screenshot job, which may finish immediately or remain queued or processing. The sample below creates a fresh, full-page PNG for a desktop instance, polls until completion, and downloads the screenshot. Replace the placeholders and install Python’s requests package first.

python -m pip install requests
import os
import time
from pathlib import Path
from urllib.parse import urlparse

import requests

API_KEY = os.environ["BROWSHOT_API_KEY"]
INSTANCE_ID = os.environ["BROWSHOT_INSTANCE_ID"]
TARGET_URL = "https://example.com/pricing"
API = "https://api.browshot.com/api/v1"

session = requests.Session()

# Request a new capture: cache=0 prevents reuse of a recent screenshot.
created = session.get(
    f"{API}/screenshot/create",
    params={
        "key": API_KEY,
        "url": TARGET_URL,
        "instance_id": INSTANCE_ID,
        "size": "page",       # Use "screen" for the visible viewport only.
        "cache": 0,
        "delay": 5,            # Seconds after page load; tune for this page.
        "details": 1,
    },
    timeout=60,
)
created.raise_for_status()
job = created.json()

while job.get("status") in {"in_queue", "processing"}:
    time.sleep(3)
    info = session.get(
        f"{API}/screenshot/info",
        params={"key": API_KEY, "id": job["id"], "details": 1},
        timeout=30,
    )
    info.raise_for_status()
    job = info.json()

if job.get("status") != "finished":
    raise RuntimeError(f"Browshot capture failed: {job.get('error', job)}")

# Download the screenshot bytes from Browshot's returned screenshot URL.
image_url = job.get("screenshot_url")
if not image_url:
    raise RuntimeError(f"No screenshot_url in completed job: {job}")
image = session.get(image_url, timeout=60)
image.raise_for_status()

host = urlparse(TARGET_URL).netloc.replace(":", "_")
stamp = time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
out = Path("captures") / host / f"{stamp}.png"
out.parent.mkdir(parents=True, exist_ok=True)
out.write_bytes(image.content)
print(f"Saved {out}; job id={job['id']}; final_url={job.get('final_url')}")

Keep the API key in an environment variable or secret manager. The key parameter authenticates the request; avoid putting keys in source control, logs, shell history, or public job URLs. For production, use a storage location with retention and access controls rather than a local folder that may be removed when a worker is recycled.

4. Make captures repeatable

A monitor only produces useful comparisons when each capture represents the same conditions. Save a metadata record alongside every image, for example:

{
  "url": "https://example.com/pricing",
  "captured_at": "2026-10-04T12:00:00Z",
  "instance_id": "YOUR_INSTANCE_ID",
  "size": "page",
  "screen_width": 1440,
  "screen_height": 1000,
  "delay_seconds": 5,
  "job_id": "BROWSHOT_JOB_ID",
  "final_url": "https://example.com/pricing"
}

Use a stable filename or object key containing the URL identity and timestamp. Preserve the original image even after computing a diff. That lets an engineer inspect whether a highlighted difference is a real release, a transient state, or a capture artifact.

Capture size and dimensions

  • size=screen captures the browser screen; use it for a viewport-specific check.
  • size=page captures the full page; use it when changes lower down the page matter. Long pages cost more time to render and can contain more dynamic content.
  • screen_width and screen_height control desktop viewport dimensions where supported. Keep them fixed to avoid crossing responsive breakpoints between runs.
  • target accepts a CSS selector for capturing an element. This can focus monitoring on a component, but selectors can break when the page markup changes; treat a missing target as a capture failure to investigate.

Load timing and cache

Browshot documents a default post-load delay of five seconds and a default cache window of 24 hours on its API documentation. Specify cache=0 when a scheduled run must represent a new capture rather than a recent cached result. The delay allows scripts to run after the page load event; increase it only when the page needs additional rendering time, since every extra second adds latency. Browshot documentation pages have shown differing maximum delay values, so check the active API documentation rather than relying on a maximum quoted in an older example.

Use max_wait when you need a bounded wait for the page-load event; it is different from the post-load delay, which still applies. A stable delay is usually preferable to a very short delay that sometimes catches a partially rendered page and sometimes catches the completed state.

5. Add interaction for pages that need it

If the page needs a click, login, tab selection, or navigation before capture, Browshot documents automation steps with commands such as click, type, javascript, sleep, navigate, and screenshot. This example illustrates the shape of a steps array; use test credentials and the selectors for your own site. Check the current API syntax and availability for your instance before using it.

[
  {"command": "click", "element": "#sign-in"},
  {"command": "type", "element": "input[name=email]", "value": "MONITOR_USER"},
  {"command": "type", "element": "input[name=password]", "value": "INJECT_SECRET_SECURELY"},
  {"command": "click", "element": "button[type=submit]"},
  {"command": "sleep", "value": "3"},
  {"command": "navigate", "value": "https://example.com/account"},
  {"command": "sleep", "value": "4"},
  {"command": "screenshot"}
]

Automation is sensitive to selector changes, authentication challenges, and site terms. Keep credentials out of committed code, restrict who can access captures, and use an account with only the permissions needed for monitoring. Browshot documents cookies and POST data as paid-use features on its custom-request documentation; check current account conditions before depending on them. It also documents custom headers, scripts, and inline scripts. The JavaScript execution guide describes running a script after page load, with a delay long enough for that script to finish.

6. Schedule capture runs and store results

Run the capture code from a scheduler such as cron, a CI pipeline, a cloud scheduler, or a queue worker. The scheduler is responsible for recurring execution; the API capture request alone does not define a monitoring cadence. Avoid starting overlapping runs for the same URL if a slow page can still be processing.

For larger URL sets, the complete API documents multiple screenshot and batch endpoints. Batch requests can reduce orchestration overhead, but track each screenshot’s status and associate each result with its URL and run ID. For asynchronous capture, Browshot supports a completion hook: provide a callback URL and accept the documented POST notification when the screenshot finishes or errors. Make the callback idempotent because Browshot documents retries when the endpoint is slow or does not return a successful 20x response.

Store at least the original image, requested URL, final URL, capture timestamp, instance, viewport, screenshot size, delay, relevant automation version, and job status. Object storage or a database-backed artifact system helps retain history; set retention according to how far back you need to investigate changes. Do not make capture URLs public if the page contains private or customer information.

7. Compare images and route meaningful differences

Image comparison is a separate step. A basic pixel difference compares each pixel against the baseline, but small font-rendering or antialiasing changes can produce noise. A perceptual image comparison can tolerate small rendering variation, while a structural comparison can highlight larger layout shifts. Choose a tool and threshold appropriate to the pages and browsers you monitor; the Browshot sources cited here do not establish a built-in recurring visual-diff and alerting system.

  1. Align image dimensions and color mode before comparison. A size mismatch should be treated as a configuration error, not silently resized without recording it.
  2. Optionally mask known volatile regions such as a clock, rotating ad, or personalized module. Keep masks versioned and review them because broad masks can hide genuine defects.
  3. Compute a difference image and a score or changed-pixel ratio. Start by reviewing samples; there is no universal threshold that separates harmless from important changes.
  4. Compare against both the accepted baseline and, when useful, the prior run. Baseline comparison catches cumulative drift; adjacent-run comparison helps pinpoint when the change occurred.
  5. Send a link to the new capture, baseline, diff, page metadata, and score to the review destination your team uses. Require human review before replacing a baseline automatically.

Consider suppressing duplicate alerts for the same unchanged diff, but keep the underlying run records. If a change is expected because of a deployment, annotate the run or approve a new baseline rather than disabling monitoring.

8. cURL and Node.js request examples

The cURL example creates a screenshot job with the complete API. Replace the URL and instance ID. The API key is passed as a query parameter in Browshot’s documented request format; avoid sharing the command with its real key in logs or tickets.

curl -G "https://api.browshot.com/api/v1/screenshot/create" \
  --data-urlencode "key=$BROWSHOT_API_KEY" \
  --data-urlencode "url=https://example.com/pricing" \
  --data-urlencode "instance_id=$BROWSHOT_INSTANCE_ID" \
  --data-urlencode "size=page" \
  --data-urlencode "cache=0" \
  --data-urlencode "delay=5" \
  --data-urlencode "details=1"

Example Node.js request using built-in fetch (Node.js 18 or newer):

const key = process.env.BROWSHOT_API_KEY;
const instanceId = process.env.BROWSHOT_INSTANCE_ID;
if (!key || !instanceId) throw new Error("Set BROWSHOT_API_KEY and BROWSHOT_INSTANCE_ID");

const q = new URLSearchParams({
  key,
  url: "https://example.com/pricing",
  instance_id: instanceId,
  size: "page",
  cache: "0",
  delay: "5",
  details: "1",
});

const response = await fetch(`https://api.browshot.com/api/v1/screenshot/create?${q}`);
if (!response.ok) throw new Error(`Browshot API HTTP ${response.status}: ${await response.text()}`);
const job = await response.json();
console.log(job); // Persist job.id and poll screenshot/info until finished or error.

For small scripts, the Simple API can return an image through a single request. It is easier to call but Browshot says it is slower than the complete API, and the complete flow gives you a job ID and status information. Prefer the complete API for a monitor that needs explicit failure handling and metadata.

9. Options reference

Option Use in a visual monitor Notes
url Page to capture Required; store the requested and final URL.
instance_id Browser/device choice Required; keep stable between runs.
size screen or page Screen is viewport-only; page is full-page.
cache Cache window in seconds Use 0 to force a fresh screenshot.
delay Extra wait after page load Documented default is five seconds; do not set a value that makes asynchronous page state inconsistent.
max_wait Bound the wait for page load Delay still applies after the load event.
screen_width, screen_height Desktop viewport geometry Fix both values to hold the responsive layout steady.
target Capture a CSS-selected element Verify that the selector exists; markup changes can invalidate it.
details Request more screenshot/job information Useful for diagnosing final URL and errors; consult current documentation for levels.
headers Custom HTTP headers Documented with browser compatibility limits.
cookie, post_data, referer Set request or session context Browshot documents paid-use conditions for these features; protect sensitive values.
script, script_inline, steps Interact with or prepare the page Keep behavior deterministic, version scripts, and allow enough time for completion.
hook Receive completion or failure notification Make the endpoint fast, return a 20x response, and handle duplicate delivery safely.
priority Prioritize private-instance jobs Documented for private instances only.
html Save rendered HTML for inspection Documentation says this has an additional credit cost; include it only when useful.
dark, hide_popups, strict_ssl Optional capture behavior Availability depends on browser type and current API support; do not change between runs without resetting the baseline.

10. Troubleshooting

Symptom Likely cause Fix
Request rejected or HTTP 400 Invalid key, URL, required parameter, or instance ID Check the API response and X-Error where available; verify credentials, URL encoding, and account access to the instance.
Screenshot job ends in error Page failed to load, domain is unreachable, or the capture encountered an error Inspect the job error and final URL, then retry with a longer load allowance or investigate the target site’s availability.
Screenshot is stale Cached result reused Set cache=0 for a fresh capture; include cache configuration in stored metadata.
Page is missing late content Content loads after the page-load event or needs interaction Adjust delay, use a documented automation step, and keep the same wait behavior across runs.
Page is blank or incomplete Blocked resources, client-side failure, consent gate, or capture occurred too early Inspect the page in the selected browser context; test the URL, instance, delay, headers, and any required interaction separately.
Screenshot differs every run Animation, time-sensitive content, rotating ads, random data, or unstable session state Stabilize the page state where possible, mask known volatile regions in the comparison layer, or monitor a narrower target.
Diff reports changes everywhere Viewport, browser, image dimensions, or device scale changed Compare metadata first and reject incompatible image pairs; restore the approved capture configuration.
CSS target is missing Selector changed or target rendered conditionally Confirm selector spelling and page state; record missing-target captures as failures instead of treating them as a normal image.
Automation stops at login Selector mismatch, MFA, CAPTCHA, or authentication flow change Review the step sequence and account policy. Avoid attempting to bypass access controls; use an approved test account and supported access method.
Callback delivery repeats Callback did not return a timely successful 20x response Return success quickly, queue heavier work, and make processing idempotent.
Unexpected usage or cost Paid instance usage, additional capture features, or frequent fresh runs Check current plan and credit terms, reduce unnecessary cadence, reuse cached captures only when freshness permits, and inspect each job’s reported cost.

11. Performance, reliability, and cost

End-to-end time includes page loading, the configured delay, queue time, image transfer, storage, and comparison. Browshot says the free shared browsers may respond more slowly because they are shared, and the complete API supports asynchronous workflows. For many pages, use a bounded worker pool, exponential backoff for transient polling or network errors, and per-job timeouts. Do not retry invalid requests indefinitely.

Reliability comes from recording job state and making each stage recoverable. Persist the job ID before polling; if a worker restarts, resume from that ID instead of submitting duplicate captures. Treat failures as a distinct monitor outcome, not as a visual change. Add a separate alert for repeated capture failures so monitoring outages do not silently appear as green results.

Cost depends on current instance and feature terms, which can change. Browshot’s feature page describes a free instance allowance of 100 screenshots per month and credit-based premium captures, but confirm current limits and pricing in the account and official page before publishing budgets. Extra features such as saving rendered HTML may carry an additional credit charge. Estimate monthly volume as URLs × runs per day × days, then account for device variants and retries. Set a run cadence that matches how quickly you need to know about a change.

12. Or skip the browser setup

ScreenshotNeo is a website screenshot API and MCP server from Yorker Media. One GET request can return a screenshot or PDF, and its parameter names support migration from other screenshot APIs. See the ScreenshotNeo documentation.

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://example.com/pricing \
  -o shot.webp

Python:

import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://example.com/pricing"},
    timeout=90,
)
r.raise_for_status()
open("shot.webp", "wb").write(r.content)

Node.js:

const q = new URLSearchParams({
  access_key: 'YOUR_API_KEY',
  url: 'https://example.com/pricing'
});
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`ScreenshotNeo HTTP ${res.status}`);
await Bun.write("shot.webp", new Uint8Array(await res.arrayBuffer()));

Cookie banners are accepted like a visitor and removed before capture; known consent platforms, newsletter popups, and chat widgets can each be switched off. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies page verdict and billing status in headers. An MCP server provides take_screenshot, get_page_info, and capture_pdf for AI agents. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up free for ScreenshotNeo.

FAQ

Does Browshot automatically detect a visual change?

The reviewed API documentation establishes screenshot capture and related workflow features, not a general recurring diff-and-alert service. Add scheduling, comparison, and alert routing to your own workflow unless current Browshot documentation confirms a suitable built-in feature.

Should I compare with the last capture or an approved baseline?

Use an approved baseline to catch cumulative drift, and optionally compare adjacent runs to identify when a change first appeared. Keep approval explicit so a defect does not become the new expected appearance automatically.

Can I monitor a logged-in page?

Browshot documents browser automation steps for interactions such as typing, clicking, waiting, and navigating. The site’s authentication policies and any required security challenge still apply, and credentials should be handled as secrets.

How often should a monitor run?

Choose a cadence based on how soon you need to notice changes, the page’s volatility, and capture costs. A marketing page may need less frequent checks than a critical release surface; there is no universally correct schedule.

Why did two identical pages produce different screenshots?

Rendering can vary with browser instance, viewport, load timing, session state, animation, dynamic content, or cache behavior. Compare stored capture metadata before interpreting image differences.