Webhooks
Webhooks let you receive real-time notifications when screenshots are ready, instead of polling the API. When a screenshot completes or fails, we send a POST request to your specified URL with the event details.
Setup
Include a webhook_url parameter when creating a screenshot:
curl -X POST https://screenshotrun.com/api/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"webhook_url": "https://yourapp.com/webhooks/screenshot"
}'
Your webhook_url must:
- Be a publicly accessible HTTPS endpoint
- Accept POST requests with a JSON body
- Respond with a
2xxstatus code within 30 seconds
Webhook Payload
When a screenshot event occurs, we send a POST request to your webhook URL with the following payload:
screenshot.completed
{
"event": "screenshot.completed",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "completed",
"url": "https://example.com",
"options": {
"width": 1280,
"height": 800,
"format": "png",
"quality": 80,
"full_page": false,
"device": "desktop",
"block_ads": false,
"block_cookies": true,
"dark_mode": false,
"delay": 0,
"timeout": 30,
"retina": false
},
"file_size": 245760,
"mime_type": "image/png",
"width": 1280,
"height": 800,
"processing_time_ms": 3450,
"image_url": "https://screenshotrun.com/api/v1/screenshots/550e8400-.../image",
"created_at": "2026-03-09T10:30:00.000000Z",
"completed_at": "2026-03-09T10:30:05.000000Z"
},
"sent_at": "2026-03-09T10:30:05.500000Z"
}
screenshot.failed
{
"event": "screenshot.failed",
"data": {
"id": "550e8400-e29b-41d4-a716-446655440000",
"status": "failed",
"url": "https://example.com",
"error": {
"message": "Page load timed out after 30 seconds.",
"code": "TIMEOUT"
},
"created_at": "2026-03-09T10:30:00.000000Z"
},
"sent_at": "2026-03-09T10:30:35.000000Z"
}
Events
| Event | Description | When it fires |
|---|---|---|
screenshot.completed |
Screenshot captured successfully | Image is ready for download via the image_url |
screenshot.failed |
Screenshot capture failed | An error occurred during capture (timeout, invalid URL, etc.) |
Receiving Webhooks
Here are examples of how to handle incoming webhooks in different languages:
PHP (Laravel)
// routes/web.php
Route::post('/webhooks/screenshot', [WebhookController::class, 'handle']);
// app/Http/Controllers/WebhookController.php
class WebhookController extends Controller
{
public function handle(Request $request)
{
$event = $request->input('event');
$data = $request->input('data');
match ($event) {
'screenshot.completed' => $this->handleCompleted($data),
'screenshot.failed' => $this->handleFailed($data),
default => null,
};
return response()->json(['received' => true]);
}
private function handleCompleted(array $data): void
{
// Download and save the image
$image = Http::withToken(config('services.screenshotapi.key'))
->get($data['image_url']);
Storage::put("screenshots/{$data['id']}.png", $image->body());
}
private function handleFailed(array $data): void
{
Log::error("Screenshot failed: {$data['error']['message']}", $data);
}
}
Python (Flask)
from flask import Flask, request, jsonify
import requests
import os
app = Flask(__name__)
@app.route("/webhooks/screenshot", methods=["POST"])
def handle_webhook():
payload = request.get_json()
event = payload["event"]
data = payload["data"]
if event == "screenshot.completed":
# Download and save the image
headers = {"Authorization": f"Bearer {os.environ['SCREENSHOT_API_KEY']}"}
image = requests.get(data["image_url"], headers=headers)
with open(f"screenshots/{data['id']}.png", "wb") as f:
f.write(image.content)
elif event == "screenshot.failed":
print(f"Screenshot failed: {data['error']['message']}")
return jsonify({"received": True})
JavaScript (Express)
const express = require("express");
const fs = require("fs");
const app = express();
app.use(express.json());
app.post("/webhooks/screenshot", async (req, res) => {
const { event, data } = req.body;
if (event === "screenshot.completed") {
// Download and save the image
const response = await fetch(data.image_url, {
headers: { "Authorization": `Bearer ${process.env.SCREENSHOT_API_KEY}` },
});
const buffer = Buffer.from(await response.arrayBuffer());
fs.writeFileSync(`screenshots/${data.id}.png`, buffer);
}
if (event === "screenshot.failed") {
console.error(`Screenshot failed: ${data.error.message}`);
}
res.json({ received: true });
});
app.listen(3000);
Ruby (Sinatra)
require "sinatra"
require "json"
require "net/http"
post "/webhooks/screenshot" do
payload = JSON.parse(request.body.read)
event = payload["event"]
data = payload["data"]
case event
when "screenshot.completed"
# Download and save the image
uri = URI(data["image_url"])
req = Net::HTTP::Get.new(uri)
req["Authorization"] = "Bearer #{ENV['SCREENSHOT_API_KEY']}"
image = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) }
File.binwrite("screenshots/#{data['id']}.png", image.body)
when "screenshot.failed"
puts "Screenshot failed: #{data['error']['message']}"
end
{ received: true }.to_json
end
Retry Policy
If your endpoint doesn't return a 2xx status code, we retry the webhook delivery with exponential backoff:
| Attempt | Delay | Total time elapsed |
|---|---|---|
| 1st retry | 30 seconds | ~30 seconds |
| 2nd retry | 2 minutes | ~2.5 minutes |
| 3rd retry | 10 minutes | ~12.5 minutes |
After 3 failed attempts, the webhook is marked as failed and no further retries are made. You can check webhook delivery status in your dashboard.
Security
Webhook payloads are currently not signed. To verify that a webhook is genuine, we recommend the following approach:
- Verify via API — When you receive a webhook, call
GET /api/v1/screenshots/{id}with your API key to confirm the screenshot exists and matches the webhook data - Use a secret path — Include a random token in your webhook URL (e.g.,
https://yourapp.com/webhooks/screenshot?token=your_secret) and reject requests without a valid token - Restrict by IP — If possible, whitelist our server IPs in your firewall rules
Best Practices
- Respond quickly — Return a
200response immediately, then process the webhook asynchronously (e.g., using a queue) - Handle duplicates — Webhook deliveries may arrive more than once in rare cases. Use the screenshot
idto deduplicate - Use HTTPS — Always use HTTPS endpoints to ensure payload security in transit
- Log payloads — Log incoming webhooks for debugging and auditing purposes
- Handle unknown events — Gracefully ignore events you don't recognize, in case we add new event types in the future
Testing Webhooks
During development, you can use tools like ngrok or webhook.site to receive webhooks on your local machine:
# Start an ngrok tunnel to your local server
ngrok http 3000
# Use the ngrok URL as your webhook_url
curl -X POST https://screenshotrun.com/api/v1/screenshots \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://example.com",
"webhook_url": "https://abc123.ngrok.io/webhooks/screenshot"
}'