AI generation API webhooks
Receive terminal generation events over HTTP with a verifiable HMAC signature, automatic retries with exponential backoff, and a full delivery history.
Overview
Webhooks are configured per workspace. When a generation reaches a terminal state, FreeInd POSTs the event to your endpoint URL. Create endpoints in the dashboard (Notifications) or with POST /v1/webhooks. The signing secret is shown exactly once — store it securely.
- At-least-once delivery with idempotent event ids — handle duplicates gracefully.
- Up to 5 attempts with exponential backoff (base 30s, capped at 1 hour).
- 10-second delivery timeout; respond 2xx to acknowledge.
- Delivery history available at
GET /v1/webhooks/:id/deliveries.
Events
| generation.completed | Generation succeeded; output assets ready |
| generation.failed | Generation failed with an error code |
| generation.cancelled | Generation cancelled by user or operator |
| generation.expired | Generation expired while queued |
Subscribe to the events you need when creating the endpoint via eventTypes.
Request format
Each delivery is a POST with these headers and a JSON body:
X-FreeInd-Signature: t=<unix-timestamp>,v1=<hmac-sha256(secret, t + "." + body)>
X-FreeInd-Event: generation.completed
X-FreeInd-Delivery: <delivery-id>{
"id": "<delivery-id>",
"event": "generation.completed",
"data": {
"generationId": "<generation-id>",
"workspaceId": "<workspace-id>",
"mode": "text_to_image",
"modelId": "<model-id>",
"status": "succeeded",
"outputAssetIds": ["<asset-id>"],
"credits": 4,
"startedAt": "2026-08-02T10:00:00Z",
"completedAt": "2026-08-02T10:00:07Z"
},
"createdAt": "2026-08-02T10:00:07Z"
}On generation.completed, data.outputAssetIds references the generated assets — download them via GET /v1/assets/:id/download.
Signature verification
Verify every delivery before trusting it. The signature is HMAC-SHA256 over <timestamp>.<raw body> using the endpoint's signing secret. Use a constant-time comparison:
import { createHmac, timingSafeEqual } from 'node:crypto';
function verifySignature(secret, rawBody, header) {
const [timestamp, signature] = header.slice(3).split(',v1=');
const expected = createHmac('sha256', secret).update(`${timestamp}.${rawBody}`).digest('hex');
return timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
}Reject deliveries with a timestamp older than ~5 minutes to protect against replay. The same pattern applies in any language — HMAC primitives are standard.
Retries & delivery history
Deliveries are retried with exponential backoff when the endpoint returns a non-2xx status, times out, or the connection fails — up to 5 attempts. After the final attempt the delivery is marked expired. Every attempt is recorded with its response status and error:
GET /v1/webhooks/:id/deliveries— up to 100 recent deliveries per endpoint- Delivery statuses:
pending → delivered | failed | expired - Disabling an endpoint (
PATCH /v1/webhooks/:idwithstatus: "disabled") pauses new deliveries
Best practices
- Respond 2xx as fast as possible — do heavy work (downloads, fan-out) asynchronously.
- Deduplicate by
id(delivery id) — at-least-once means you may see duplicates. - Rotate secrets by creating a new endpoint, updating your consumer, then deleting the old one.
- Prefer webhooks over polling for production workloads.