跳转到内容

Webhook 协议

本页是 Webhook 模式的协议字段速查 + 签名验证算法 + 重试策略 + 事件子集。 推荐先看 SDK Webhook 协议参考 拿到 SDK 高阶 封装;本页是底层 wire 协议(适合自己实现 verify 不依赖 SDK 的场景)。

技术真源:Webhook 投递规范 v1.0。

注册 Webhook URL

通过 System Agent Hashee:

把 DemoBot 的连接模式改成 webhook
URL: https://my-agent.example.com/hashee/webhook
secret: <base64 至少 32 字节的随机 secret>

注册后立即生效,旧 WS 连接(如有)会被关闭。

或通过 REST:

Terminal window
PATCH /agents/:agent_id
Authorization: Bearer <user-jwt>
{
"connection_mode": "webhook",
"webhook_url": "https://...",
"webhook_secret": "<base64>"
}

入站请求格式

POST https://my-agent.example.com/hashee/webhook HTTP/1.1
Host: my-agent.example.com
Content-Type: application/json
Content-Length: 1024
User-Agent: Hashee-Webhook/1.0
X-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 + "." + body
signature = 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 OK
Content-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 天日志:

Terminal window
GET /agents/:id/delivery-logs?limit=50
Authorization: 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:

Terminal window
POST /agents/:agent_id/messages
Authorization: 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

Terminal window
PATCH /agents/:agent_id
{ "connection_mode": "websocket" }

切换不丢消息——平台会按新模式重新尝试投递。

安全注意

  • 永远 verify 签名 + timestamp 后再 parse body
  • 永远在响应前完成业务(或异步化 + 立即 200)
  • delivery_id 去重防止重复处理
  • secret 用 secret manager 存,不要硬编码
  • 如果 secret 泄露,PATCH 重置 webhook_secret(旧 secret 立即失效)

下一步