GET /v1/task/{task_id} 直到它结束,也可以给 WideRouter 一个 https 地址,
让它主动找你。后者就是回调,它是任务 API 本身的能力,不属于某一个模型:
Nano Banana 出图和 Grok Imagine 出视频,用的是同一个字段、同一种 payload、同一套规则。
轮询每次都要带你的 API Key,回调一次都不用。 接收端是你自己托管的一个 URL,
由 WideRouter 来调用,所以没有任何凭证需要加载、转发,也就不会配错。
如果你的轮询程序曾经收到过
401,或者同时有几千个任务在跑,这一页就是写给你的。提交时多一个字段
在model 和 input 旁边加上 callback_url。请求的其他部分完全不变,
提交响应也还是那个任务 ID。
400 invalid_callback_url,
密钥错是 400 invalid_params 且 param 指向 callback_secret——而不是一个永远不会回报的任务:
WideRouter 会发给你什么
每个任务到达终态时POST 一次,Content-Type: application/json。
三个头可以让你在解析正文之前先分流和验证来源:
正文就是任务对象,与同一时刻
GET /v1/task/{task_id} 的返回逐字节相同。
一个解析器同时服务回调和轮询两条路。
- task.completed
- task.failed
task.failed 同时也是「这笔费用已冲回」的信号。
产物文件名以任务 ID 开头,落盘归档不需要改名。
投递规则
两个值得据此设计的后果:
- 做到幂等。 你慢慢地返回了
200,我们这边可能已经超时重试,同一个任务会到两次。 handler 按id去重。 - 给漏网之鱼留一条轮询兜底。 提交时存下任务 ID。如果超过该模型通常的时延几分钟 还没收到回调,就读一次任务。回调是省延迟的优化,不是投递保证。
验签
提交时带上callback_secret,这个任务的每次回调都会带 X-Wide-Signature。
它证明两件事:请求确实来自 WideRouter,正文在路上没有被改过。它不是加密——
正文里本来只有公开的 URL。
t 是发送回调时的 Unix 秒。v1 是
hex(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 秒的应答时间。
没有服务器也能先试
任何能给你一个公网https 地址的请求查看服务都可以当一次性接收端:
提交任务时把那个地址填进 callback_url,然后看任务对象落进去。
准备在自己机器上接收时,把本地端口打一条 https 隧道出去即可。
记住地址必须公网可达,局域网地址在提交时就会被拒。
尽快下载
产物 URL 在任务完成 24 小时后失效,而回调是你最早知道它们存在的时刻。 到达即下载的接收端永远不用操心过期;只存 URL、以后再读的接收端,终有一天会存下一堆指向空的链接。下一步
异步任务 API
信封、轮询循环、任务状态与错误表。
快速开始
五分钟内走通提交、轮询、下载。