Skip to Content

Webhook 通知

建立任務時傳入 callback_url,ofox 就會用 webhook 主動推送結果,你不需要輪詢。任務進入終態時,ofox 會向該地址傳送 POST,請求體攜帶完整的任務物件(傳送失敗會重試——見下方「重試策略」章節)。

callback_url 必須是 HTTPS 並通過 SSRF 校驗(拒絕私有網路、迴路、雲端中繼資料地址)。非法地址會在建立時被拒,返回 400 invalid_callback_url——見 建立影片任務

事件類型

每個任務推送一個事件,與它最終進入的終態一一對應(該值同樣出現在 data.status 中)。

事件觸發時機
video.generation.completed任務成功
video.generation.failed任務失敗
video.generation.cancelled任務被取消
video.generation.expired任務過期

推送體

每次推送都是向你 callback_url 發起的 POST,攜帶以下 header:

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>

請求體是一個信封結構。其中 data 欄位與同一任務的 查詢任務 回應體完全一致:

{ "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 } }
欄位類型說明
eventstring上表中的事件類型之一
idstring任務 id(UUID)
created_atinteger事件時間戳,Unix 秒
dataobject完整任務物件——與 GET /v1/videos/{id} 結構相同

冪等

同一事件可能被推送多次——重試可能在你的端點已處理首次推送之後再次觸發。請用 X-Ofox-Idempotency-Key header 去重,其值為 <id>-<status>(例如 9bcf3c60-7db2-4e1a-a1b2-c3d4e5f60718-completed),每個 key 至多處理一次。

返回任意 2xx 狀態碼即視為已接收成功。非 2xx 回應或逾時會被判定為推送失敗並觸發重試,因此請盡快返回 2xx,把耗時處理放到非同步執行。

簽名驗證

每次推送都帶 X-Ofox-Signature header:HMAC-SHA256(請求體原文, 你的 webhook secret),base64url 編碼。用你的 secret 重新計算並做恆定時間比較,驗證通過後再信任推送內容。

verify-webhook.js
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 解析或重新序列化之前——否則簽名對不上。Express 中用 express.raw({ type: 'application/json' }) 取得原始請求體。

重試策略

推送失敗(非 2xx 或逾時)會自動重試,採用帶上限的指數退避加抖動(capped exponential backoff + jitter)——最多重試 10 次,總窗口約 40 分鐘。第 n 次重試前的間隔為 min(5·2^(n-1), 600) 秒,再加上隨機抖動。

重試間隔累計
15s5s
210s15s
320s35s
440s1m15s
580s2m35s
6160s5m15s
7320s10m35s
8600s(封頂)20m35s
9600s30m35s
10600s40m35s

兜底

重試全部耗盡後,ofox 停止推送。任務結果不會遺失——你隨時可以輪詢 GET /v1/videos/{id} 取得最終狀態(受該端點的限流約束)。

Last updated on