Webhooks
Передайте callback_url, когда создаете задачу, и ofox доставит результат через webhook вместо того, чтобы вы опрашивали его. Когда задача достигает конечного состояния, ofox отправляет POST на ваш URL с полным объектом задачи в body (неудачная доставка повторяется — см. раздел «Повторы» ниже).
callback_url должен быть HTTPS и проходить SSRF-проверку (приватные, loopback- и cloud-metadata-адреса отклоняются). Недействительный URL отклоняется при создании с 400 invalid_callback_url — см. Создание видео.
События
На одну задачу доставляется одно событие, соответствующее конечному статусу, в котором она остановилась (то же значение появляется в data.status).
| Событие | Когда срабатывает |
|---|---|
video.generation.completed | Задача успешно завершилась |
video.generation.failed | Задача завершилась ошибкой |
video.generation.cancelled | Задача была отменена |
video.generation.expired | Задача истекла |
Payload доставки
Каждая доставка — это POST на ваш callback_url с такими заголовками:
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>Body представляет собой envelope. Его поле data идентично body ответа Получение статуса видео для той же задачи:
{
"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
}
}| Поле | Тип | Описание |
|---|---|---|
event | string | Один из типов событий выше |
id | string | Task id (UUID) |
created_at | integer | Время события, Unix seconds |
data | object | Полный объект задачи — та же форма, что и GET /v1/videos/{id} |
Идемпотентность
Одно и то же событие может быть доставлено более одного раза — повтор может сработать после того, как ваш эндпоинт уже обработал первую доставку. Дедуплицируйте по заголовку X-Ofox-Idempotency-Key, который равен <id>-<status> (например, 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed), и обрабатывайте каждый ключ не более одного раза.
Ответьте любым статусом 2xx, чтобы подтвердить получение. Ответ не-2xx или таймаут считается неудачной доставкой и запускает повтор, поэтому возвращайте 2xx быстро, а любую тяжелую работу выполняйте асинхронно.
Проверка подписи
Каждая доставка содержит заголовок X-Ofox-Signature: HMAC-SHA256(raw request body, your webhook secret), закодированный base64url. Пересчитайте его со своим secret и сравните за постоянное время, прежде чем доверять 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)
}Вычисляйте HMAC по сырым байтам ровно в том виде, в котором они получены, до любого JSON parse или повторной сериализации — иначе подпись не совпадет. В Express захватывайте body через express.raw({ type: 'application/json' }).
Повторы
Неудачная доставка (не-2xx или таймаут) автоматически повторяется с ограниченным экспоненциальным backoff и jitter — до 10 повторов в окне примерно 40 минут. Задержка перед повтором n равна min(5·2^(n-1), 600) секунд плюс случайный jitter.
| Повтор | Задержка | Накопительно |
|---|---|---|
| 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
Если все повторы исчерпаны, ofox прекращает попытки доставки. Результат задачи никогда не теряется — вы всегда можете опросить GET /v1/videos/{id}, чтобы получить финальное состояние (с учетом лимита запросов этого эндпоинта).