Webhooks

Receive real-time notifications when screenshots complete or fail. Webhooks are sent for both individual screenshots and batch requests.

Supported Events

Event Trigger
screenshot.completed Individual screenshot captured successfully
screenshot.failed Individual screenshot capture failed
batch.completed All screenshots in a batch finished
batch.failed Batch processing failed

Configuration

Configure your webhook URL in the Dashboard Settings:

  1. Go to Dashboard → Webhooks
  2. Enter your webhook endpoint URL
  3. Copy your webhook signing secret
  4. Use the Send Test button to verify your endpoint

All screenshot and batch requests will automatically send webhooks to your configured URL.

Webhook Payload

screenshot.completed

Sent when an individual screenshot is captured successfully:

{
  "event": "screenshot.completed",
  "id": "req_abc123def456",
  "timestamp": "2024-01-18T12:00:00Z",
  "data": {
    "url": "https://example.com",
    "screenshot_url": "https://cdn.renderscreenshot.com/screenshots/ab/cd/abcd1234.png",
    "width": 1200,
    "height": 630,
    "size": 123456,
    "format": "png",
    "cached": false
  }
}

screenshot.failed

Sent when a screenshot capture fails:

{
  "event": "screenshot.failed",
  "id": "req_abc123def456",
  "timestamp": "2024-01-18T12:00:00Z",
  "data": {
    "url": "https://example.com",
    "error": "Page failed to load within 30 seconds"
  }
}

batch.completed

Sent when all screenshots in a batch have finished processing:

{
  "event": "batch.completed",
  "id": "batch_abc123",
  "timestamp": "2024-01-18T12:00:00Z",
  "data": {
    "summary": {
      "total": 10,
      "completed": 9,
      "failed": 1
    },
    "results_url": "https://api.renderscreenshot.com/v1/batches/batch_abc123"
  }
}

batch.failed

Sent when batch processing fails entirely:

{
  "event": "batch.failed",
  "id": "batch_abc123",
  "timestamp": "2024-01-18T12:00:00Z",
  "data": {
    "summary": {
      "total": 10,
      "completed": 0,
      "failed": 10
    },
    "results_url": "https://api.renderscreenshot.com/v1/batches/batch_abc123"
  }
}

Webhook Headers

Every webhook request includes these headers:

POST /webhooks/screenshots HTTP/1.1
Host: your-server.com
Content-Type: application/json
X-Webhook-ID: whk_abc123def456
X-Webhook-Timestamp: 1705579200
X-Webhook-Signature: sha256=a1b2c3d4e5f6...
User-Agent: RenderScreenshot-Webhook/1.0
Header Description
X-Webhook-ID Unique identifier for this webhook delivery
X-Webhook-Timestamp Unix timestamp when the webhook was sent
X-Webhook-Signature HMAC-SHA256 signature for verification

Verifying Signatures

Always verify webhook signatures to ensure authenticity. Use the signing secret from your dashboard.

Java

import com.renderscreenshot.sdk.Webhook;
import com.renderscreenshot.sdk.Webhook.WebhookEvent;

// In your servlet or controller
String signature = request.getHeader("X-Webhook-Signature");
String timestamp = request.getHeader("X-Webhook-Timestamp");
String payload = /* read request body as string */;

if (Webhook.verify(payload, signature, timestamp, System.getenv("WEBHOOK_SECRET"))) {
    WebhookEvent event = Webhook.parse(payload);

    if ("screenshot.completed".equals(event.getType())) {
        System.out.println("Screenshot ready: " + event.getData());
    } else if ("screenshot.failed".equals(event.getType())) {
        System.out.println("Screenshot failed: " + event.getData());
    }

    response.setStatus(200);
} else {
    response.setStatus(401);
}

Node.js

import { verifyWebhook, parseWebhook } from 'renderscreenshot';

