Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

A callback (usually an HTTP webhook) lets your application submit a screenshot job, return immediately, and receive a POST when rendering succeeds or fails. The reliable pattern is: create a durable job record, submit an asynchronous request with webhook_url, authenticate and deduplicate every callback, persist the result or error, acknowledge quickly, and run slow processing separately. Keep polling as a reconciliation fallback.

What a callback changes in a screenshot workflow

A synchronous screenshot request keeps your connection open until the browser has loaded the page and produced an image. That is simple, but slow pages, JavaScript-heavy sites and queues can make request timeouts likely. An asynchronous request separates submission from rendering:

  1. Your service creates an internal job ID and stores the URL, capture options and expected callback.
  2. You submit the screenshot request with async=true (ScreenshotOne) or the provider’s asynchronous POST flow (Urlbox), plus webhook_url.
  3. The provider acknowledges submission quickly and renders in its background queue.
  4. The provider POSTs a success or error event to your callback endpoint.
  5. Your endpoint verifies authenticity, records the event idempotently and returns a fast 2xx response.
  6. A worker downloads or processes the image, updates application state and notifies the original caller.

ScreenshotOne describes this pattern for asynchronous rendering, uploading to S3 and returning the file location to the webhook. Urlbox posts after a render succeeds or an error occurs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Design the job record before writing the webhook

Do not make the callback your source of truth. Store a record before submitting the request so an event can be matched even if it arrives quickly.

Field Purpose
job_id Your stable identifier, generated before submission.
requested_url and options Reconstructs what was requested and supports audits.
provider and provider render ID Routes parsing and reconciliation. Urlbox calls its identifier renderId.
status queued, succeeded, failed or duplicate.
result location Durable object-storage key or provider URL, not merely a temporary link.
last event fingerprint Prevents a retried or replayed callback from repeating side effects.
timestamps and raw event Supports support requests, latency analysis and recovery.

Pass an external identifier when the provider supports one. ScreenshotOne echoes external_identifier in the x-screenshotone-external-identifier header. Urlbox events include an event, a renderId and, on success, result.renderUrl plus render metadata.

Submit an asynchronous request

ScreenshotOne

Set async=true and provide webhook_url. If you ask ScreenshotOne to store the image in S3, add storage_return_location=true so the callback includes the storage location. Errors are not included by default; request them with webhook_errors=true. ScreenshotOne also exposes error information in headers.

const payload = {
  url: "https://example.com",
  async: true,
  webhook_url: "https://app.example.com/webhooks/screenshotone",
  external_identifier: "job_01J...",
  webhook_errors: true,
  storage_return_location: true
};

// Send payload using the provider's documented API endpoint and your API key.
// Save the returned provider reference with your internal job before responding.

The webhook body can contain screenshot_url and storage information. Use the storage location when available; a render URL may have a limited lifetime.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Urlbox

Urlbox accepts webhook_url and supports synchronous or asynchronous POST requests. Its asynchronous flow can be handled by webhook or polling. The JSON API is suited to larger HTML payloads and application-controlled workflows. Store the returned render reference, then accept events such as render.succeeded and failure events.

const request = {
  url: "https://example.com",
  webhook_url: "https://app.example.com/webhooks/urlbox",
  // Include the provider's asynchronous option and your render settings.
  metadata: { job_id: "job_01J..." }
};

Use the exact option names and authentication method shown in your Urlbox account documentation; the important workflow property is that submission and completion are separate requests.

Build a safe callback endpoint

Preserve the raw body and verify signatures

Parse JSON only after preserving the raw bytes. ScreenshotOne signs callbacks with HMAC-SHA-256 in X-ScreenshotOne-Signature, using a secret different from the API key. Compute the digest over the exact raw body and compare it in constant time. Never log the signing secret.

import express from "express";
import crypto from "node:crypto";

const app = express();
app.use(express.raw({ type: "application/json" }));
const secret = process.env.SCREENSHOTONE_WEBHOOK_SECRET;

function validSignature(raw, supplied) {
  if (!supplied || !secret) return false;
  const expected = crypto.createHmac("sha256", secret).update(raw).digest("hex");
  const a = Buffer.from(expected, "utf8");
  const b = Buffer.from(supplied, "utf8");
  return a.length === b.length && crypto.timingSafeEqual(a, b);
}

app.post("/webhooks/screenshotone", async (req, res) => {
  const raw = req.body; // preserve before JSON.parse
  if (!validSignature(raw, req.get("X-ScreenshotOne-Signature"))) {
    return res.sendStatus(401);
  }
  let event;
  try { event = JSON.parse(raw.toString("utf8")); }
  catch { return res.sendStatus(400); }

  // Extract your external identifier or provider reference.
  // Atomically insert an event key; if it already exists, do no work.
  // Persist success (screenshot_url/storage) or failure (code/message).
  // Enqueue image processing, then acknowledge immediately.
  res.sendStatus(204);
});

app.listen(process.env.PORT || 3000);

For Urlbox, authenticate according to the mechanism enabled for your account. If no provider signature is available for a particular integration, put the callback behind HTTPS, use an unguessable path or token, restrict accepted methods and source traffic where practical, and treat every event as untrusted input.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Make processing idempotent

