Webhooks
タスク作成 時に callback_url を渡すと、自分でポーリングする代わりに、ofox が webhook で結果を配信します。タスクが終端状態に達すると、ofox は body に完全な task object を含めて、あなたの URL に POST を送信します(配信に失敗した場合はリトライされます。下のリトライセクションをご覧ください)。
callback_url は HTTPS で、SSRF 検証に合格する必要があります(private、loopback、cloud-metadata address は拒否されます)。無効な URL は作成時に 400 invalid_callback_url で拒否されます。動画を作成 をご覧ください。
Events
各タスクにつき1つの event が配信され、確定した終端 status と一致します(同じ値が data.status に入ります)。
| Event | トリガー |
|---|---|
video.generation.completed | タスクが成功した |
video.generation.failed | タスクが失敗した |
video.generation.cancelled | タスクがキャンセルされた |
video.generation.expired | タスクが期限切れになった |
Delivery payload
各配信は、以下の headers を含む callback_url への POST です:
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 | 上記の event type のいずれか |
id | string | Task id(UUID) |
created_at | integer | event timestamp、Unix 秒 |
data | object | 完全な task object — GET /v1/videos/{id} と同じ shape |
冪等性
同じ event が複数回配信される場合があります。あなたのエンドポイントが最初の配信をすでに処理した後に、リトライが発生することがあります。X-Ofox-Idempotency-Key header で重複排除してください。この値は <id>-<status>(例:9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed)で、各 key は最大1回だけ処理します。
受信確認として任意の 2xx status を返してください。非 2xx レスポンスまたはタイムアウトは配信失敗として扱われ、リトライが発生します。そのため、すばやく 2xx を返し、重い処理は非同期で行ってください。
署名検証
すべての配信には X-Ofox-Signature header が含まれます:HMAC-SHA256(raw request body, your webhook secret) を base64url エンコードした値です。payload を信頼する前に、secret を使って再計算し、constant time で比較してください。
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 や再シリアライズの前に、受信したとおりの raw bytes に対して計算してください。そうしないと署名が一致しません。Express では express.raw({ type: 'application/json' }) で body を取得します。
リトライ
配信失敗(非 2xx またはタイムアウト)は、上限付き指数バックオフと jitter で自動的にリトライされます。約40分の window で 最大10回のリトライ が行われます。リトライ 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 |
フォールバック
すべてのリトライが尽きると、ofox は配信試行を停止します。タスク結果が失われることはありません。いつでも GET /v1/videos/{id} をポーリングして最終状態を取得できます(そのエンドポイントのレート制限の対象です)。