Menu
webhooksintegrationstutorial

Real-Time Screenshot Notifications with Webhooks

SnapSharp Team·March 25, 2026·4 min read

Instead of polling for results, let SnapSharp push notifications to you. Webhooks fire when screenshots complete, requests fail, or your usage hits a threshold.

What you can subscribe to

EventWhen it fires
screenshot.completedAny successful screenshot, OG image, or HTML-to-image request
screenshot.failedA request failed (timeout, unreachable URL, etc.)
usage.threshold.80Monthly usage hit 80% of your plan limit
usage.threshold.100Monthly usage hit 100% — requests will be rejected

Setting up webhooks

  1. Go to Dashboard → Settings → Webhooks
  2. Enter your endpoint URL (must be HTTPS)
  3. Select which events you want
  4. Save — copy the webhook secret (shown once)

Building a webhook handler

Here's a minimal Express.js handler that verifies the signature and processes events:

import express from 'express';
import crypto from 'crypto';

const app = express();
app.use(express.raw({ type: 'application/json' }));

const WEBHOOK_SECRET = process.env.SNAPSHARP_WEBHOOK_SECRET!;

app.post('/webhooks/snapsharp', (req, res) => {
  const signature = req.headers['x-webhook-signature'] as string;
  const expected = 'sha256=' + crypto
    .createHmac('sha256', WEBHOOK_SECRET)
    .update(req.body)
    .digest('hex');

  if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(expected))) {
    return res.status(401).send('Invalid signature');
  }

  const payload = JSON.parse(req.body.toString());

  switch (payload.event) {
    case 'screenshot.completed':
      console.log(`Screenshot done: ${payload.data.url} (${payload.data.response_time_ms}ms)`);
      break;
    case 'screenshot.failed':
      console.error(`Screenshot failed: ${payload.data.url} — ${payload.data.error_message}`);
      break;
    case 'usage.threshold.80':
      console.warn(`Usage at ${payload.data.percentage}% — consider upgrading`);
      break;
    case 'usage.threshold.100':
      console.error('Monthly limit reached!');
      break;
  }

  res.status(200).send('OK');
});

Payload structure

Every webhook delivers a JSON payload:

{
  "event": "screenshot.completed",
  "timestamp": "2026-03-25T14:30:00.000Z",
  "data": {
    "request_id": "550e8400-e29b-41d4-a716-446655440000",
    "endpoint": "/v1/screenshot",
    "url": "https://example.com",
    "status_code": 200,
    "response_time_ms": 1247,
    "cached": false
  }
}

Security: always verify signatures

Every webhook request includes an X-Webhook-Signature header with an HMAC-SHA256 hash. Always verify it before processing — this confirms the request came from SnapSharp, not an attacker.

Automatic retries

If your endpoint returns a non-2xx status or doesn't respond within 10 seconds, SnapSharp retries:

  • Attempt 1: Immediate
  • Attempt 2: After 30 seconds
  • Attempt 3: After 5 minutes
  • Attempt 4 (final): After 1 hour

After all retries fail, the delivery is marked as failed. Check delivery history in the dashboard.

Use cases

  • Slack/Discord notifications — post to a channel when screenshots fail
  • Monitoring dashboards — track success rates and response times
  • Usage alerts — get warned before hitting your monthly limit
  • Pipeline triggers — kick off downstream processing when a screenshot is ready
  • Error tracking — log failed screenshots to Sentry or your error tracker

Testing

Use the Test button next to any webhook in the dashboard to send a sample payload and verify your endpoint works.

Full webhook docs: snapsharp.dev/docs/webhooks

Frequently Asked Questions

How do I test webhooks locally without deploying?

Use ngrok http 3000 or cloudflared tunnel --url http://localhost:3000 to expose your local endpoint over HTTPS. Paste the public URL into the dashboard, then click Test to send a sample payload. For unit tests, replay stored payloads against your handler and verify signature validation separately with mocked HMAC input.

What should my webhook endpoint return?

Return 200 OK (or any 2xx) within 10 seconds. Return 4xx only if you want to explicitly reject and skip retries (e.g. signature mismatch = 401). Return 5xx for transient failures so SnapSharp retries. Respond fast — process heavy work in a background job, not inside the webhook handler.

Why am I seeing duplicate webhook deliveries?

Duplicates are normal at low rates — they occur when our retry system sees a timeout even though your handler processed the request. Make your handler idempotent by deduping on data.request_id (unique per original API call). Don't rely on at-most-once delivery.

Can I subscribe the same endpoint to multiple events?

Yes — a single webhook subscription can include any combination of events. Filter inside your handler by payload.event. This is the recommended pattern: one endpoint, one signature secret, one logging pipeline.

How do I rotate the webhook signing secret?

Create a new webhook subscription (generating a fresh secret), deploy the handler supporting both secrets temporarily, then delete the old subscription. During the overlap your handler should accept signatures from either secret — a simple if-else using crypto.timingSafeEqual against both HMACs.

What's the delivery SLA for webhooks?

Webhooks fire immediately after the triggering event — typically delivered within 1–3 seconds of the source event. If your endpoint is unreachable, retries span up to ~1 hour (0s, 30s, 5m, 1h). After 4 failed attempts, delivery is marked failed and visible in the dashboard's delivery history.


Related: Webhooks docs · Pricing · Visual Diff & Website Monitoring

Real-Time Screenshot Notifications with Webhooks — SnapSharp Blog