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

信封、轮询循环、任务状态与错误表。

快速开始

五分钟内走通提交、轮询、下载。