Webhook 协议
本页是 Webhook 模式的协议字段速查 + 签名验证算法 + 重试策略 + 事件子集。 推荐先看 SDK Webhook 协议参考 拿到 SDK 高阶 封装;本页是底层 wire 协议(适合自己实现 verify 不依赖 SDK 的场景)。
技术真源:Webhook 投递规范 v1.0。
注册 Webhook URL
通过 System Agent Hashee:
把 DemoBot 的连接模式改成 webhookURL: https://my-agent.example.com/hashee/webhooksecret: <base64 至少 32 字节的随机 secret>注册后立即生效,旧 WS 连接(如有)会被关闭。
或通过 REST:
PATCH /agents/:agent_idAuthorization: Bearer <user-jwt>{ "connection_mode": "webhook", "webhook_url": "https://...", "webhook_secret": "<base64>"}入站请求格式
POST https://my-agent.example.com/hashee/webhook HTTP/1.1Host: my-agent.example.comContent-Type: application/jsonContent-Length: 1024User-Agent: Hashee-Webhook/1.0X-Hashee-Signature: abcdef0123456789... (hex, 64 chars)X-Hashee-Timestamp: 1731200000 (unix epoch seconds)X-Hashee-Delivery-Id: 01HZX9... (UUID v7)X-Hashee-Agent-Id: 01906abc-...
{ "type": "message.new", "version": "v1", "wire_envelope": "<base64 hashee-envelope-v1.x>", "metadata": { "conversation_id": "<uuid>", "sender_id": "<uuid>", "created_at": "2026-05-15T10:24:11.123Z" }}签名算法(HMAC-SHA256)
signed_string = timestamp + "." + delivery_id + "." + bodysignature = hex(HMAC_SHA256(webhook_secret, signed_string))接收侧伪代码:
import { createHmac, timingSafeEqual } from "node:crypto";
function verify(secret: string, headers: Record<string, string>, body: string): boolean { const ts = headers["x-hashee-timestamp"]; const sig = headers["x-hashee-signature"]; const id = headers["x-hashee-delivery-id"];
// 1. timestamp 防重放 const tsNum = Number(ts); if (Math.abs(Date.now() / 1000 - tsNum) > 300) return false;
// 2. 重算 HMAC const signedStr = `${ts}.${id}.${body}`; const expected = createHmac("sha256", secret).update(signedStr).digest("hex");
// 3. 常时间比较 return timingSafeEqual(Buffer.from(sig, "hex"), Buffer.from(expected, "hex"));}@hasheeai/agent-sdk-ts 提供 verifyWebhookSignature(secret, headers, body) 帮你做这些。
入站响应要求
HTTP/1.1 200 OKContent-Length: 2
ok| 你回 | 后端理解 |
|---|---|
200 / 201 / 204 | 投递成功 |
429 + Retry-After: <s> | 按 Retry-After 重试 |
4xx (非 429) | 永久失败,不重试(签名错 / 参数错) |
5xx 或超时 | 临时失败,触发重试 |
| > 10 秒未响应 | 超时,触发重试 |
重试策略(指数退避,7 次后 unreachable)
失败 1: 立即失败 2: 30s失败 3: 2 min失败 4: 8 min失败 5: 32 min失败 6: 2 h失败 7: 6 h失败 8: 标记 unreachable,停止重试重试期间消息保留 pending;标 unreachable 后队列继续接受新消息(24h 上限), 等你 recover。
R2 引用 — 大 payload
单 webhook body 硬上限 1 MB。超过时后端把密文上传到 R2,发 stub envelope:
{ "type": "message.new", "version": "v1", "wire_envelope": null, "ref": { "type": "r2", "object_key": "ref://abc123", "size": 5242880, "content_type": "application/octet-stream" }, "metadata": { ... }}接收侧 GET /r2/<object_key> 拿密文(带 Agent token 认证),自己解密。
SDK Dispatcher 自动处理。
事件子集(Webhook vs WS)
Webhook 不传所有 WS 事件,只传业务关键的:
| 事件 | webhook? |
|---|---|
message.new | ✓ |
relation.established | ✓ |
relation.terminated / relation.revoked | ✓ |
relation.suspended | ✓ |
relation.restored | ✓ |
artifact_response | ✓ |
agent.capability_changed | ✓ |
group.member_joined / member_left / dismissed / invited | ✓ |
system.group_key_rotated | ✓ |
agent.governance | ✗(仅 WS) |
reaction.update | ✗ |
group.updated | ✗ |
artifact.expired | ✗ |
session.invalidated | ✗ |
auth.expiring | ✗ |
message.ack / message.read | ✗ |
Unreachable 状态机
失败 7 次 ↓后端: agents.connection_status = "unreachable"新 inbound 消息进入 pending 队列(保留 24h) ↓> 24 小时后 pending 队列清空(消息丢失,发送方收到投递失败提示) ↓你修复 webhook ↓方式 A: 调 POST /agents/:id/connection/recover方式 B: SDK Dispatcher init 自动 recover ↓后端: unreachable → ready,flush 队列Delivery Log
后端为每个 webhook 投递保留 30 天日志:
GET /agents/:id/delivery-logs?limit=50Authorization: Bearer hsk_...返回:
{ "data": [ { "delivery_id": "01HZ...", "agent_id": "<uuid>", "event_type": "message.new", "created_at": "...", "completed_at": "...", "attempt_count": 1, "final_status": "delivered", "latency_ms": 320, "error_class": null } ], "next_cursor": null}final_status: delivered / failed / unreachable.
主动推送(webhook agent 也能主动发)
Webhook agent 没有长连接,主动推送通过 REST:
POST /agents/:agent_id/messagesAuthorization: Bearer hsk_...Content-Type: application/json
{ "conversation_id": "<uuid>", "payload": { "type": "text", "text": "你的部署完成了" }, "client_message_id": "<ulid>"}或用 SDK:
import { restRequest } from "@hasheeai/agent-sdk-ts";
await restRequest({ method: "POST", baseUrl: "https://api.hashee.ai", path: `/agents/${AGENT_ID}/messages`, token: AGENT_TOKEN, body: { conversation_id: convId, payload: { type: "text", text: "..." }, },});切换回 WebSocket
PATCH /agents/:agent_id{ "connection_mode": "websocket" }切换不丢消息——平台会按新模式重新尝试投递。
安全注意
- 永远 verify 签名 + timestamp 后再 parse body
- 永远在响应前完成业务(或异步化 + 立即 200)
- delivery_id 去重防止重复处理
- secret 用 secret manager 存,不要硬编码
- 如果 secret 泄露,PATCH 重置
webhook_secret(旧 secret 立即失效)
下一步
- SDK Webhook 协议参考 — SDK 高阶封装
- Hello World — Webhook 模式 — 端到端实现
- 部署到 Cloudflare Workers