Callbacks can be duplicated, delayed or replayed. Use a database uniqueness constraint on a provider event ID when supplied; otherwise derive a fingerprint from the provider, render ID, event type and raw body hash. Process a success transition only when the job is still pending. A duplicate should receive a successful HTTP response after the first event has been recorded, not trigger another download, charge or publication.

Acknowledge before slow work

Return a 2xx response after authentication and durable enqueueing. Image transformations, S3 copies, thumbnail generation and notifications belong in a worker. If your process crashes after acknowledging but before enqueueing, a transactional outbox (event row plus queue record in one transaction) prevents silent loss.

Success, failure and storage handling

Success events

Validate that the event belongs to the requested job, then store the screenshot URL or cloud-storage location and the provider reference. Download into storage you control when the URL is temporary. Keep the original provider metadata for diagnosis.

Error events

Record the provider error code and message, mark the attempt failed and apply a bounded retry policy appropriate to the cause. Retry transient queue or network failures; do not blindly retry authentication errors, invalid URLs or pages that consistently fail. Alert only after your policy is exhausted.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Unknown and late events

Return a non-success response for an unverifiable callback. For an authenticated event with an unknown job, retain it in a quarantine table and investigate rather than creating a new job from attacker-controlled data. A late success after your own timeout should still be reconciled if the request is valid.

Polling versus callbacks

Situation Prefer Reason
Many jobs or long renders Callback Workers are not tied up waiting and the provider pushes completion.
Local development or a one-off script Polling Less infrastructure than exposing a public HTTPS endpoint.
Provider retry behavior is undocumented Callback plus reconciliation polling You get low latency while periodic checks recover missed events.
Strict firewall or private network Polling, or a public relay The provider must be able to reach the callback URL.

Do not promise yourself that a webhook will always arrive. The cited provider documentation does not establish a universal retry schedule. Run a reconciler that finds jobs stuck in queued beyond your service-level threshold and polls provider status where supported. Use exponential backoff with jitter and a maximum age.

Operational checklist

  • Use HTTPS and authenticate every callback.
  • Preserve the raw request body for signature verification and audit.
  • Generate and store your job ID before submission.
  • Persist provider IDs, event type, timestamps and the complete error details.
  • Enforce idempotency with a unique event key or fingerprint.
  • Limit body size, validate schemas and reject unexpected content types.
  • Return 2xx quickly after durable enqueueing.
  • Keep result files in durable storage and record their retention policy.
  • Measure queue age, callback latency, signature failures, duplicate rate and reconciliation count.
  • Provide an operator replay tool that re-runs processing from the stored event without accepting a new external request.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and fixes

The provider reports a webhook URL error

Check that DNS resolves publicly, the certificate is valid, the route accepts POST, and redirects are not required. Confirm your firewall, authentication middleware and request-size limit allow the provider’s request.

Every callback returns 401

For ScreenshotOne, verify you are using the webhook secret rather than the API key, the exact X-ScreenshotOne-Signature header, and the untouched raw body. Middleware that parses and reserializes JSON before verification changes the signed bytes.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

The callback succeeds but no image is published

Inspect the event transaction and queue. A 2xx response must occur after the event and work item are durable, not before. Check that a worker is running and that the result URL has not expired.

Jobs remain queued forever

Run reconciliation polling, inspect provider status and compare your stored provider ID with the submitted request. Distinguish a lost callback from a provider-side render failure.

Duplicate images or notifications appear

Add an atomic uniqueness constraint and make downstream actions conditional on a state transition from pending to succeeded. Never use “callback received” alone as permission to repeat side effects.

Or skip the browser setup

If you only need a dependable screenshot request rather than a custom browser-and-webhook stack, ScreenshotNeo provides a website screenshot API and MCP server. Its asynchronous jobs support signed webhooks, while one-call captures can return PNG, JPEG, WebP or PDF.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo documentation for callback and capture options. Before capture it accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups and chat widgets; each cleanup step can be turned off. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads and cache hits cost nothing, and each response reports the page verdict and billing status in X-Page-Verdict and X-Billed headers. Its MCP server exposes take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients.

ScreenshotNeo includes full-page and selector capture, device presets and arbitrary viewports, retina scale, PDF controls, custom CSS and JavaScript, clicks, waits, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, transparent backgrounds, resizing, chosen-TTL caching, signed image links, bulk capture of up to 100 URLs per call, usage and OpenAPI APIs, and parameter names compatible with those used by other screenshot APIs. Plans include 1,000 free shots each month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Reference implementation flow

  1. Accept: validate the caller’s URL and options, create job_id, and return HTTP 202 with that ID.
  2. Submit: send the provider request with the callback URL and external identifier; save the provider reference.
  3. Receive: verify the signature, parse the event and locate the job.
  4. Commit: atomically record the event and transition the job once.
  5. Process: enqueue storage, transformation and notification work.
  6. Reconcile: poll stale jobs and quarantine unknown events.
  7. Report: expose job status and a durable result location to your caller.

Frequently Asked Questions

Can a webhook endpoint be private?

Only if the provider can reach it through your network arrangement. Otherwise expose a narrowly scoped public HTTPS relay that authenticates and forwards the event to your private queue.

Should I retry a callback request myself?

Retry your own downstream processing, not an already accepted event. Make event recording idempotent, and use reconciliation polling because provider retry guarantees are not established here.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

What should I return for a duplicate event?

After authenticating it, return a successful response without repeating side effects. Your uniqueness constraint should make that path safe.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.