Webhooks
Passez un callback_url lorsque vous créez une tâche et ofox livre le résultat par webhook au lieu que vous l’interrogiez. Lorsque la tâche atteint un état terminal, ofox envoie un POST à votre URL avec l’objet complet de la tâche dans le corps (une livraison échouée est relancée — voir la section Relances ci-dessous).
callback_url doit être en HTTPS et passer la validation SSRF (les adresses privées, loopback et de métadonnées cloud sont rejetées). Une URL invalide est rejetée à la création avec 400 invalid_callback_url — voir Créer une vidéo.
Événements
Un événement est livré par tâche, correspondant au statut terminal sur lequel elle s’est stabilisée (la même valeur apparaît dans data.status).
| Événement | Déclenché quand |
|---|---|
video.generation.completed | La tâche a réussi |
video.generation.failed | La tâche a échoué |
video.generation.cancelled | La tâche a été annulée |
video.generation.expired | La tâche a expiré |
Payload de livraison
Chaque livraison est un POST vers votre callback_url avec ces 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>Le corps est une enveloppe. Son champ data est identique au corps de réponse Obtenir le statut vidéo pour la même tâche :
{
"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
}
}| Champ | Type | Description |
|---|---|---|
event | string | L’un des types d’événements ci-dessus |
id | string | Id de tâche (UUID) |
created_at | integer | Horodatage de l’événement, secondes Unix |
data | object | L’objet complet de la tâche — même forme que GET /v1/videos/{id} |
Idempotence
Le même événement peut être livré plusieurs fois — une relance peut se produire après que votre endpoint a déjà traité la première livraison. Dédupliquez sur le Header X-Ofox-Idempotency-Key, qui vaut <id>-<status> (par exemple 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed), et traitez chaque clé au plus une fois.
Répondez avec n’importe quel statut 2xx pour accuser réception. Une réponse non-2xx ou un timeout est traité comme une livraison échouée et déclenche une relance ; renvoyez donc 2xx rapidement et effectuez tout traitement lourd de façon asynchrone.
Vérification de signature
Chaque livraison porte un Header X-Ofox-Signature : HMAC-SHA256(raw request body, your webhook secret), encodé en base64url. Recalculez-le avec votre secret et comparez en temps constant avant de faire confiance au 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)
}Calculez le HMAC sur les octets bruts exactement tels que reçus, avant tout parsing JSON ou toute re-sérialisation — sinon la signature ne correspondra pas. Dans Express, capturez le corps avec express.raw({ type: 'application/json' }).
Relances
Une livraison échouée (non-2xx ou timeout) est relancée automatiquement avec un backoff exponentiel plafonné plus jitter — jusqu’à 10 relances sur une fenêtre d’environ 40 minutes. Le délai avant la relance n est min(5·2^(n-1), 600) secondes, plus un jitter aléatoire.
| Relance | Délai | Cumul |
|---|---|---|
| 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
Si toutes les relances sont épuisées, ofox cesse les tentatives de livraison. Le résultat de la tâche n’est jamais perdu — vous pouvez toujours interroger GET /v1/videos/{id} pour récupérer l’état final (sous réserve de la limite de débit de cet endpoint).