API docs menu
Reference
Webhooks
Register an https URL and we send an event when a generation finishes, so you don't have to poll. Each event is signed, so you can check it came from us.
| Endpoint | What it does |
|---|---|
| POST /v1/webhook_endpoints | Create one: url, and events (default: all of generation.succeeded, generation.failed). The answer includes the signing secret, shown once. Up to 5 per account. |
| GET /v1/webhook_endpoints | List endpoints with their last delivery. |
| DELETE /v1/webhook_endpoints/{id} | Stop sending and delete the endpoint. |
| POST /v1/webhook_endpoints/{id}/rotate_secret | A new secret, shown once. For 24 hours events are signed with both, so you can switch without missing any. |
curl
curl https://api.fittins.ai/v1/webhook_endpoints \
-H "Authorization: Bearer $FITTINS_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"url": "https://yourstore.com/hooks/fittins",
"events": [
"generation.succeeded",
"generation.failed"
]
}'The event
HTTP
POST https://yourstore.com/hooks/fittins
Fittins-Signature: t=1793905382,v1=5b1e0f7c…
Content-Type: application/json
{
"id": "evt_9Lw3Kd2PqR7sT1vX4yZ8",
"object": "event",
"type": "generation.succeeded",
"created_at": "2026-11-08T17:03:02.000Z",
"livemode": true,
"data": { "object": { /* the full generation */ } }
}Checking the signature
The Fittins-Signature header has a timestamp t and one or two v1 signatures. Each v1 is the HMAC-SHA256, in hex, of the timestamp, a dot and the raw request body, using your secret.
import crypto from "node:crypto";
// rawBody: the request body exactly as received (a string)
export function verifyFittins(rawBody, header, secret) {
const parts = header.split(",").map((p) => p.split("="));
const t = parts.find(([k]) => k === "t")?.[1];
const expected = crypto.createHmac("sha256", secret).update(`${t}.${rawBody}`).digest("hex");
const fresh = Math.abs(Date.now() / 1000 - Number(t)) < 300;
return fresh && parts.some(([k, v]) => k === "v1" && v.length === expected.length &&
crypto.timingSafeEqual(Buffer.from(v), Buffer.from(expected)));
}The JavaScript and Python libraries include this check. See Libraries.
Delivery
- Answer with any 2xx within 10 seconds. Do slow work after answering.
- A failed delivery is retried after 1 minute, 5 minutes, 30 minutes, 2 hours and 12 hours.
- An event can arrive more than once. Use its id to ignore repeats.
- An endpoint that only fails for 3 days is paused and the account owner is emailed. To start again, delete it and create a new one.
- Test keys have their own endpoints and only send test events.
A notice for one generation
Instead of an endpoint for every event, a create request can carry webhook_url: an https address we call once when that generation ends, whether it succeeded, failed or was canceled. It needs no endpoint and works with test keys too.
HTTP
POST https://yourstore.com/fittins/done/7Hq2
Content-Type: application/json
{
"id": "evt_5Wm2Lq8RzK4c",
"object": "event",
"type": "generation.succeeded",
"created_at": "2026-11-10T17:03:02.000Z",
"livemode": true,
"data": { "object": { "id": "gen_7Hq2Mz9TkQ4x1bVnR8sL", "object": "generation", "type": "model", "status": "succeeded" } }
}- It carries only the generation's id, type and status, never the result, and it isn't signed. Read the result with GET /v1/generations/{id} and your key.
- It is retried at the same times as endpoint events, and checked against the same rules for public addresses.
- Endpoint events keep coming as before; the notice doesn't replace them.
Questions? Email support@fittins.ai with the X-Request-Id of the request.Next: Usage →