Olvídate del polling. Flixly envía un POST con un evento firmado a tu URL en el momento en que una generación se completa, tanto si la petición fue síncrona (modelos de imagen) como asíncrona (modelos de vídeo, colas lentas).
Cada entrega lleva una firma sobre `${timestamp}.${body}`, así que puedes comprobar que viene realmente de nosotros y que nadie la ha manipulado.
La cabecera X-Flixly-Timestamp te permite rechazar firmas con más de 5 minutos de antigüedad: una defensa frente a reenvíos capturados.
3 intentos con backoff exponencial (inmediato, +2s, +6s). Tiempo límite de 5 segundos por intento. Consultar /generations/{id} es tu alternativa.
Cada API key tiene su propio secreto de webhook. Puedes rotarlo cuando quieras desde el panel: las firmas antiguas dejan de validarse al instante.
Dos formas de indicarle a Flixly a dónde enviar tus eventos por POST:
Define una URL de webhook por defecto y genera un secreto de firma en cada API key. Todas las generaciones enviadas con esa clave se entregan en esa URL.
Sobrescribe el valor por defecto de la clave en cada petición indicando webhook_url en el cuerpo del POST. Útil para destinos puntuales o para enrutar según el entorno.
POST /api/v1/generate
{
"model": "veo-3-fast",
"prompt": "...",
"webhook_url": "https://you/hook"
}Un POST por cada generación completada (o fallida). Cuerpo JSON, cabeceras firmadas.
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 o generation.failed.POST /api/v1/generate y que usas con GET /api/v1/generations/{id}.completed o failed en el momento de la entrega.null si falla.null si todo va bien. Ya viene saneado: puedes mostrarlo a tus usuarios sin problema.Firmamos cada entrega con HMAC-SHA256 sobre `${timestamp}.${body}`. Tú recalculas el HMAC en tu servidor con el secreto que te mostramos al generarlo y lo comparas en tiempo constante.
JSON.parse → JSON.stringify cambia los espacios y el orden de los campos, lo que rompe la firma. En Express, usa un middleware de raw body. En el App Router de Next.js, llama a await req.text() antes que a 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");
}Todas las entregas siguen el mismo patrón, sean síncronas o asíncronas:
2xx. Devuelve 200 OK con el cuerpo vacío si no tienes nada que decir.301, 302 y las demás respuestas 3xx como fallos: eliminarían silenciosamente la cabecera X-Flixly-Signature en el siguiente salto.Cada entrega lleva una cabecera X-Flixly-Delivery-Id única. Si los reintentos hacen que recibas el mismo evento dos veces (por ejemplo, nuestro reintento se disparó pero tu respuesta anterior tardó demasiado en llegarnos), usa esa cabecera para deduplicar por tu lado.
El campo id del evento es el id de la generación: el mismo en todos los reintentos de una misma entrega. El X-Flixly-Delivery-Id solo cambia entre intentos si reconstruimos el payload (algo que hoy no hacemos).