Asynchronous Screenshots with Webhooks
The standard SavePage.io API is synchronous: you send a request and wait for the screenshot to be captured before receiving a response. This works well for most use cases, but it has limitations for long-running captures and high-volume workflows.
The problem with synchronous requests
A synchronous screenshot request ties up an HTTP connection for the entire capture duration. For a simple page, this is 2-3 seconds. For a full-page capture of a complex SPA with a 5-second delay, it can be 15-20 seconds.
At scale, this creates issues:
- HTTP timeouts on load balancers (typically 30-60 seconds)
- Connection pool exhaustion on the client
- Wasted resources holding connections open while waiting
The webhook approach
With webhooks, the flow changes:
- You send a capture request with a
callback_urlparameter - The API immediately responds with a
202 Acceptedand a capture ID - The screenshot is captured asynchronously
- When the capture completes, the API sends a POST request to your callback URL with the results
import requests
# Step 1: Initiate capture
response = requests.post(
"https://api.savepage.io/v1/capture",
json={
"url": "https://example.com",
"width": 1440,
"height": 900,
"fullpage": True,
"callback_url": "https://myapp.example.com/webhooks/screenshot"
},
headers={"Authorization": "Bearer YOUR_API_KEY"},
)
data = response.json()
# {"status": "accepted", "capture_id": "cap_abc123"}
print(f"Capture queued: {data['capture_id']}")
# Step 2: Receive the webhook (your server)
from flask import Flask, request
app = Flask(__name__)
@app.route("/webhooks/screenshot", methods=["POST"])
def handle_screenshot():
data = request.json
# {
# "capture_id": "cap_abc123",
# "status": "success",
# "image": "https://cdn.savepage.io/captures/abc123.png",
# "width": 1440,
# "height": 12400,
# "format": "png",
# "captured_at": "2025-09-20T12:00:00Z"
# }
# Process the result
save_screenshot(data["capture_id"], data["image"])
return "OK", 200
Advantages
No connection timeout risk. Your HTTP request completes immediately. The capture can take as long as it needs.
Better resource utilization. Your client is not blocked waiting for screenshots. It can submit many captures and process results as they arrive.
Natural batching. Submit 100 capture requests in a loop, then process the 100 webhook callbacks as they complete. No need to manage concurrent HTTP connections.
Retry handling. If your webhook endpoint is temporarily unavailable, the API retries the callback with exponential backoff.
Webhook security
To verify that webhook payloads come from SavePage.io and not a malicious third party:
- Each webhook includes an
X-Signatureheader - The signature is an HMAC-SHA256 of the request body using your webhook secret
- Verify the signature before processing the payload
import hmac
import hashlib
def verify_webhook(payload, signature, secret):
expected = hmac.new(
secret.encode(),
payload,
hashlib.sha256
).hexdigest()
return hmac.compare_digest(expected, signature)
When to use webhooks vs synchronous
Use synchronous requests when:
- You need the screenshot immediately (interactive tools, live previews)
- Capture time is predictable and short (viewport-only, no delay)
- Volume is low (under 100/hour)
Use webhooks when:
- Captures are long-running (full page, high delay)
- You are processing batches of URLs
- Your client cannot hold long connections
- You want fire-and-forget submission
Both approaches use the same rendering infrastructure. The difference is in how results are delivered.