// In your route handler (Express example)
app.post('/webhooks/screenshot', (req, res) => {
  const signature = req.headers['x-webhook-signature'];
  const timestamp = req.headers['x-webhook-timestamp'];
  const payload = JSON.stringify(req.body);

  if (verifyWebhook(payload, signature, timestamp, process.env.WEBHOOK_SECRET)) {
    const event = parseWebhook(req.body);

    if (event.type === 'screenshot.completed') {
      console.log('Screenshot ready:', event.data.response?.url);
    } else if (event.type === 'screenshot.failed') {
      console.error('Screenshot failed:', event.data.error?.message);
    }

    res.status(200).send('OK');
  } else {
    res.status(401).send('Invalid signature');
  }
});

PHP

function verifyWebhook($payload, $signature, $timestamp, $secret) {
    $expected = hash_hmac('sha256', "$timestamp.$payload", $secret);
    return hash_equals("sha256=$expected", $signature);
}

// In your endpoint
$signature = $_SERVER['HTTP_X_WEBHOOK_SIGNATURE'];
$timestamp = $_SERVER['HTTP_X_WEBHOOK_TIMESTAMP'];
$payload = file_get_contents('php://input');

if (verifyWebhook($payload, $signature, $timestamp, $_ENV['WEBHOOK_SECRET'])) {
    // Process webhook
} else {
    http_response_code(401);
}

Python

import os
from flask import Flask, request, jsonify
from renderscreenshot import verify_webhook, parse_webhook

app = Flask(__name__)

@app.route('/webhooks/screenshot', methods=['POST'])
def handle_webhook():
    signature = request.headers.get('X-Webhook-Signature')
    timestamp = request.headers.get('X-Webhook-Timestamp')
    payload = request.get_data(as_text=True)

    if verify_webhook(payload, signature, timestamp, os.environ['WEBHOOK_SECRET']):
        event = parse_webhook(request.json)

        if event['type'] == 'screenshot.completed':
            print('Screenshot ready:', event['data'].get('response', {}).get('url'))
        elif event['type'] == 'screenshot.failed':
            print('Screenshot failed:', event['data'].get('error', {}).get('message'))

        return jsonify({'status': 'ok'}), 200
    else:
        return jsonify({'error': 'Invalid signature'}), 401

Ruby

def verify_webhook(payload, signature, timestamp, secret)
  expected = OpenSSL::HMAC.hexdigest(
    'SHA256',
    secret,
    "#{timestamp}.#{payload}"
  )

  Rack::Utils.secure_compare("sha256=#{expected}", signature)
end

# In your controller
signature = request.headers['X-Webhook-Signature']
timestamp = request.headers['X-Webhook-Timestamp']
payload = request.raw_post

if verify_webhook(payload, signature, timestamp, ENV['WEBHOOK_SECRET'])
  # Process webhook
else
  head :unauthorized
end

Retry Policy

Failed webhook deliveries are retried with exponential backoff:

Attempt Delay
1 Immediate
2 ~1 minute
3 ~5 minutes
4 ~30 minutes
5 ~2 hours

After 5 failed attempts, the webhook is abandoned.

Responding to Webhooks

Return a 2xx status code to acknowledge receipt:

HTTP/1.1 200 OK

Any non-2xx response triggers a retry.

Testing Webhooks

Dashboard Test Button

Use the Send Test button in your webhook settings to send a test payload to your endpoint.

Local Development

Use tools like webhook.site or ngrok to test webhooks locally:

# Using ngrok
ngrok http 3000

# Then configure your webhook URL as:
# https://abc123.ngrok.io/webhooks/screenshots

Best Practices

  1. Always verify signatures - Prevent spoofed webhook requests
  2. Respond quickly - Return 200 immediately, process asynchronously
  3. Handle duplicates - Webhooks may be delivered more than once
  4. Log webhook IDs - Use X-Webhook-ID for debugging and deduplication

See Also

Was this page helpful?