Skip to content
Docs/Webhooks

Webhook Notifications

Coming in Pro Plan

Receive real-time notifications when your screenshots, PDFs, and batch jobs are ready. No polling required.

Overview

Webhook notifications allow your application to receive real-time HTTP POST callbacks when capture events occur. Instead of polling our API to check if a screenshot is ready, you configure a webhook URL and we push the results to you as soon as they are available.

Webhooks are especially useful for batch processing, where you submit multiple URLs and want to be notified when results are ready, rather than repeatedly checking status.

Available Events

capture.completed

Fired when a screenshot, PDF, or OG image has been successfully generated.

capture.failed

Fired when a capture request fails due to navigation error, timeout, or rendering issue.

batch.completed

Fired when all captures in a batch request have been processed (succeeded or failed).

batch.progress

Fired periodically during batch processing to report progress (every 10 completed items).

Configuration

Register a webhook endpoint by sending a POST request to the webhooks API. You can subscribe to specific events and provide a signing secret for verification.

Register a Webhook
curl -X POST "https://captureapi.dev/api/v1/webhooks" \
  -H "X-API-Key: cap_your_api_key_here" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://your-app.com/api/webhook",
    "events": ["capture.completed", "capture.failed", "batch.completed"],
    "secret": "whsec_your_signing_secret"
  }'

Payload Examples

Capture Completed

Sent when a single screenshot, PDF, or OG image is successfully generated.

capture.completed
{
  "event": "capture.completed",
  "timestamp": "2026-03-25T14:30:00Z",
  "request_id": "req_abc123def456",
  "data": {
    "url": "https://example.com",
    "type": "screenshot",
    "format": "png",
    "width": 1280,
    "height": 720,
    "file_size": 245760,
    "file_url": "https://captureapi.dev/files/req_abc123def456.png",
    "expires_at": "2026-03-26T14:30:00Z"
  }
}

Capture Failed

Sent when a capture request fails for any reason.

capture.failed
{
  "event": "capture.failed",
  "timestamp": "2026-03-25T14:30:05Z",
  "request_id": "req_xyz789",
  "data": {
    "url": "https://invalid-domain.example",
    "type": "screenshot",
    "error_code": "NAVIGATION_FAILED",
    "error_message": "Could not navigate to URL: DNS resolution failed"
  }
}

Batch Completed

Sent when all items in a batch request have been processed.

batch.completed
{
  "event": "batch.completed",
  "timestamp": "2026-03-25T14:31:00Z",
  "batch_id": "batch_abc123",
  "data": {
    "total": 10,
    "succeeded": 9,
    "failed": 1,
    "results": [
      {
        "request_id": "req_001",
        "url": "https://example.com",
        "status": "completed",
        "file_url": "https://captureapi.dev/files/req_001.png"
      },
      {
        "request_id": "req_002",
        "url": "https://invalid.example",
        "status": "failed",
        "error_code": "NAVIGATION_FAILED"
      }
    ]
  }
}

Signature Verification

Every webhook request includes an X-CaptureAPI-Signature header containing an HMAC-SHA256 signature of the request body. Always verify this signature to ensure the webhook is from CaptureAPI and has not been tampered with.

Verify Webhook Signature (Node.js)
import crypto from "crypto";

function verifyWebhookSignature(
  payload: string,
  signature: string,
  secret: string
): boolean {
  const expectedSignature = crypto
    .createHmac("sha256", secret)
    .update(payload)
    .digest("hex");

  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expectedSignature)
  );
}

// In your webhook handler:
app.post("/api/webhook", (req, res) => {
  const signature = req.headers["x-captureapi-signature"];
  const isValid = verifyWebhookSignature(
    JSON.stringify(req.body),
    signature,
    process.env.WEBHOOK_SECRET
  );

  if (!isValid) {
    return res.status(401).json({ error: "Invalid signature" });
  }

  const { event, data } = req.body;

  switch (event) {
    case "capture.completed":
      // Process the completed screenshot
      console.log("Screenshot ready:", data.file_url);
      break;
    case "capture.failed":
      // Handle the failure
      console.error("Capture failed:", data.error_message);
      break;
    case "batch.completed":
      // Process batch results
      console.log("Batch done:", data.succeeded, "/", data.total);
      break;
  }

  res.status(200).json({ received: true });
});

Retry Policy

If your endpoint returns a non-2xx status code or times out (30 second limit), we will retry the delivery with exponential backoff.

AttemptDelayDescription
1st retry30 secondsQuick retry for transient failures
2nd retry2 minutesShort wait before second attempt
3rd retry10 minutesMedium wait
4th retry1 hourExtended wait
5th retry4 hoursFinal attempt before marking as failed

After 5 failed delivery attempts, the webhook event is marked as failed. You can view failed deliveries in your dashboard and manually retry them.

Best Practices

Respond quickly

Return a 200 status code as fast as possible. Process the webhook payload asynchronously to avoid timeouts.

Verify signatures

Always verify the X-CaptureAPI-Signature header to ensure the request is authentic and has not been modified.

Handle duplicates

Use the request_id field to deduplicate events. In rare cases, the same event may be delivered more than once.

Use HTTPS

Webhook URLs must use HTTPS. We do not deliver webhooks to HTTP endpoints for security reasons.

Log everything

Store raw webhook payloads for debugging. Webhook delivery logs are available in your dashboard for 30 days.