Webhooks
Informe um callback_url ao criar uma tarefa e a ofox entrega o resultado por webhook em vez de você fazer polling. Quando a tarefa chega a um estado terminal, a ofox envia um POST para sua URL com o objeto completo da tarefa no corpo (uma entrega com falha é tentada novamente — veja a seção Novas tentativas abaixo).
callback_url deve usar HTTPS e passar pela validação SSRF (endereços privados, loopback e de metadados de nuvem são rejeitados). Uma URL inválida é rejeitada na criação com 400 invalid_callback_url — veja Criar vídeo.
Eventos
Um evento é entregue por tarefa, correspondente ao status terminal em que ela terminou (o mesmo valor aparece em data.status).
| Evento | Disparado quando |
|---|---|
video.generation.completed | A tarefa foi bem-sucedida |
video.generation.failed | A tarefa falhou |
video.generation.cancelled | A tarefa foi cancelada |
video.generation.expired | A tarefa expirou |
Payload de entrega
Cada entrega é um POST para seu callback_url com estes 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>O corpo é um envelope. O campo data é idêntico ao corpo da resposta de Obter status do vídeo para a mesma tarefa:
{
"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 | Descrição |
|---|---|---|
event | string | Um dos tipos de evento acima |
id | string | Id da tarefa (UUID) |
created_at | integer | Timestamp do evento, segundos Unix |
data | object | O objeto completo da tarefa — mesmo formato de GET /v1/videos/{id} |
Idempotência
O mesmo evento pode ser entregue mais de uma vez — uma nova tentativa pode acontecer depois que seu endpoint já processou a primeira entrega. Deduplicate pelo header X-Ofox-Idempotency-Key, que é <id>-<status> (por exemplo 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed), e processe cada key no máximo uma vez.
Responda com qualquer status 2xx para confirmar o recebimento. Uma resposta não 2xx ou um timeout é tratado como falha de entrega e dispara uma nova tentativa, então retorne 2xx rapidamente e faça qualquer trabalho pesado de forma assíncrona.
Verificação da assinatura
Cada entrega carrega um header X-Ofox-Signature: HMAC-SHA256(raw request body, your webhook secret), codificado em base64url. Recalcule com seu secret e compare em tempo constante antes de confiar no 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)
}Calcule o HMAC sobre os bytes brutos exatamente como recebidos, antes de qualquer parse JSON ou nova serialização — caso contrário, a assinatura não corresponderá. No Express, capture o corpo com express.raw({ type: 'application/json' }).
Novas tentativas
Uma entrega com falha (não 2xx ou timeout) é tentada novamente automaticamente com backoff exponencial limitado mais jitter — até 10 novas tentativas ao longo de uma janela de cerca de 40 minutos. O atraso antes da tentativa n é min(5·2^(n-1), 600) segundos, mais jitter aleatório.
| Tentativa | Atraso | Cumulativo |
|---|---|---|
| 1 | 5s | 5s |
| 2 | 10s | 15s |
| 3 | 20s | 35s |
| 4 | 40s | 1m15s |
| 5 | 80s | 2m35s |
| 6 | 160s | 5m15s |
| 7 | 320s | 10m35s |
| 8 | 600s (capped) | 20m35s |
| 9 | 600s | 30m35s |
| 10 | 600s | 40m35s |
Fallback
Se todas as novas tentativas se esgotarem, a ofox para de tentar a entrega. O resultado da tarefa nunca é perdido — você sempre pode consultar GET /v1/videos/{id} por polling para buscar o estado final (sujeito ao limite de taxa desse endpoint).