你给 POST https://api.hiapi.ai/v1/tasks 提交任务时带上了 callback.url,想让 HiAPI 在任务跑完后主动 POST 通知你,可是任务在 GET /v1/tasks/{taskId} 里明明已经是 success,你的回调接口却一条都没收到。这类问题八成不在 HiAPI 那边,而在回调地址是否可达、接收端是否按约定返回 2xx,以及轮询和回调有没有抢跑。下面按真实的回调契约逐条排查,并给出能立刻落地的修复步骤。
如果你的任务是迟迟不到终态(
data.status长时间停在queued/handling/archiving),那是另一类问题,请看 HiAPI 任务卡住或超时的诊断与重试指南。本文只讲任务已经跑完、但回调通知收不到的场景。
问题现象
典型表现是任务侧一切正常,GET /v1/tasks/{taskId} 返回 data.status 为 success(或 fail),但你部署在公网的回调接口日志里没有任何来自 HiAPI 的 POST 记录;或者偶尔收到、经常丢;又或者同一个 taskId 被投递了两次,被你的去重逻辑当成了异常。
先建立一个事实:回调只是取结果的一种方式,不是唯一通道。HiAPI 的异步任务永远可以用 GET /v1/tasks/{taskId} 主动轮询拿到终态,回调(Webhook)只是帮你省掉轮询。所以"回调收不到"不等于"结果拿不到",先用轮询确认任务本身是好的,再定位投递问题。
回调的真实契约(先对齐,再排错)
注册回调时,callback 是 POST /v1/tasks 的顶层字段,和 model、input 平级,不能塞进 input 里:
{
"model": "gpt-image-2",
"input": { "prompt": "a calm blue data center" },
"callback": {
"url": "https://your-domain.com/hiapi/callback",
"when": "final"
}
}
几个必须记住的点:
callback.url必须是公网可达的 http(s) URL,HiAPI 从公网向它发起 POST。callback.when目前只接受final:任务到达终态时投递一次,success和fail都会触发。没有按进度或分阶段的回调。- 投递方式为
POST+Content-Type: application/json,请求体和GET /v1/tasks/{taskId}的data字段完全一致,也就是任务详情对象,含status、output、error、taskId、created、completed等。 - 投递失败(非 2xx / 超时 / 网络错误)时按 立即 → +1 分钟 → +5 分钟 → +20 分钟 重投,最多 4 次;4 次都失败就不再重试,且不影响任务本身的状态。
- 终态回调是**至少一次(at least once)**语义,不是恰好一次。同一个
taskId可能被投递多次,接收端要按taskId做幂等去重。
契约细节以官方文档为准:创建任务与 callback 字段、异步 API 总览(回调重试与幂等)。
常见原因(按命中概率排序)
1. 回调地址不可公网访问(最常见)
写成了 http://localhost:3000/...、内网 IP,或只在内网 DNS 里解析的域名。HiAPI 从公网打不到你的本机,自然投不进来。本地开发最容易踩这条。
2. 接收端没返回 2xx
handler 抛异常、返回 3xx 重定向、被鉴权中间件挡成 401/403,或 5xx。HiAPI 只认 HTTP 2xx 为投递成功,其余一律进入重试,重试窗口耗尽后就表现为"收不到"。
3. 处理超时
handler 里先做重活(下载产物、转存对象存储、写数据库、发消息)再返回 200,拖太久被判超时并触发重试,叠加次数多了就像在丢包。
4. 把 callback 放进了 input 里
它是顶层字段,放进 input 不会被识别为回调注册;而且每个模型的 input 是严格 schema,多塞未知键通常会在提交时同步报错 invalid input: <root>: additional properties 'callback' not allowed,任务压根没提交成功。
5. when 填了 final 以外的值
目前只支持 final,填别的会被拒。
6. 轮询与回调抢跑
既开了轮询又等回调,轮询先一步拿到终态并关闭了本地任务记录,等回调到达时找不到对应记录,被当成"没收到"。
7. 证书或 WAF 拦截
回调地址用了自签 / 过期证书,或 WAF 把不带常规 UA、不带你自家鉴权头的匿名 POST 当可疑流量拦下。
修复步骤
- 先确认任务本身是好的。用
GET /v1/tasks/{taskId}查data.status,是success/fail说明任务已终态,问题就在投递环节;还停在非终态则属于另一类问题(见开头的卡住排查)。 - 把回调地址换成公网 https 域名。从一台外网机器执行
curl -i -X POST https://your-domain.com/hiapi/callback -d '{}',确认能连通并返回 2xx。localhost 调试请用公网隧道把本地端口映射出去。 - 让 handler 无条件快速返回 2xx。收到请求先回 200,再异步处理下载 / 转存 / 写库;同时对该路径豁免鉴权中间件,因为 HiAPI 的回调不会带你自家的鉴权头。
- 核对
callback的位置和取值。确认它在请求体顶层(和model、input平级),url为公网 http(s),when为final。 - 按
taskId做幂等去重。收到回调先查本地是否已处理过这个taskId,没处理过再落库 / 触发后续动作;at-least-once 语义下重复到达是正常的,别把重复当异常丢掉。 - 加一条兜底轮询。对超过预期时长还没收到回调的
taskId,主动GET /v1/tasks/{taskId}补查终态。回调是优化项,轮询是底线,两者用同一套幂等逻辑收口。 - 尽快转存产物。回调体里的
output[].url是临时链接(storage为temp,expireAt为 Unix 时间,约 7 天后过期),拿到后立刻下载或转存到你自己的存储,别直接存热链。
最小验证示例
先不碰回调,用"提交 + 轮询"证明你的 key 和接入本身是通的(用裸模型 id):
# 1. 提交任务
curl -s -X POST https://api.hiapi.ai/v1/tasks \
-H "Authorization: Bearer $HIAPI_API_KEY" \
-H "Content-Type: application/json" \
-d '{"model":"gpt-image-2","input":{"prompt":"a calm blue data center"}}'
# → {"code":200,"message":"success","data":{"taskId":"tk-hiapi-..."}}
# 2. 用返回的 taskId 轮询终态
curl -s https://api.hiapi.ai/v1/tasks/tk-hiapi-xxxx \
-H "Authorization: Bearer $HIAPI_API_KEY"
# → data.status 从 queued/handling/archiving 走到 success(含 output)或 fail(含 error)
如果提交就回 HTTP 401 + {"error":{"code":"permission_denied","type":"hiapi_error","request_id":"..."}},说明 API key 不对,去控制台核对后重试。接入确认没问题后,再给 POST 体加上顶层 callback,按上面的修复步骤逐项核对接收端。
FAQ
回调一直收不到,但任务其实成功了,结果还能拿到吗?
能。任何时候都可以 GET /v1/tasks/{taskId} 读 data.status 和 data.output,回调丢失不影响你主动取结果。
回调会重试几次、间隔多久?
非 2xx / 超时 / 网络错误时按 立即 → +1 分钟 → +5 分钟 → +20 分钟 重投,最多 4 次。4 次都失败后不再重试,且不影响任务自身的 success/fail 状态。
同一个任务收到两次回调正常吗?
正常。终态回调是"至少一次"语义,按 taskId 幂等去重即可,不要把重复投递当成错误。
本地开发怎么接收回调?
localhost 和内网地址 HiAPI 打不到。要么用公网隧道把本地端口映射到公网,要么本地阶段直接改用轮询,上线再切回调。
回调请求体里都有什么字段?
和 GET /v1/tasks/{taskId} 的 data 完全一致,包含 taskId、status、output(成功时的产物数组)、error(失败时的 code 与 message)、created、completed 等。详见任务详情文档。
收不到回调会多扣费吗? 不会。任务失败本身不扣费,预留额度会退回;回调投递成功与否也不改变任务的计费结果,计费只按任务最终是否生成成功来算。





