Skip the polling. Flixly POSTs a signed event to your URL the moment a generation lands — whether the request was sync (image models) or async (video models, slow queues).
Every delivery carries a signature over `${timestamp}.${body}` so you can verify it actually came from us and wasn't tampered with.
X-Flixly-Timestamp header lets you reject signatures older than ~5 minutes — defends against captured replays.
3 attempts with exponential backoff (immediate, +2s, +6s). 5-second timeout per attempt. Polling /generations/{id} is your fallback.
Each API key has its own webhook secret. Rotate at any time from the dashboard — old signatures fail verification immediately.
Two ways to tell Flixly where to POST your events:
Set a default webhook URL + generate a signing secret on each API key. Every generation submitted with that key delivers to that URL.
Override the key default per-request by setting webhook_url in the POST body. Useful for one-off destinations or routing by environment.
POST /api/v1/generate
{
"model": "veo-3-fast",
"prompt": "...",
"webhook_url": "https://you/hook"
}One POST per completed (or failed) generation. JSON body, signed headers.
POST https://your-server.example.com/flixly-webhook
Content-Type: application/json
User-Agent: Flixly-Webhook/1.0
X-Flixly-Event: generation.completed
X-Flixly-Timestamp: 1781085600
X-Flixly-Signature: sha256=abc123...
X-Flixly-Delivery-Id: wh_a1b2c3d4e5f6g7h8{
"event": "generation.completed",
"id": "j5h2k9...",
"status": "completed",
"type": "TEXT_TO_IMAGE",
"model": "flux-dev",
"output_url": "https://cdn.flixly.ai/outputs/...",
"credits_charged": 1,
"error": null,
"created_at": "2026-06-06T12:00:00Z",
"completed_at": "2026-06-06T12:00:05Z"
}generation.completed or generation.failedPOST /api/v1/generate and use with GET /api/v1/generations/{id}.completed or failed by the time we deliver.null on failure.null on success. Already sanitized — safe to surface to users.We sign every delivery with HMAC-SHA256 over `${timestamp}.${body}`. You re-compute the HMAC server-side using the secret we showed you when you generated it, and compare in constant time.
JSON.parse → JSON.stringify changes whitespace and field order, which breaks the signature. In Express, use a raw-body middleware. In Next.js App Router, call await req.text() before await req.json().
import { Flixly } from "@flixly/sdk";
// In your webhook handler — Express, Hono, Next.js, etc.
export async function POST(req) {
const rawBody = await req.text(); // MUST be raw — re-stringifying breaks the signature
const signature = req.headers.get("x-flixly-signature");
const timestamp = req.headers.get("x-flixly-timestamp");
const valid = await Flixly.verifyWebhookSignature({
secret: process.env.FLIXLY_WEBHOOK_SECRET,
timestamp,
signature,
body: rawBody,
tolerance: 300, // optional — defaults to 300s replay window
});
if (!valid) {
return new Response("invalid signature", { status: 401 });
}
const event = JSON.parse(rawBody);
// event.event === "generation.completed" | "generation.failed"
// event.id, event.status, event.output_url, event.credits_charged, ...
await handleGenerationEvent(event);
return new Response("ok");
}Each delivery follows the same pattern, sync or async:
2xx response. Return 200 OK with an empty body if you have nothing to say.301, 302, and other 3xxs as failures — they would silently strip the X-Flixly-Signature header on the next hop.Each delivery carries a unique X-Flixly-Delivery-Id header. If retries cause you to receive the same event twice (e.g. our retry fired but your earlier response just took too long to reach us), use that header to dedupe on your side.
The event's id field is the generation id — same across retries of the same delivery. The X-Flixly-Delivery-Id changes per attempt only if we rebuild the payload (we don't currently).