ScreenshotNeo

BlogHow-to

How to Set a Screenshot API Callback URL for Asynchronous Captures

Configure a screenshot API callback by following that provider’s async endpoint, payload, and acknowledgement rules. Here are the key differences and examples.

By the ScreenshotNeo team4 October 202610 min read

To set a screenshot API callback URL, use the callback field or dedicated callback endpoint documented for the exact capture operation, submit the capture asynchronously, and provide a publicly reachable HTTP endpoint that accepts the provider’s callback method and payload. Return the acknowledgement that provider requires, and validate signatures or shared secrets before trusting the result. Callback names, payload formats, and availability are provider-specific.

An asynchronous acceptance response means the job was queued; it does not mean the screenshot is ready. Treat the later callback as a separate server-to-server event. Before building around callbacks, verify that they are enabled for the particular provider deployment you use.

1. Identify the provider’s callback contract

Do not assume that webhook_url or callback_url works across screenshot APIs. Confirm these details in the documentation for the operation that creates the capture:

  • Submission route and mode: Does the operation accept an async flag, or is there a separate callback route?
  • Callback setting: Record the exact field name and whether it belongs in the query string or JSON body.
  • Delivery format: Determine whether the callback is JSON or multipart data, and which fields or files it contains.
  • Acknowledgement: Find the required successful HTTP status. Do not assume the provider retries failures unless its documentation says so.
  • Authentication: Check whether the provider signs callbacks or supports a shared secret, and learn the precise verification procedure.
  • Job tracking: Find the initial response format and any status or polling operation for diagnosing missing callbacks.
  • Availability: Confirm the feature is active for your account and deployment, not just described in a general protocol example.

These differences are concrete: ScreenshotOne documents async=true and webhook_url on its /take request; ScreenshotMAX documents webhook_url with async processing; Shotbot requires its dedicated POST /capture/callback route for callback captures. Shotbot’s ordinary POST /capture route is polling-only, and using callback_url there returns 400 callback_url_wrong_endpoint. [ScreenshotOne webhook guide] [ScreenshotMAX documentation] [Shotbot documentation]

2. Build a reachable callback receiver

  1. Create an application route that the provider’s servers can reach over the documented protocol. For production, use a stable public URL and HTTPS where supported. A localhost address is private to your machine and cannot receive a provider’s internet request. For local development, expose a temporary public development endpoint using a suitable tunnel, then replace it with your deployed route.
  2. Accept the documented HTTP method and content type. Many webhook flows use POST, but implement the provider’s exact contract. For example, Shotbot describes a multipart image upload, while ScreenshotOne and ScreenshotMAX document JSON-style callback bodies.
  3. Read the original request body before parsing if signature verification uses the raw bytes. Parsing and re-serializing JSON can change whitespace or byte representation and invalidate an HMAC check.
  4. Verify the signature or shared secret according to the provider’s instructions. Reject invalid events before using the screenshot or changing application state. Keep callback secrets in server-side configuration, not in browser code or logs.
  5. Validate the expected event structure and associate the callback with the capture or application operation it belongs to. Make processing safe to repeat: a callback can be delivered again in webhook systems, so avoid duplicating downstream work when you can identify an already-processed result.
  6. Return the documented success response promptly after accepting the event. If the work triggered by the callback is lengthy, save or queue the validated event and process it separately rather than holding the HTTP request open. Follow your provider’s documented acknowledgement behavior.

ScreenshotMAX explicitly requires a publicly accessible endpoint that accepts POST and returns a 2xx acknowledgement. Its async submission returns HTTP 202 while the job is queued; that is the response to the capture request, separate from the later callback acknowledgement. [ScreenshotMAX documentation]

3. Configure a provider-specific async request

ScreenshotOne: async=true and webhook_url

ScreenshotOne’s documented flow sends an asynchronous request to /take with async=true and webhook_url. Its example also uses response_type=json, store=true, and storage_return_location=true when the caller needs the uploaded file location. The callback is delivered as a POST body. Use your own API key and a receiver URL you control:

curl -G "https://api.screenshotone.com/take" \
  --data-urlencode "access_key=YOUR_SCREENSHOTONE_API_KEY" \
  --data-urlencode "url=https://example.com" \
  --data-urlencode "async=true" \
  --data-urlencode "webhook_url=https://app.example.com/webhooks/screenshotone" \
  --data-urlencode "response_type=json" \
  --data-urlencode "store=true" \
  --data-urlencode "storage_return_location=true"

