Webhooks
Webhooks deliver real-time HTTP POST notifications to your endpoint when events occur in Captureze.
Note: Webhooks are available on Starter and Pro plans.
Setting Up Webhooks
- Go to Settings → Webhooks
- Click Add Webhook
- Enter your endpoint URL (must be HTTPS)
- Select which events to receive
- Save — copy the secret shown once and store it securely
You can also manage webhooks via the API: POST /api/webhooks.
Events
screenshot.completed
A screenshot was successfully captured (on schedule or manually).
screenshot.failed
A screenshot capture failed (timeout, unreachable URL, etc.).
diff.detected
A visual change was detected above the configured diff_threshold for the schedule.
Request Headers
Each webhook request includes these headers:
| Header | Description |
|---|---|
Content-Type | application/json |
X-Captureze-Signature | HMAC-SHA256 hex signature of the request body |
X-Captureze-Event | Event name (e.g. screenshot.completed) |
X-Captureze-Delivery | Unique UUID for this delivery attempt |
Payload Format
screenshot.completed
{
"event": "screenshot.completed",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"scheduleId": "550e8400-e29b-41d4-a716-446655440000",
"scheduleName": "Example Monitor",
"scheduleUrl": "https://example.com",
"screenshotId": "7f3e9a12-...",
"screenshotUrl": "https://captureze.com/screenshots/...",
"fileSize": 284512,
"diffPercent": 0.5,
"storageType": "platform"
}
} screenshot.failed
{
"event": "screenshot.failed",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"scheduleId": "550e8400-...",
"scheduleName": "Example Monitor",
"url": "https://example.com",
"error": "Navigation timeout exceeded"
}
} diff.detected
{
"event": "diff.detected",
"timestamp": "2025-01-15T10:30:00Z",
"data": {
"scheduleId": "550e8400-...",
"scheduleName": "Example Monitor",
"scheduleUrl": "https://example.com",
"screenshotId": "7f3e9a12-...",
"screenshotUrl": "https://captureze.com/screenshots/...",
"diffPercent": 5.2,
"threshold": 5,
"previousScreenshotId": "3a9f1c44-..."
}
} Verifying Signatures
The X-Captureze-Signature header is an HMAC-SHA256 hex digest
of the raw JSON request body, signed with your webhook secret:
signature = HMAC-SHA256(secret, JSON.stringify(body)) Always verify the signature before processing a webhook to ensure it came from Captureze:
const crypto = require('crypto');
// IMPORTANT: verify against the raw request body bytes,
// not a re-serialized version — use the express raw-body verify hook.
const express = require('express');
const app = express();
app.post('/webhook',
express.json({
verify: (req, _res, buf) => { req.rawBody = buf; }
}),
(req, res) => {
const signature = req.headers['x-captureze-signature'];
const secret = process.env.WEBHOOK_SECRET;
if (!signature) {
return res.status(401).send('Missing signature');
}
const expected = crypto
.createHmac('sha256', secret)
.update(req.rawBody)
.digest('hex');
const sigBuf = Buffer.from(signature, 'hex');
const expBuf = Buffer.from(expected, 'hex');
if (sigBuf.length !== expBuf.length ||
!crypto.timingSafeEqual(sigBuf, expBuf)) {
return res.status(401).send('Invalid signature');
}
const { event, data } = req.body;
console.log('Received event:', event, data);
res.sendStatus(200);
}
); Important: Parse the body as raw JSON for signature verification — avoid double-serialization.
Use express.json() and verify req.body (the already-parsed object), passing it back
through JSON.stringify() as shown above.
Retry Policy
If your endpoint returns a non-2xx status code or times out, Captureze will retry:
- 1st retry: after 1 minute
- 2nd retry: after 5 minutes
- 3rd retry: after 30 minutes
- 4th retry: after 2 hours
- 5th retry: after 24 hours
After 5 failed attempts the delivery is marked as failed. You can view the full delivery log in
Settings → Webhooks → Logs or via GET /api/webhooks/:id/logs.
Best Practices
- Respond quickly — return
200within 30 seconds; do heavy work asynchronously - Verify signatures — always validate
X-Captureze-Signaturebefore processing - Deduplicate — use
X-Captureze-Deliveryto detect duplicate retries - HTTPS only — plain HTTP endpoints are not accepted
Testing Webhooks
Use Settings → Webhooks → Send Test or POST /api/webhooks/:id/test
to send a sample payload to your endpoint without waiting for a real event.
For local development, tools like webhook.site or ngrok let you receive webhooks on localhost.