事件回调
注册一个 HTTPS 接口后,Uika 会在媒体任务结束时立即发送带签名的事件,无需轮询。回调签名遵循 Standard Webhooks 规范,兼容该规范的库均可验证回调。
注册回调接口
每个组织最多 16 个
在本控制台的“事件回调”页添加接口。所有者和管理员可以新增、编辑和删除,所有成员均可查看。新接口默认接收全部事件类型,包括将来新增的类型;如只需部分事件,请设置事件筛选。
- 必须使用 443 端口的 HTTPS,且公网可以访问该地址。
- 不接受 pika.art 域名下的地址。
- 请立即返回 2xx 响应,再异步处理耗时工作。
- 系统不会跟随重定向,请填写最终地址。
| 事件 | 触发时机 |
|---|---|
| media.job.completed | 任务成功完成,结果已可获取。 |
| media.job.failed | 任务失败,错误代码见 data.error。 |
在接口详情页点击“发送测试”,会发送带签名的 webhook.test 事件。无需运行任务,即可检查地址、签名和处理逻辑。无论事件筛选如何设置,接口都会收到测试事件;测试仅发送一次,不会重试。
单次请求回调
一次回调,对应一个任务
提交媒体任务时,可以选填 webhook_url。Uika 会将该任务的最终状态事件发送到此地址,无需预先注册接口。
POST https://api.dev.pika.art/v1/media/pika/pika-2.5/text-to-videoauthorization: Bearer $UIKA_API_KEYcontent-type: application/json{"prompt": "a paper boat crossing a puddle","webhook_url": "https://example.com/pika/webhooks"}
此类回调使用组织级单次请求密钥签名,而非某个接口的密钥。可在控制台的“事件回调”页查看,或通过以下接口获取: GET /v1/webhooks/secret。其他规则一致,包括事件结构、 webhook-* 请求头、Standard Webhooks 验证和重试计划。
- 地址需满足与注册接口相同的要求:HTTPS、443 端口、公网可访问、非 pika.art 域名,且不能包含用户名或密码。
- 回调可能早于提交请求的响应到达。请通过 data.id 匹配任务,并使用 webhook-id 去重。
- 重复使用幂等键时,如果 webhook_url 不同,会返回 409。
- 任务失败时,也会向同一地址发送 media.job.failed,无需额外轮询失败状态。
回调内容示例
POST · application/json
POST https://example.com/pika/webhookscontent-type: application/jsonuser-agent: pika-webhooks/1.0webhook-id: msg_0f1e4b7a-9c33-4d81-b0a2-6e5d8c7f41abwebhook-timestamp: 1786650067webhook-signature: v1,LWSb0oRhsMhAaMLpQGXjNL0aOEmMPuTv0AUKZBFPEUE={"type": "media.job.completed","timestamp": "2026-08-14T09:41:07.482913+00:00","data": {"id": "media_8f3a2c91-5b7d-4e0a-9c26-31d4f2a8e6b0","status": "completed","model_id": "pika/pika-2.5/text-to-video","runtime_s": 42.7,"output": {"media_type": "video","video": {"url": "https://api.dev.pika.art/v1/files/output.mp4","content_type": "video/mp4"}},"error": null,"usage": { "video_output_tokens": 50638 },"billing": null}}
- webhook-id 是事件标识,重试与重放会复用此值,收到重复 ID 时可直接去重。
- webhook-timestamp 是本次发送尝试的 Unix 时间,而非事件发生时间。
- data 是任务完成时的任务对象,结构与 GET /v1/media/jobs/{request_id} 的返回一致。
- runtime_s 和 usage 会尽量提供,但也可能为 null。
- 回调中的 billing 始终为 null。事件进入队列后才结算费用,请查询 GET /v1/media/jobs/{request_id},待 billing.state 为 settled 后读取最终费用。
验证签名
验证通过后,再信任回调内容
向 Standard Webhooks 库传入原始请求字节及三个 webhook-* 请求头。务必使用原始字节,重新序列化后的 JSON 会导致签名不匹配。
from fastapi import FastAPI, HTTPException, Requestfrom standardwebhooks import Webhook, WebhookVerificationErrorapp = FastAPI()webhook = Webhook(UIKA_WEBHOOK_SECRET)@app.post("/pika/webhooks")async def receive(request: Request) -> dict:try:event = webhook.verify(await request.body(), dict(request.headers))except WebhookVerificationError:raise HTTPException(status_code=400, detail="invalid signature")handle(event["type"], event["data"])return {"ok": True}
- 验证库会拒绝时间偏差超过五分钟的请求,以防攻击者重放此前捕获的回调。
- 密钥轮换期间,请求头包含多个签名。匹配任意一个即有效,标准库已支持此行为。
- 验证失败请返回 4xx 并停止处理,不要读取正文。
不使用库时的验证方式
签名原文是以下字节序列: {webhook-id}.{webhook-timestamp}.{raw body}。获取密钥时,先去掉 whsec_ 前缀,再对剩余部分进行 Base64 解码。使用该密钥对签名原文计算 HMAC-SHA256,再对结果进行 Base64 编码。
该 webhook-signature 请求头包含一个或多个 v1,<signature> 值,以空格分隔。请以恒定时间分别比较,任意匹配即可接受回调。这样轮换密钥时不会漏掉事件。同时请检查 webhook-timestamp 是否在允许的时间范围内。通常允许五分钟,以防重放旧请求。
import base64import hashlibimport hmacimport timedef verify(secret, body, msg_id, timestamp, signature_header):if abs(time.time() - int(timestamp)) > 300:raise ValueError("timestamp outside tolerance")key = base64.b64decode(secret.removeprefix("whsec_"))signed = f"{msg_id}.{timestamp}.".encode() + bodyexpected = base64.b64encode(hmac.new(key, signed, hashlib.sha256).digest()).decode()for part in signature_header.split(" "):version, _, signature = part.partition(",")if version == "v1" and hmac.compare_digest(expected, signature):returnraise ValueError("no matching signature")
重试
约 55 小时内最多尝试 9 次
接口在 20 秒内返回任意 2xx 即视为成功;其他情况均视为失败,并按计划重试。
- 重试间隔约为 5 秒、5 分钟、30 分钟、2 小时、5 小时、10 小时、14 小时和 24 小时,并加入随机偏移。
- 以秒为单位的 Retry-After 请求头可推迟下次重试,最长推迟一小时,但不能让重试提前。
- 返回 410 Gone 时,系统会停用该接口,并显示“已停用(Gone)”;你可在接口页重新启用。
- 最后一次尝试仍失败时,回调会标记为失败,你可在接口详情页手动重放。
- 每次尝试均复用同一个 webhook-id,请以此去重。
- webhook.test 事件例外:只尝试一次,不会重试。
每次失败都会记录原因,你可在控制台的接口详情页查看近期回调及所有尝试记录。
| 错误 | 含义 |
|---|---|
| timeout | 20 秒内未收到响应。 |
| connect | 无法建立连接。 |
| blocked | 出口网络拒绝该地址,原因可能是私有地址或未获允许。 |
| tls | TLS 握手或证书校验失败。 |
| http_4xx:<代码> | 你的接口返回了 4xx。 |
| http_5xx:<代码> | 你的接口返回了 5xx。 |
| signing | 回调签名失败,系统会重试,不收取费用。 |
密钥与轮换
平滑轮换,不遗漏事件
每个接口有独立签名密钥,单次请求回调则共用组织级密钥。两种密钥均为 Base64 编码,并带有 whsec_ 前缀。接口密钥可在详情页查看,组织密钥可在“事件回调”页查看。两者轮换方式相同:选择旧密钥立即失效,或继续有效一小时、一天。
在重叠有效期内,每次回调都会用新旧密钥分别签名,因此 webhook-signature 包含两个以空格分隔的值,任意签名均有效,可在不中断事件的情况下切换。
webhook-signature: v1,LWSb0oRhsMhAaMLpQGXjNL0aOEmMPuTv0AUKZBFPEUE= v1,tQ9nS2pKcW4Hs1dRz7yVbXm0gJ3FfN8uLoEiA6TrCxU=