Cookies keep FreeInd working. Optional analytics require your permission; ads for free accounts are served by Google, which shows its own consent message where required. Read our cookie policy.
Receive terminal generation events over HTTP with a verifiable HMAC signature, automatic retries with exponential backoff, and a full delivery history.
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.
GET /v1/webhooks/:id/deliveries.| 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.
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.
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.
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 endpointpending → delivered | failed | expiredPATCH /v1/webhooks/:id with status: "disabled") pauses new deliveriesid (delivery id) — at-least-once means you may see duplicates.