Use only the documented options your workflow needs. The storage parameters are useful when the callback should include the stored file location; confirm the current response fields in ScreenshotOne’s guide. Validate its X-ScreenshotOne-Signature with HMAC SHA-256 and the secret key from the access page. That signing secret is different from the API key. [ScreenshotOne webhook guide]

ScreenshotMAX: async request with webhook_url

ScreenshotMAX documents a webhook_url field in the request and asynchronous processing. The following shows the shape of a JSON submission; add the provider’s required authentication and screenshot parameters from its current API reference:

curl -X POST "https://api.screenshotmax.com/" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_SCREENSHOTMAX_API_KEY" \
  -d '{
    "url": "https://example.com",
    "async": true,
    "webhook_url": "https://app.example.com/webhooks/screenshotmax"
  }'

Check the current documentation for the exact API route, authentication scheme, and required request fields before using this template. ScreenshotMAX documents optional webhook_signed signing and the X-Screenshotmax-WebHook-Signature header. Its guide says to calculate HMAC SHA-256 over the exact raw JSON body before parsing it. [ScreenshotMAX documentation]

Shotbot: use the dedicated callback route

Shotbot’s callback flow uses POST /capture/callback and includes callback_url in the JSON request. The ordinary POST /capture endpoint is polling-only. The sample below shows the documented field pattern; check the current Shotbot reference for required capture fields and API authentication:

curl -X POST "https://api.shotbot.io/capture/callback" \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer YOUR_SHOTBOT_API_KEY" \
  -d '{
    "url": "https://example.com",
    "callback_url": "https://app.example.com/webhooks/shotbot",
    "callback_secret": "YOUR_CALLBACK_SECRET"
  }'

Shotbot says the optional callback_secret is echoed back for the receiver to compare. It POSTs the result image as multipart data and sends status=ERR on failure; its receiver expects a 2xx response. Its status endpoint can help diagnose delivery: a callback capture’s completed status indicates accepted delivery, while upload_failed indicates endpoint refusal. [Shotbot documentation]

4. Verify signed callbacks safely

Signature headers and signing secrets differ by provider. Follow the provider’s exact algorithm, encoding, header format, and signed bytes. Do not copy one provider’s verification scheme to another.

  1. Read the request body as raw bytes.
  2. Compute the documented HMAC with the provider’s callback signing secret and hash algorithm.
  3. Parse the received signature header according to its documented format.
  4. Compare signatures using a constant-time comparison function where available.
  5. Only after verification, parse and act on the callback payload.

For ScreenshotOne, the signature is in X-ScreenshotOne-Signature; use the HMAC SHA-256 method and access-page secret key described in its guide. For ScreenshotMAX, use the X-Screenshotmax-WebHook-Signature header and calculate against the exact raw JSON body as documented. Shotbot’s documented optional callback secret is echoed for comparison. [ScreenshotOne webhook guide] [ScreenshotMAX documentation] [Shotbot documentation]

5. Choose webhooks or polling deliberately

Approach Useful when Trade-off
Callback/webhook Your application has a public receiver and wants the provider to notify it on completion. You must operate and secure an internet-reachable endpoint, handle the provider’s payload and acknowledgement contract, and monitor delivery.
Polling You cannot accept inbound requests, or the provider exposes a status endpoint and your workflow can check it. Your application must retain the job reference and make follow-up requests until it reaches a terminal state.
Synchronous capture The caller can wait for rendering and the provider supports a synchronous operation. The request stays open for the capture duration and may not suit long-running or bulk workflows.

Use the provider’s own job and delivery semantics. A webhook is an event notification, not a guarantee that a particular provider’s callback service is currently available. For example, the screenshotapis.org reference describes a webhook_url flow but warns that async callbacks return HTTP 503 without charging a credit on that deployment, and recommends synchronous rendering. Verify live availability before depending on that flow. [Screenshot API callback documentation and availability notice]

6. Troubleshoot missing or rejected callbacks

