Async callbacks
Queue a capture and receive the result as a signed webhook.
Slow pages, batch jobs and anything you do not want to block on should use the async
mode: pass callback_url and the API returns 202 immediately, then POSTs the result
to your endpoint when the render finishes.
curl -X POST https://ssnap.cc/api/v1/screenshot \
-H "Authorization: Bearer $SSNAP_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com/heavy-report",
"format": "pdf",
"network_idle": true,
"callback_url": "https://your-app.com/hooks/ssnap"
}'{
"status": "queued",
"message": "Screenshot queued; the result will be delivered to the callback URL."
}202 only means the job was accepted. Subscription, quota and url checks run when the
job executes, so failures arrive at your webhook, never as the HTTP response.
Callback requirements
callback_url passes the same public-host checks as url: http/https only, no
loopback, private, link-local or reserved targets. See
URL restrictions.
The host is re-checked a second time at delivery, immediately before the POST, because a job may run long after it was validated and DNS can be repointed in between.
Payloads
Delivered as POST with Content-Type: application/json.
{
"status": "success",
"url": "https://ssnap.cc/screenshots/0193ab…/file?expires=…&signature=…",
"cached": false
}{
"status": "error",
"error": "Monthly screenshot limit reached",
"code": "quota_exceeded"
}The url is signed and valid for 24 hours, so download the file promptly if you need to
keep it. code values are the same set listed in API error codes.
Signature headers
| Header | Value |
|---|---|
X-Ssnap-Timestamp | Unix timestamp of the delivery attempt. |
X-Ssnap-Signature | sha256= + HMAC-SHA256 of "<timestamp>.<raw body>", keyed with your webhook secret. |
Verify against the raw request body, before any JSON parsing, and reject deliveries whose timestamp is far from now to blunt replays. Worked examples in Verify webhook signatures.
Retries and delivery guarantees
- The capture job itself retries up to 3 times, backing off 10s then 30s, with a 120-second timeout per attempt.
- The webhook POST is retried up to 3 times, 500 ms apart, with a 10-second timeout.
- A delivery that still fails is logged and dropped. It does not re-run the capture, because a retry would consume a second quota slot for an image that already exists.
Design your handler to be idempotent and to return 2xx quickly; do the heavy work
after acknowledging.
Async vs sync
| Aspect | Sync | Async |
|---|---|---|
| Response | Bytes or signed url | 202 acknowledgement |
| Good for | Interactive requests, small pages | Heavy pages, PDFs, batch jobs |
| Failure reaches you as | HTTP status + code | Webhook with "status": "error" |
| Needs a public endpoint | No | Yes |