Skip to main content
Every generation endpoint is asynchronous. Instead of polling the GET endpoint, you can pass a webhook_url and Samsa will POST a signed event to it when the job is completed or failed.

Requesting a webhook

Add webhook_url to any generation request. It is per request — different jobs can target different URLs.
Webhooks are a convenience, not the source of truth. If every delivery attempt fails, the job result is still available from its GET endpoint. Poll as a fallback for anything critical.

Events

Webhooks fire only on terminal transitions — completed or failed. Jobs that are cancelled do not emit a webhook.

Payload

The request body is JSON. The data object is the same shape you get from the job’s GET status endpoint.
For a failed event, status is "failed" and data carries an error object using the same inner shape as the error envelope.

Signature headers

Every delivery carries three headers:
The scheme is Svix-compatible: HMAC-SHA256 over {webhook-id}.{webhook-timestamp}.{raw-body}, base64-encoded, prefixed with v1,.
Sign and verify over the raw request bytes exactly as received — never a re-serialized copy. Re-serializing (re-ordering keys, changing whitespace) changes the bytes and breaks the signature.

Verify the signature

Your per-key webhook_secret lives in the app under Settings → API Keys (each key has its own secret; rotate it independently of the key). The snippets below are verified against a fixed test vector — run them as-is and they return true, so you can confirm your implementation reproduces the signature byte-for-byte before wiring in a real secret.
The test vector’s body is a fixed reference string, so its exact bytes never change and the signature stays reproducible — it is intentionally not the live event payload shown above. Signature verification always runs over the exact raw bytes you receive, whatever their shape, so this is purely a self-test for your verifier. Likewise, webhook-id is an opaque, stable-per-event string — treat it as a token, never parse it.
In production, always also enforce the timestamp window: reject the delivery if |now − webhook-timestamp| > 300 seconds.

Retries and delivery rules

  • Success is any 2xx response returned within a 10-second timeout (connect timeout 5s). A 3xx is treated as a failure — Samsa does not follow redirects.
  • On failure, Samsa retries with the initial attempt plus 5 retries6 delivery attempts total — backing off 5s → 30s → 2m → 15m → 1h.
  • After the final attempt the delivery is dropped (and logged). The job result stays queryable from its GET endpoint.
  • Endpoints must be HTTPS. Respond quickly (2xx) and do heavy processing asynchronously so you never exceed the 10-second window.
Use the stable webhook-id to make your handler idempotent. A retried delivery reuses the same webhook-id, so you can safely ignore an event you have already processed.