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
| Event | When it fires |
|---|---|
screenshot.completed | Any successful screenshot, OG image, or HTML-to-image request |
screenshot.failed | A request failed (timeout, unreachable URL, etc.) |
usage.threshold.80 | Monthly usage hit 80% of your plan limit |
usage.threshold.100 | Monthly usage hit 100% — requests will be rejected |
Setting up webhooks
- Go to Dashboard → Settings → Webhooks
- Enter your endpoint URL (must be HTTPS)
- Select which events you want
- 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