Use Case

Website Change Detection with Screenshots & APIs

Use Case Monitoring

Monitor Website Changes with Automated Screenshots

Published February 20, 2026 · 9 min read

Website analytics monitoring dashboard

Photo by Carlos Muza on Unsplash

Websites change constantly — pricing updates, design refreshes, content modifications, and sometimes unexpected defacements. If you need to track these changes (for competitive intelligence, compliance, or quality assurance), a website change detection API built on automated screenshots is the most reliable approach.

Text-based diffing misses visual changes like layout shifts, image swaps, and CSS modifications. Screenshot-based monitoring catches everything the human eye would see.

🚀 TL;DR: Capture periodic screenshots of any webpage, compare them with pixel-level diffing, and get alerted when something changes. We'll build this step-by-step with SnapAPI.

How Visual Change Detection Works

The process is straightforward:

  • Step 1: Capture a baseline screenshot of the target page
  • Step 2: On a schedule, capture a new screenshot
  • Step 3: Compare the two images pixel-by-pixel
  • Step 4: If the difference exceeds a threshold, trigger an alert
  • Step 5: Update the baseline with the new screenshot

Capture Screenshots with SnapAPI

cURL

curl -X POST https://api.snapapi.pics/v1/screenshot \
  -H "X-API-Key: YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://competitor.com/pricing",
    "width": 1440,
    "height": 900,
    "fullPage": true,
    "blockCookieBanners": true,
    "format": "png"
  }' --output baseline.png

Node.js — Full Change Detection System

const fetch = require('node-fetch');
const fs = require('fs');
const { PNG } = require('pngjs');
const pixelmatch = require('pixelmatch');

const SNAPAPI_KEY = process.env.SNAPAPI_KEY;
const THRESHOLD = 0.01; // 1% pixel difference triggers alert

async function captureScreenshot(url) {
  const response = await fetch('https://api.snapapi.pics/v1/screenshot', {
    method: 'POST',
    headers: {
      'X-API-Key': SNAPAPI_KEY,
      'Content-Type': 'application/json'
    },
    body: JSON.stringify({
      url,
      width: 1440,
      height: 900,
      fullPage: true,
      blockCookieBanners: true,
      format: 'png'
    })
  });
  return Buffer.from(await response.arrayBuffer());
}

async function compareImages(baselinePath, newBuffer) {
  const baseline = PNG.sync.read(fs.readFileSync(baselinePath));
  const current = PNG.sync.read(newBuffer);
  
  const { width, height } = baseline;
  const diff = new PNG({ width, height });
  
  const numDiffPixels = pixelmatch(
    baseline.data, current.data, diff.data,
    width, height,
    { threshold: 0.1 }
  );
  
  const diffPercent = numDiffPixels / (width * height);
  return { diffPercent, diffImage: PNG.sync.write(diff) };
}

async function monitorPage(url, name) {
  const baselinePath = `baselines/${name}.png`;
  const newScreenshot = await captureScreenshot(url);
  
  if (!fs.existsSync(baselinePath)) {
    fs.writeFileSync(baselinePath, newScreenshot);
    console.log(`Baseline created for ${name}`);
    return;
  }
  
  const { diffPercent, diffImage } = await compareImages(
    baselinePath, newScreenshot
  );
  
  if (diffPercent > THRESHOLD) {
    console.log(`⚠️ Change detected on ${name}: ${(diffPercent * 100).toFixed(2)}%`);
    fs.writeFileSync(`diffs/${name}-${Date.now()}.png`, diffImage);
    fs.writeFileSync(`snapshots/${name}-${Date.now()}.png`, newScreenshot);
    // Send alert via Slack, email, etc.
    await sendAlert(name, diffPercent, newScreenshot);
  }
  
  // Update baseline
  fs.writeFileSync(baselinePath, newScreenshot);
}

// Monitor multiple pages
const pages = [
  { url: 'https://competitor.com/pricing', name: 'competitor-pricing' },
  { url: 'https://competitor.com/features', name: 'competitor-features' },
  { url: 'https://yoursite.com', name: 'our-homepage' }
];

for (const page of pages) {
  await monitorPage(page.url, page.name);
}

Python — Change Detection

import requests
import os
from PIL import Image
from io import BytesIO
import numpy as np
from datetime import datetime

SNAPAPI_KEY = os.environ['SNAPAPI_KEY']
THRESHOLD = 0.01  # 1% change triggers alert

def capture_screenshot(url: str) -> bytes:
    response = requests.post(
        'https://api.snapapi.pics/v1/screenshot',
        headers={
            'X-API-Key': SNAPAPI_KEY,
            'Content-Type': 'application/json'
        },
        json={
            'url': url,
            'width': 1440,
            'height': 900,
            'fullPage': True,
            'blockCookieBanners': True,
            'format': 'png'
        }
    )
    return response.content

