Webhooks
Pasa un callback_url cuando creas una tarea y ofox entrega el resultado por webhook en lugar de que tengas que consultarlo. Cuando la tarea llega a un estado terminal, ofox envía un POST a tu URL con el objeto completo de la tarea en el cuerpo (una entrega fallida se reintenta — consulta la sección Reintentos más abajo).
callback_url debe usar HTTPS y pasar la validación SSRF (se rechazan direcciones privadas, de bucle local y de metadatos cloud). Una URL inválida se rechaza al crear la tarea con 400 invalid_callback_url; ver Crear video.
Eventos
Se entrega un evento por tarea, correspondiente al estado terminal en el que terminó (el mismo valor aparece en data.status).
| Evento | Se activa cuando |
|---|---|
video.generation.completed | La tarea tuvo éxito |
video.generation.failed | La tarea falló |
video.generation.cancelled | La tarea fue cancelada |
video.generation.expired | La tarea expiró |
Payload de entrega
Cada entrega es un POST a tu callback_url con estos headers:
POST https://your-app.example/webhooks/ofox-video
Content-Type: application/json
X-Ofox-Idempotency-Key: 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed
X-Ofox-Signature: <HMAC-SHA256, base64url>El cuerpo es un sobre. Su campo data es idéntico al cuerpo de respuesta de Obtener estado del video para la misma tarea:
{
"event": "video.generation.completed",
"id": "9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718",
"created_at": 1776211400,
"data": {
"id": "9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718",
"status": "completed",
"model": "google/veo-3.1",
"prompt": "A golden retriever running on the beach at sunset",
"unsigned_urls": ["https://upstream.example/out.mp4"],
"mirror_urls": ["https://cdn.ofox.ai/videos/vgen_xxx.mp4?sig=..."],
"usage": {
"video_seconds": 5,
"video_cost": "0.4000000000"
},
"created_at": 1776211362,
"updated_at": 1776211400
}
}| Campo | Tipo | Descripción |
|---|---|---|
event | string | Uno de los tipos de evento anteriores |
id | string | Id de tarea (UUID) |
created_at | integer | Marca de tiempo del evento, segundos Unix |
data | object | El objeto completo de la tarea: misma forma que GET /v1/videos/{id} |
Idempotencia
El mismo evento puede entregarse más de una vez: un reintento puede dispararse después de que tu endpoint ya procesó la primera entrega. Deduplica usando el header X-Ofox-Idempotency-Key, que es <id>-<status> (por ejemplo 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed), y procesa cada key como máximo una vez.
Responde con cualquier estado 2xx para confirmar la recepción. Una respuesta que no sea 2xx o un timeout se trata como entrega fallida y dispara un reintento, así que devuelve 2xx rápidamente y realiza cualquier trabajo pesado de forma asíncrona.
Verificación de firma
Cada entrega incluye un header X-Ofox-Signature: HMAC-SHA256(raw request body, your webhook secret), codificado en base64url. Vuelve a calcularlo con tu secreto y compáralo en tiempo constante antes de confiar en el payload.
import crypto from 'node:crypto'
// X-Ofox-Signature = HMAC-SHA256(raw request body, your webhook secret),
// base64url-encoded.
function verifyOfoxSignature(rawBody, signatureHeader, secret) {
if (typeof signatureHeader !== 'string') return false
const expected = crypto
.createHmac('sha256', secret)
.update(rawBody)
.digest('base64url')
const a = Buffer.from(expected)
const b = Buffer.from(signatureHeader)
// Constant-time compare; guard length first (timingSafeEqual throws on mismatch).
return a.length === b.length && crypto.timingSafeEqual(a, b)
}Calcula el HMAC sobre los bytes sin procesar exactamente como se recibieron, antes de cualquier parseo JSON o serialización de nuevo; de lo contrario, la firma no coincidirá. En Express, captura el cuerpo con express.raw({ type: 'application/json' }).
Reintentos
Una entrega fallida (no 2xx o timeout) se reintenta automáticamente con backoff exponencial limitado más jitter: hasta 10 reintentos durante una ventana de aproximadamente 40 minutos. La demora antes del reintento n es min(5·2^(n-1), 600) segundos, más jitter aleatorio.
| Reintento | Demora | Acumulado |
|---|---|---|
| 1 | 5s | 5s |
| 2 | 10s | 15s |
| 3 | 20s | 35s |
| 4 | 40s | 1m15s |
| 5 | 80s | 2m35s |
| 6 | 160s | 5m15s |
| 7 | 320s | 10m35s |
| 8 | 600s (limitado) | 20m35s |
| 9 | 600s | 30m35s |
| 10 | 600s | 40m35s |
Fallback
Si se agotan todos los reintentos, ofox deja de intentar la entrega. El resultado de la tarea nunca se pierde: siempre puedes consultar GET /v1/videos/{id} para obtener el estado final (sujeto al límite de tasa de ese endpoint).