Skip to main content
非同步 API 上的每個任務都有兩種拿結果的方式。可以一直輪詢 GET /v1/task/{task_id} 直到它結束,也可以給 WideRouter 一個 https 地址, 讓它主動找你。後者就是回撥,它是任務 API 本身的能力,不屬於某一個模型: Nano Banana 出圖和 Grok Imagine 出影片,用的是同一個欄位、同一種 payload、同一套規則。
輪詢每次都要帶你的 API Key,回撥一次都不用。 接收端是你自己託管的一個 URL, 由 WideRouter 來呼叫,所以沒有任何憑證需要載入、轉發,也就不會配錯。 如果你的輪詢程式曾經收到過 401,或者同時有幾千個任務在跑,這一頁就是寫給你的。

提交時多一個欄位

modelinput 旁邊加上 callback_url。請求的其他部分完全不變, 提交響應也還是那個任務 ID。
兩個欄位都在提交時檢查,寫錯了立刻報錯——URL 錯是 400 invalid_callback_url, 金鑰錯是 400 invalid_paramsparam 指向 callback_secret——而不是一個永遠不會回報的任務:

WideRouter 會發給你什麼

每個任務到達終態時 POST 一次,Content-Type: application/json。 三個頭可以讓你在解析正文之前先分流和驗證來源: 正文就是任務物件,與同一時刻 GET /v1/task/{task_id} 的返回逐位元組相同。 一個解析器同時服務回撥和輪詢兩條路。
失敗的任務在傳送回撥之前就已經退款,所以 task.failed 同時也是「這筆費用已衝回」的訊號。 產物檔名以任務 ID 開頭,落盤歸檔不需要改名。

投遞規則

兩個值得據此設計的後果:
  • 做到冪等。 你慢慢地返回了 200,我們這邊可能已經超時重試,同一個任務會到兩次。 handler 按 id 去重。
  • 給漏網之魚留一條輪詢兜底。 提交時存下任務 ID。如果超過該模型通常的時延幾分鐘 還沒收到回撥,就讀一次任務。回撥是省延遲的最佳化,不是投遞保證。

驗籤

提交時帶上 callback_secret,這個任務的每次回撥都會帶 X-Wide-Signature。 它證明兩件事:請求確實來自 WideRouter,正文在路上沒有被改過。它不是加密—— 正文裡本來只有公開的 URL。
t 是傳送回撥時的 Unix 秒。v1hex(HMAC-SHA256(callback_secret, "<t>.<原始正文>")):時間戳、一個點、 然後是收到的請求正文原樣。
1

拿原始正文

摘要算的是位元組。把 JSON 解析再序列化一遍位元組就變了,所以要在框架碰它之前把正文讀出來。
2

重算並恆定時間比較

用同一個金鑰重算 HMAC,用恆定時間比較函式比對,不要用 ==
3

拒絕過期的時間戳

now - t 超過 300 秒的一律丟掉,正負都算。這是防止被截獲的回撥事後重放的手段。 實測偏差約 3 秒。
不帶 callback_secret 就沒有簽名頭,callback_url 裡那段猜不到的路徑 就是你和一條偽造的「已完成」之間的全部屏障。無論哪種,下載或計費之前都先用你的 API Key 重新讀一次 GET /v1/task/{task_id}:簽名證明的是誰發的回撥, 回查證明的是任務現在是什麼狀態。

三種語言的接收端

每個 handler 做的是同樣的五件事:對原始正文驗籤,檢查事件頭,立刻用 200 應答, 把任務 ID 交給佇列,再由 worker 重新讀任務並下載產物。verify 就是上一節那個函式。下載刻意放在請求 handler 之外, 因為產物可能有好幾 MB,而你只有 10 秒的應答時間。
worker 讀任務用的是和提交同一把 API Key。「提交那邊載入了 Key、讀取那邊沒載入」 是接收端手裡只有任務 ID 卻拿不到圖的頭號原因:每個 GET 都是 401, 而每個 POST 都成功。兩條路共用一個配置好的客戶端。

沒有伺服器也能先試

任何能給你一個公網 https 地址的請求檢視服務都可以當一次性接收端: 提交任務時把那個地址填進 callback_url,然後看任務物件落進去。 準備在自己機器上接收時,把本地埠打一條 https 隧道出去即可。 記住地址必須公網可達,區域網地址在提交時就會被拒。

儘快下載

產物 URL 在任務完成 24 小時後失效,而回調是你最早知道它們存在的時刻。 到達即下載的接收端永遠不用操心過期;只存 URL、以後再讀的接收端,終有一天會存下一堆指向空的連結。

下一步

非同步任務 API

信封、輪詢迴圈、任務狀態與錯誤表。

快速開始

五分鐘內走通提交、輪詢、下載。