webhook field when you submit an asynchronous task. When the task
finishes (succeeds or fails), SnapGen POSTs the result to your URL. No
registration, no extra credentials — just your API key.
Quick start
webhook_secret is optional — it makes deliveries
HMAC-signed so you can verify them.
We POST to exactly the URL you provide. Webhooks fire only on final states
(completed / failed) — never while the task is still running.
What you receive
The POST body is identical to the Get task status response — one parser handles both:"status": "failed", "result": null, and
error.message — they are automatically refunded.
Retries and deduplication
- Respond with any
2xxwithin 10 seconds. Acknowledge first, process asynchronously. - No
2xx(timeout or5xx): we retry after 10s, 30s, 60s, then 5m, 30m, and 2h — about three hours in total, so a deploy window or short outage on your side doesn’t lose the notification. - A
4xxresponse means your address rejected the request — we stop immediately, no retries. - In rare cases you may receive the same notification twice. Deduplicate on
id+status. - Webhooks are an optimization, not the source of truth: if all retries are
exhausted,
GET /v1/tasks/{id}still has the result. Keep a reconciliation poll for tasks you haven’t heard back about.
Webhook URL requirements
An invalid
webhook value fails the submission with an invalid_request
error — fix the URL and resubmit.
Developing locally? Private addresses can’t receive webhooks — expose your
handler with a tunnel (for example
cloudflared tunnel or ngrok http 3000) and use the tunnel URL, or just poll
GET /v1/tasks/{id} during development.Verifying authenticity
Two options, from simplest to strongest: Option 1 — re-fetch (no secret needed). Treat the callback as a notification and confirm the result with the source of truth: fetchGET /v1/tasks/{id} using your own API key
before acting on it. A forged callback cannot survive that check.
Option 2 — signed webhooks. Pass a webhook_secret (8–128 characters,
you choose it) alongside webhook when submitting:
X-Shim-Signature: t=<unix seconds>,v1=<hex> header, where
v1 = HMAC-SHA256(webhook_secret, "<t>.<raw request body>"). Compute over the
raw body bytes and reject timestamps older than five minutes:
Minimal receiver
FAQ
I submitted with a webhook but received nothing
I submitted with a webhook but received nothing
Check, in order: (1) has the task actually finished? Fetch
GET /v1/tasks/{id} — webhooks only fire on completed/failed;
(2) is your URL publicly reachable from the internet; (3) does your
endpoint return 2xx within 10 seconds — a 4xx stops delivery
permanently.Why is result.videos[].url an array?
Why is result.videos[].url an array?
Some models return multiple files per task. Handle it as an array even
when it contains one URL.
How long do result links work?
How long do result links work?
Until
expires_at (media is retained for 14 days). Download the files to
your own storage promptly.Do webhooks replace polling?
Do webhooks replace polling?
Polling stays fully supported. Webhooks are an optimization — if a
delivery is missed,
GET /v1/tasks/{id} always has the latest state.
For anything critical, keep a reconciliation poll as the fallback.Can I receive webhooks on localhost?
Can I receive webhooks on localhost?
No — private addresses are rejected for security. Use a tunnel
(
cloudflared, ngrok) during development, or poll instead.