Symptom Likely cause What to check or fix
No callback arrives The URL is local/private, blocked, mistyped, or callback delivery is unavailable on the deployment. Check the URL from outside your network, inspect provider job status and delivery logs if available, and confirm current feature availability. Use polling or synchronous capture if callbacks are not active.
HTTP 400 from capture submission Callback option is on the wrong operation or a required field is missing. Match the documented route and field exactly. In Shotbot’s case, use POST /capture/callback; its polling-only route rejects callback_url with callback_url_wrong_endpoint.
HTTP 202 but no image in the response The request was accepted for background work. Do not treat 202 as the completed capture. Wait for the callback or use the documented status/polling route.
Callback receives a non-2xx response The route errors, times out, rejects the method/content type, or returns a status the provider does not accept. Inspect application logs and return the documented success status after safely accepting the event. ScreenshotMAX requires a 2xx acknowledgement; Shotbot also expects 2xx.
Signature validation fails Wrong secret, wrong header, body parsed before verification, or wrong signed bytes/encoding. Use the provider’s callback secret rather than an API key where specified. For ScreenshotMAX, verify HMAC against the exact raw JSON bytes. Check the header spelling and encoding.
Payload parser fails The route assumes JSON while the provider sends multipart, or the content type handling is wrong. Inspect the provider contract and request content type. Shotbot documents multipart image delivery; JSON parsing is not appropriate for that payload.
Callback says capture failed The capture itself failed; delivery and rendering are separate failure stages. Inspect the provider’s status/error data and retry the capture only under the provider’s documented behavior.
Development works but production fails Different public URL, TLS, firewall, route method, body-size, or secret configuration. Compare deployed route configuration with the tested receiver; check ingress and application logs, and verify the production URL is the one sent in the capture request.

7. Reliability, latency, and cost considerations

  • Keep capture and callback state separate. Persist the initial job reference and callback processing state so a caller can distinguish queued, completed, failed, and delivery-problem cases supported by the provider.
  • Make event handling safe to repeat. Record processed callback identifiers or an equivalent capture state before triggering non-repeatable downstream actions.
  • Separate acknowledgement from heavy work. Validate and durably accept the callback, return success as documented, then process expensive work outside the request where appropriate.
  • Measure end-to-end time. Track submission time, callback arrival time, and processing completion. Async capture avoids holding the original request open, but total completion time still depends on rendering and delivery.
  • Plan for failure without inventing retry guarantees. Monitor job status and callback receiver errors. Use polling or a provider-supported recovery path if a callback is missing; do not assume retries unless the provider documents them.
  • Understand billing semantics. An HTTP 202 is an acceptance status, not evidence that a screenshot was produced or billed. Check the provider’s own billing and failure rules. The screenshotapis.org deployment cited above specifically says its unavailable callback responses return 503 without charging a credit.
  • Protect data and credentials. Keep API and signing secrets server-side, avoid logging sensitive callback bodies unnecessarily, and apply retention controls to stored screenshots and payloads.

Or skip the browser setup

If you need a screenshot without building and operating a capture worker, ScreenshotNeo is a website screenshot API and MCP server. One GET request returns a PNG, JPEG, WebP, or PDF; see the ScreenshotNeo API documentation. This synchronous call saves a WebP screenshot:

curl -G "https://api.screenshotneo.com/v1/shot" \
  -d access_key=YOUR_API_KEY \
  --data-urlencode url=https://stripe.com \
  -o shot.webp
import requests

r = requests.get(
    "https://api.screenshotneo.com/v1/shot",
    params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"},
    timeout=90,
)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
await Bun.write('shot.webp', res);
  • Cookie banners, popups, and chat widgets are removed before the shot.
  • Bot checks, blank pages, and failed loads are never billed.
  • An MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf.
  • 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000.

Sign up for ScreenshotNeo’s free plan: 1,000 screenshots a month, no card required.

Frequently asked questions

Does every screenshot API use webhook_url?

No. Some use that request field, while others require a dedicated callback operation or use a different name. Follow the exact provider and endpoint documentation.

Can a callback URL point to localhost?

No, not for a provider calling your server over the internet. Use a public development tunnel for local work or deploy a reachable receiver.

Is HTTP 202 the screenshot result?

No. In the documented async flow it indicates that work was accepted or queued. The completion arrives separately by callback or is retrieved through the provider’s status flow.

Should I retry a callback that fails signature verification?

Do not process an unverified event. Check your secret, raw-body handling, header parsing, and provider-specific signing instructions before deciding how to recover the capture result.