Skip to main content
Add a 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:
Failed tasks arrive with "status": "failed", "result": null, and error.message — they are automatically refunded.

Retries and deduplication

  • Respond with any 2xx within 10 seconds. Acknowledge first, process asynchronously.
  • No 2xx (timeout or 5xx): 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 4xx response 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: fetch GET /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:
Deliveries for that task then carry an 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

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.
Some models return multiple files per task. Handle it as an array even when it contains one URL.
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.
No — private addresses are rejected for security. Use a tunnel (cloudflared, ngrok) during development, or poll instead.