done or failed). Every delivery is signed so you can confirm it came from us.
Configure a webhook
Register one or more webhooks in the web panel. Each registered webhook has its own URL and its own webhook secret (shown next to the URL). The secret signs every delivery sent to that URL — copy it into the matching receiver to verify signatures. Treat it like a password. A delivery is always signed with the secret of the registered webhook whose URL it is sent to. That is the secret you verify against — no global or shared secret is involved.Choosing which webhook receives a job
You have two options:- Default (account webhook)
- Per request (callback_url)
If you register a single webhook and send no
callback_url, every terminal job is delivered to
it automatically. (When multiple webhooks are registered and no callback_url is given, the
oldest one is used as the default.)Delivery
When a job finishes, we send a singlePOST with a JSON body and these headers:
A delivery counts as successful when your endpoint responds with any
2xx status. Anything else
(or a timeout) is retried — see Retries.
Payload
string
UUID of the job.
string
Terminal status:
done or failed.object | null
Present (non-null) only when
status is done.string | null
Failure reason. Present only when
status is failed, otherwise null.object | null
Echo of the
metadata you supplied on the generation request. Keys beginning with an underscore
(_) are stripped before sending. null if you supplied none.Verify the signature
We computeHMAC-SHA256("{X-Timestamp}.{body}", secret) and send it as
X-Signature: sha256=<hex>, where body is the JSON payload serialized with sorted keys and no
whitespace.
1
Re-serialize the body
Parse the JSON and dump it with sorted keys and no spaces:
json.dumps(payload, sort_keys=True, separators=(",", ":")).2
Build the signed message
Concatenate the timestamp header and the serialized body:
"{X-Timestamp}.{body}".3
Compute and compare
Compute
HMAC-SHA256(message, your_webhook_secret), hex-encode it, prefix with sha256=, and
compare to X-Signature using a constant-time comparison.4
Reject stale deliveries
Reject the request if
|now - X-Timestamp| > 300 seconds to guard against replays.The Node.js example sorts only top-level keys. If your payload contains nested objects, sort
recursively to match our serialization.
Retries
If a delivery does not return2xx, we retry up to 6 times with increasing delays:
That is 7 delivery attempts over roughly 53 minutes before the webhook is marked failed. Each
attempt is re-signed with a fresh
X-Timestamp, so validate every delivery independently and make
your handler idempotent on job_id.