def compare_images(baseline_path: str, new_bytes: bytes) -> float:
    baseline = np.array(Image.open(baseline_path))
    current = np.array(Image.open(BytesIO(new_bytes)))
    
    # Resize if dimensions differ
    if baseline.shape != current.shape:
        current = np.array(
            Image.open(BytesIO(new_bytes)).resize(
                (baseline.shape[1], baseline.shape[0])
            )
        )
    
    diff = np.abs(baseline.astype(int) - current.astype(int))
    changed_pixels = np.sum(diff.mean(axis=2) > 25)
    total_pixels = baseline.shape[0] * baseline.shape[1]
    return changed_pixels / total_pixels

def monitor_page(url: str, name: str):
    baseline_path = f'baselines/{name}.png'
    new_screenshot = capture_screenshot(url)
    
    if not os.path.exists(baseline_path):
        with open(baseline_path, 'wb') as f:
            f.write(new_screenshot)
        print(f'Baseline created for {name}')
        return
    
    diff_percent = compare_images(baseline_path, new_screenshot)
    
    if diff_percent > THRESHOLD:
        timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
        print(f'⚠️ Change detected: {name} ({diff_percent:.2%})')
        with open(f'snapshots/{name}_{timestamp}.png', 'wb') as f:
            f.write(new_screenshot)
    
    # Update baseline
    with open(baseline_path, 'wb') as f:
        f.write(new_screenshot)

# Run monitoring
pages = [
    ('https://competitor.com/pricing', 'competitor-pricing'),
    ('https://competitor.com/features', 'competitor-features'),
]

for url, name in pages:
    monitor_page(url, name)

Use Cases for Website Change Detection

Competitive Intelligence

Track competitor pricing pages, feature lists, and landing pages. Get notified the moment they change their pricing model or launch a new feature.

Compliance Monitoring

Regulated industries need to ensure web content stays compliant. Automated visual monitoring catches unauthorized changes to disclaimers, terms, and regulatory content.

Brand Protection

Monitor your own pages for defacement, unauthorized edits, or broken layouts caused by deployments. Catch issues before customers do.

SEO Monitoring

Track how your pages appear in search results. Combine screenshots with SnapAPI's Extract API to monitor both visual appearance and content changes simultaneously.

Adding Content Extraction

Visual diffing tells you something changed. To know what changed, combine it with content extraction:

// Capture both screenshot and content
const [screenshot, content] = await Promise.all([
  snapapi.screenshot({ url, fullPage: true }),
  snapapi.extract({ url, type: 'markdown' })
]);

// Compare text content for semantic changes
const textDiff = diffText(previousContent, content.data);

// Compare screenshots for visual changes
const visualDiff = compareImages(previousScreenshot, screenshot);

// Rich alert with both types of changes
if (visualDiff > 0.01 || textDiff.changes > 0) {
  await alert({
    page: url,
    visualChange: `${(visualDiff * 100).toFixed(1)}%`,
    textChanges: textDiff.summary,
    screenshot: screenshot
  });
}

Scheduling and Infrastructure

For production use, you'll want to run monitoring on a schedule:

  • Cron jobs: Simple and reliable for fixed schedules (hourly, daily)
  • Serverless functions: AWS Lambda + CloudWatch Events or Vercel Cron
  • Queue-based: For large numbers of pages, use a job queue (Bull, Celery) with rate limiting

SnapAPI handles the browser infrastructure — you just need to handle scheduling and storage.

Pricing for Monitoring Workloads

Website monitoring is a high-frequency use case. Here's how SnapAPI pricing works:

  • Free: 200 screenshots/month — monitor ~6 pages daily
  • Pro ($19/mo): 25,000 screenshots — monitor 100+ pages hourly
  • Business ($79/mo): 100,000 screenshots — enterprise-grade monitoring

Get Started

Build your website change detection system in under an hour. Sign up free and start monitoring today.

💡 Pro tip: Use the blockCookieBanners option to prevent GDPR consent popups from causing false-positive change detections on European websites.

Start Capturing for Free

200 screenshots/month. Screenshots, PDF, scraping, and video recording. No credit card required.

Get Free API Key →

Building a Production Change Detection System

Visual change detection catches what text diffing misses: layout shifts, color changes, image swaps, and element repositioning. Here is how to build a complete monitoring pipeline using SnapAPI screenshots as the source of truth.

Full Stack Change Monitor (Python + SQLite)

import requests, hashlib, sqlite3, smtplib
from datetime import datetime
from pathlib import Path

