Skip to main content
Every task on the async API can be delivered two ways. You can poll GET /v1/task/{task_id} until it finishes, or you can hand WideRouter an https URL and let it come to you. The second path is the callback, and it is a property of the task API itself, not of any one model: the same field, the same payload and the same rules apply to a Nano Banana image and a Grok Imagine video.
Polling needs your API key on every request. A callback needs none. The receiver is a URL you host; WideRouter calls it, so there is no credential to load, forward or get wrong. If your poller ever answers 401, or you have thousands of tasks in flight, this is the page for you.

One field on submit

Add callback_url next to model and input. Nothing else about the request changes, and the submit response is the same task id you would get otherwise.
Both fields are checked at submit time, so a mistake fails immediately instead of producing a task that silently never reports back. A bad URL is 400 invalid_callback_url; a bad secret is 400 invalid_params with param set to callback_secret:

What WideRouter sends you

One POST per task, Content-Type: application/json, when the task reaches a terminal state. Three headers let you route and authenticate before you parse the body: The body is the task object, byte for byte what GET /v1/task/{task_id} returns at that moment. One parser serves both the callback and the polling path.
A failed task is refunded before the callback is sent, so task.failed is also your signal that the charge has been reversed. Output files are named after the task id, which makes them easy to file without renaming.

Delivery rules

Two consequences worth designing around:
  • Be idempotent. A retry after a slow 200 means the same task can arrive twice. Key your handler on id.
  • Keep a polling fallback for stragglers. Store the task id at submit time. If a callback has not arrived a few minutes after the model’s usual latency, read the task once. A callback is a latency optimization, not a delivery guarantee.

Verifying the signature

Send a callback_secret on submit and every callback for that task carries X-Wide-Signature. It proves two things: the request came from WideRouter, and the body was not altered on the way. It is not encryption — the body only ever holds public URLs.
t is the Unix time the callback was sent. v1 is hex(HMAC-SHA256(callback_secret, "<t>.<raw body>")): the timestamp, a dot, then the request body exactly as received.
1

Take the raw body

The digest is over bytes. Parsing the JSON and serializing it again changes the bytes, so read the body before any framework touches it.
2

Recompute and compare in constant time

Rebuild the HMAC with the same secret and compare with a constant-time function, never with ==.
3

Reject stale timestamps

Drop anything whose t is more than 300 seconds away from your clock in either direction. That is what stops a captured callback from being replayed later. Measured skew in testing was about 3 seconds.
Without a callback_secret there is no signature header, and the unguessable path in callback_url is the only thing between you and a forged “completed”. Either way, re-read GET /v1/task/{task_id} with your API key before you download or bill on a task: the signature proves who sent the callback, the read proves what the task is now.

A receiver in three languages

Each handler does the same five things: verify the signature over the raw body, check the event header, acknowledge with 200 immediately, hand the task id to a queue, and let a worker re-read the task and download the outputs. The verify function is the one from the previous section. The download is deliberately outside the request handler because outputs can be several megabytes and you have ten seconds to answer.
The worker reads the task with the same API key you submit with. A key that is loaded for submit but not for the read path is the single most common way a receiver ends up with task ids and no images: every GET answers 401 while every POST succeeds. Keep one configured client for both.

Trying it without a server

Any request-inspection service that gives you a public https URL works as a throwaway receiver: submit a task with that URL as callback_url and watch the task object land in the inspector. When you are ready to receive on your own machine, an https tunnel to your local port does the same job. Remember that the URL must be reachable from the internet; a LAN address is rejected at submit time.

Download promptly

Output URLs stop working 24 hours after the task completes, and the callback is the earliest moment you know they exist. A receiver that downloads on arrival never has to think about expiry; one that only stores the URL and reads it later will eventually store links to nothing.

Next steps

Async task API

The envelope, the polling loop, task states and the error table.

Quickstart

Submit, poll and download in about five minutes.