Async, bulk & webhooks
For high-throughput workloads (hundreds or thousands of screenshots), use the async endpoints.
Async single screenshot
POST /v1/screenshot-async — same body as /v1/screenshot, but always returns 202 Accepted immediately.
curl https://websitescreenshotapi.net/api/v1/screenshot-async \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://news.ycombinator.com",
"format": "png",
"full_page": true,
"webhook_url": "https://yoursite.com/wh/screenshot",
"webhook_secret": "my-shared-secret-32-chars"
}'
Response:
{
"job_id": "job_abc",
"db_id": 42,
"status": "queued",
"status_url": "https://websitescreenshotapi.net/api/v1/screenshot/42",
"credits_charged": 1,
"credits_remaining": 1999,
"webhook_configured": true
}
Then either:
- Wait for the webhook (recommended)
- Poll
GET /v1/screenshot/42every 1-3s
Bulk batch (up to 500 URLs)
POST /v1/bulk — fire-and-forget batch of URLs with shared options.
curl https://websitescreenshotapi.net/api/v1/bulk \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"urls": [
"https://example.com",
"https://news.ycombinator.com",
"https://github.com"
],
"format": "png",
"viewport_width": 1280,
"block_ads": true,
"webhook_url": "https://yoursite.com/wh/batch",
"webhook_secret": "my-shared-secret"
}'
Response:
{
"batch_id": "bat_a1b2c3d4e5f6...",
"status": "queued",
"total": 3,
"credits_charged": 3,
"credits_remaining": 1997,
"webhook_configured": true,
"status_url": "https://websitescreenshotapi.net/api/v1/batch/bat_..."
}
Credits are debited upfront (worst case if you mix formats; for now homogeneous batches only).
Get batch status
GET /v1/batch/{batch_id} — progress + completion.
{
"batch_id": "bat_a1b2…",
"status": "processing",
"total": 3,
"processed": 2,
"failed": 0,
"progress": 0.667,
"created_at": "2026-05-06T01:00:00+00:00",
"started_at": "2026-05-06T01:00:01+00:00",
"has_webhook":true,
"results": null
}
Add ?include=results to get all individual results inline:
GET /v1/batch/bat_a1b2…?include=results
{
"...": "...",
"results": [
{
"db_id": 42,
"input_url": "https://example.com",
"url": "https://websitescreenshotapi.net/files/123/job_abc.png",
"expires_at": "2026-05-07T01:00:00+00:00",
"format": "png",
"status": "done",
"size_bytes": 17117,
"width": 1280,
"height": 720,
"duration_ms": 412
}
]
}
Webhooks
When you supply webhook_url (and optional webhook_secret), we POST a JSON body to that URL when the work is done.
Headers
POST /your-webhook HTTP/1.1
Content-Type: application/json
User-Agent: ScreenshotAPI-Webhook/1.0
X-ScreenshotAPI-Event: screenshot.completed
X-ScreenshotAPI-Signature: sha256=<hex>
Events
screenshot.completed— single async screenshot donebatch.completed— bulk batch done (one webhook per batch, not per item)
Payload — screenshot.completed
{
"event": "screenshot.completed",
"screenshot": {
"job_id": "job_…",
"db_id": 42,
"input_url": "https://example.com",
"url": "https://websitescreenshotapi.net/files/123/job_abc.png",
"expires_at": "2026-05-07T01:00:00+00:00",
"format": "png",
"status": "done",
"size_bytes": 17117,
"width": 1280,
"height": 720,
"duration_ms": 412,
"credits_charged": 1,
"error_code": null,
"error_message": null,
"created_at": "2026-05-06T01:00:00+00:00",
"completed_at": "2026-05-06T01:00:01+00:00"
},
"sent_at": "2026-05-06T01:00:01+00:00"
}
Payload — batch.completed
{
"event": "batch.completed",
"batch_id": "bat_a1b2…",
"status": "completed",
"total": 3,
"processed": 3,
"failed": 0,
"created_at": "2026-05-06T01:00:00+00:00",
"completed_at": "2026-05-06T01:00:30+00:00",
"results": [ /* array of screenshot objects */ ],
"sent_at": "2026-05-06T01:00:31+00:00"
}
Verify the signature
If you supplied webhook_secret, verify the signature with timing-safe HMAC-SHA256:
Node.js:
import crypto from 'node:crypto';
export function verify(rawBody, sigHeader, secret) {
const expected = 'sha256=' + crypto.createHmac('sha256', secret)
.update(rawBody).digest('hex');
return crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected));
}
Python:
import hmac, hashlib
def verify(raw_body: bytes, sig: str, secret: str) -> bool:
expected = "sha256=" + hmac.new(secret.encode(), raw_body, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, sig)
PHP:
function verify(string $rawBody, string $sig, string $secret): bool {
$expected = 'sha256=' . hash_hmac('sha256', $rawBody, $secret);
return hash_equals($expected, $sig);
}
Retry policy
We retry 3 times with exponential backoff (1s, 4s, 16s) on non-2xx responses. After 3 failures, the webhook is abandoned. The result remains accessible via GET /v1/screenshot/{db_id} or GET /v1/batch/{id}.
The full body is the raw POST body — don't re-serialize before HMAC verification.