SNAPAPI_KEY = "YOUR_KEY"
WATCH_URLS = [
    "https://competitor.com/pricing",
    "https://competitor.com/features",
    "https://yoursite.com"
]

def capture(url: str) -> bytes:
    r = requests.get(
        "https://api.snapapi.pics/v1/screenshot",
        headers={"X-API-Key": SNAPAPI_KEY},
        params={"url": url, "format": "png", "width": 1280, "full_page": True}
    )
    return r.content

def get_hash(data: bytes) -> str:
    return hashlib.sha256(data).hexdigest()

def check_and_alert(url: str, db: sqlite3.Connection):
    img = capture(url)
    new_hash = get_hash(img)
    slug = hashlib.md5(url.encode()).hexdigest()[:8]

    cur = db.execute("SELECT hash, captured_at FROM snapshots WHERE url=? ORDER BY id DESC LIMIT 1", (url,))
    row = cur.fetchone()

    if row is None:
        # First capture
        db.execute("INSERT INTO snapshots (url, hash, captured_at) VALUES (?,?,?)",
                   (url, new_hash, datetime.now().isoformat()))
        Path(f"snapshots/{slug}_baseline.png").write_bytes(img)
        print(f"Baseline saved: {url}")
    elif row[0] != new_hash:
        # Change detected
        db.execute("INSERT INTO snapshots (url, hash, captured_at) VALUES (?,?,?)",
                   (url, new_hash, datetime.now().isoformat()))
        Path(f"snapshots/{slug}_latest.png").write_bytes(img)
        print(f"CHANGE DETECTED: {url} (was {row[0][:8]}, now {new_hash[:8]})")
        # Send alert (implement your notification here)
    else:
        print(f"No change: {url}")
    db.commit()

# Initialize
db = sqlite3.connect("monitor.db")
db.execute("CREATE TABLE IF NOT EXISTS snapshots (id INTEGER PRIMARY KEY, url TEXT, hash TEXT, captured_at TEXT)")
Path("snapshots").mkdir(exist_ok=True)

for url in WATCH_URLS:
    check_and_alert(url, db)

Text-Level Change Detection via /v1/extract

import requests, difflib, json
from datetime import datetime

SNAPAPI_KEY = "YOUR_KEY"

def extract_text(url: str) -> str:
    r = requests.get(
        "https://api.snapapi.pics/v1/extract",
        headers={"X-API-Key": SNAPAPI_KEY},
        params={"url": url, "format": "markdown"}
    )
    return r.text

def diff_pages(url: str, old_text: str, new_text: str) -> list:
    diff = list(difflib.unified_diff(
        old_text.splitlines(), new_text.splitlines(),
        lineterm='', n=3
    ))
    return diff

# Example: detect pricing changes
url = "https://competitor.com/pricing"
old_text = open("last_pricing.txt").read()
new_text = extract_text(url)

changes = diff_pages(url, old_text, new_text)
if changes:
    print(f"PRICING PAGE CHANGED ({len(changes)} line diffs):")
    for line in changes[:20]:
        print(line)
    open("last_pricing.txt", "w").write(new_text)
else:
    print("No pricing changes detected")

When to Use Screenshot vs Text Monitoring

Use Case Screenshot Hash Text Extract Diff
Competitor pricing changes Good (visual) Better (see exact change)
UI layout / design changes Best Misses visual shifts
Product availability / stock OK Best (exact keyword)
News / blog monitoring Too noisy (ads change) Best (content only)
Visual regression testing Best Not applicable

Frequency Tip

Check high-value pages (competitor pricing) every hour. Blog indexes daily. Documentation weekly. Adjust frequency to balance cost vs detection speed.

Noise Reduction

Add a clip_selector param to screenshot only a specific section (e.g., pricing table). Avoids false positives from rotating banners, live chat widgets, or timestamps.

Alert Channels

Send change alerts via Slack webhook, Telegram bot, or email. Attach the diff screenshot or text diff directly to the notification for immediate context.

FAQ

Can I clip a specific element instead of full page?

Yes. Pass a clip_selector CSS selector. SnapAPI captures only the matched element, reducing noise from page chrome and dynamic widgets.

How do I monitor pages that require login?

Pass session cookies in the cookies parameter. SnapAPI forwards them to the Chromium session, enabling monitoring of authenticated dashboards and admin panels.

What is the minimum monitoring interval?

No minimum — you can call as frequently as your plan supports. A Growth plan gives 50K calls/month, enough to monitor 50 URLs every 30 minutes around the clock.

Start Monitoring Web Changes

200 free screenshots to start. Detect competitor changes before they matter. No setup required.

Start Free Read Docs