Blog
Use Case

Using a Screenshot API for Website Monitoring & Visual Regression Testing

Learn how to use a screenshot API for website monitoring and visual regression testing — scheduled captures, webhook delivery, image diffing, and automated visual alerts.

Unit tests and end-to-end tests catch logic bugs, but they cannot catch the category of bug that makes your customers say "something looks wrong." A misaligned nav bar, a broken hero image, a JavaScript error that silently removes half the page content — these are visual regressions, and a screenshot API is the most practical tool for catching them in production. This guide shows how to wire ScreenshotFreeAPI into a monitoring and visual regression workflow using webhooks, scheduled captures, and image diffing.

Pattern 1: Scheduled Screenshot via Webhook

The simplest monitoring pattern is a cron job that POSTs a screenshot request on a schedule, then stores the result. You compare each new screenshot to the previous one and alert if the visual difference exceeds a threshold. Here is a complete Node.js implementation using the native cron pattern:

typescript
import { CronJob } from 'cron';
import fs from 'node:fs/promises';
import path from 'node:path';

const API_KEY = process.env.SFA_API_KEY!;
const BASE_URL = 'https://api.screenshotfreeapi.com';
const MONITORED_URLS = [
  'https://yoursite.com',
  'https://yoursite.com/pricing',
  'https://yoursite.com/blog',
];

async function captureAndStore(url: string): Promise<void> {
  const res = await fetch(`${BASE_URL}/screenshots/web`, {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${API_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      url,
      fullPage: true,
      format: 'png',
      viewport: { width: 1440, height: 900 },
      // Webhook receives the result; no polling needed
      webhookUrl: 'https://yourapp.com/hooks/monitoring-complete',
      // Tag the job so your webhook handler knows which URL it belongs to
      metadata: { monitoredUrl: url, capturedAt: new Date().toISOString() },
    }),
  });

  if (!res.ok) throw new Error(`Failed to queue capture for ${url}: ${res.status}`);
  const { jobId } = await res.json() as { jobId: string };
  console.log(`Queued monitoring capture for ${url}: ${jobId}`);
}

// Run every hour
const job = new CronJob('0 * * * *', async () => {
  console.log('Running scheduled visual monitoring...');
  await Promise.all(MONITORED_URLS.map(url => captureAndStore(url)));
});

job.start();

Pattern 2: Webhook Handler and Image Diffing

When the job completes, ScreenshotFreeAPI delivers a POST to your webhookUrl. Your handler downloads the new screenshot, loads the previous screenshot from storage, runs a pixel-level diff, and alerts if the difference score exceeds your threshold. The example below uses pixelmatch, a lightweight diffing library with no native dependencies.

typescript
import express from 'express';
import { PNG } from 'pngjs';
import pixelmatch from 'pixelmatch';
import fs from 'node:fs/promises';
import path from 'node:path';

const app = express();
app.use(express.json());

const SCREENSHOT_DIR = '/var/data/monitoring-screenshots';
const DIFF_THRESHOLD = 0.05; // 5% pixel difference triggers an alert

interface WebhookPayload {
  event: string;
  jobId: string;
  status: string;
  screenshots: Array<{ url: string }>;
  metadata?: { monitoredUrl?: string; capturedAt?: string };
}

app.post('/hooks/monitoring-complete', async (req, res) => {
  const payload = req.body as WebhookPayload;
  if (payload.event !== 'job.completed') return res.sendStatus(200);

  const { monitoredUrl, capturedAt } = payload.metadata ?? {};
  const screenshotUrl = payload.screenshots[0]?.url;

  if (!screenshotUrl || !monitoredUrl) return res.sendStatus(400);

  // Download the new screenshot
  const imgRes = await fetch(screenshotUrl);
  const newImageBuffer = Buffer.from(await imgRes.arrayBuffer());

  // Build a slug from the URL for file storage
  const slug = monitoredUrl.replace(/https?:///, '').replace(/[^a-z0-9]/gi, '_');
  const newPath = path.join(SCREENSHOT_DIR, `${slug}_${Date.now()}.png`);
  const previousPath = path.join(SCREENSHOT_DIR, `${slug}_latest.png`);

  await fs.writeFile(newPath, newImageBuffer);

  // Compare to the previous screenshot if it exists
  try {
    const previousBuffer = await fs.readFile(previousPath);
    const img1 = PNG.sync.read(previousBuffer);
    const img2 = PNG.sync.read(newImageBuffer);

    if (img1.width === img2.width && img1.height === img2.height) {
      const diff = new PNG({ width: img1.width, height: img1.height });
      const numDiffPixels = pixelmatch(img1.data, img2.data, diff.data, img1.width, img1.height, { threshold: 0.1 });
      const diffRatio = numDiffPixels / (img1.width * img1.height);

      console.log(`Visual diff for ${monitoredUrl}: ${(diffRatio * 100).toFixed(2)}%`);

      if (diffRatio > DIFF_THRESHOLD) {
        console.error(`⚠ Visual regression detected on ${monitoredUrl}! Diff: ${(diffRatio * 100).toFixed(2)}%`);
        // TODO: send Slack / PagerDuty / email alert here
      }
    }
  } catch {
    console.log(`No previous screenshot found for ${monitoredUrl} — storing baseline`);
  }

  // Update the "latest" reference
  await fs.writeFile(previousPath, newImageBuffer);
  res.sendStatus(200);
});

app.listen(3001);

Pattern 3: App Store Visual Monitoring

The same monitoring pattern applies to app store screenshots. Use the /monitors/app endpoint with "notifyOnChange": true and ScreenshotFreeAPI handles the scheduling and diffing server-side, delivering a webhook only when the store page actually changes. This is more efficient than polling daily from your own cron job.

bash
curl -X POST https://api.screenshotfreeapi.com/monitors/app \
  -H "Authorization: Bearer sfa_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "appName": "YourApp",
    "stores": ["ios", "android"],
    "schedule": "daily",
    "notifyOnChange": true,
    "webhookUrl": "https://yourapp.com/hooks/app-monitor"
  }'

Note

The notifyOnChange mode performs server-side diffing before delivering the webhook. Your endpoint only fires when screenshots actually change — not on every scheduled capture. This dramatically reduces noise in high-frequency monitoring setups.

Tuning Your Diff Threshold

A 5% pixel difference threshold works well for most static marketing pages. Dynamic pages (news sites, dashboards with live data) will always have some visual churn from content updates — you may need to set a higher threshold (15–20%) or limit your capture to specific elements using the description AI targeting field rather than full-page screenshots. Targeting the navigation bar or hero section of a dynamic page isolates the structural elements you actually care about from the constantly changing content area.

Set up automated visual monitoring for your site in minutes — free tier includes 100 captures per month.

Start free — no credit card

Marcus Webb

Developer Advocate at ScreenshotFreeAPI