Hello World — Webhook 模式
Webhook 模式适合 无状态 + 函数式 的部署目标:Cloudflare Workers、Vercel Edge Functions、AWS Lambda、Google Cloud Functions。SDK 跳过 Layer 4 Double Ratchet (无状态环境无法维护 ratchet state),保留 Layer 1-3 的内容加密 + 签名 + per-recipient wrap,安全性仍然远高于”明文 + TLS”。
依据:Webhook 投递规范 v1.0。
整体架构
Hashee Mobile / Desktop │ ▼Hashee API (盲管道,加密路由) │ │ HTTP POST {webhook_url} │ body: 加密 wire envelope (≤ 1 MB) │ headers: │ X-Hashee-Signature: <hex HMAC-SHA256> │ X-Hashee-Timestamp: <unix epoch s> │ X-Hashee-Delivery-Id: <uuid> ▼你的 Webhook 端点(Cloudflare Workers / Lambda / ...) │ ▼ SDK Webhook Dispatcher 处理: ├─ 验证 HMAC(防冒充) ├─ 验证 timestamp ±5min(防重放) ├─ 检查 delivery_id 去重 ├─ 解析 + 解密 wire envelope(Layer 1-3) └─ 调用你的 onMessage / onEvent handler │ ▼ 同步返回 200 OK(≤ 10 秒) │ │ 主动推送(如果业务需要): ▼ restRequest("POST", "/agents/:id/messages", ...)Hashee APICloudflare Workers 实现
最常见的部署目标。完整可工作的 worker.ts:
import { createWebhookDispatcher } from "@hasheeai/agent-sdk-ts";
interface Env { HASHEE_AGENT_ID: string; HASHEE_AGENT_TOKEN: string; HASHEE_WEBHOOK_SECRET: string; HASHEE_X25519_PRIVATE_BASE64: string; HASHEE_ED25519_PRIVATE_BASE64: string;}
export default { async fetch(request: Request, env: Env, ctx: ExecutionContext): Promise<Response> { if (request.method !== "POST" || new URL(request.url).pathname !== "/hashee/webhook") { return new Response("not found", { status: 404 }); }
const dispatcher = createWebhookDispatcher({ agentId: env.HASHEE_AGENT_ID, token: env.HASHEE_AGENT_TOKEN, secret: env.HASHEE_WEBHOOK_SECRET, privateKeyBase64: env.HASHEE_X25519_PRIVATE_BASE64, signingPrivateKeyBase64: env.HASHEE_ED25519_PRIVATE_BASE64,
onMessage: async (msg) => { if (msg.payload?.type !== "text") return; // 业务侧:调 LLM / DB 等 const reply = await generateReply(msg.payload.text); // 异步回写(可在 ctx.waitUntil 里) ctx.waitUntil( dispatcher.sendReply(msg.conversation_id, { type: "text", text: reply, }), ); },
onEvent: async (event) => { if (event.type === "relation.revoked") { // 清理本 Worker 缓存里的用户数据 await cleanupUser(event.payload.user_id); } }, });
try { const headers = Object.fromEntries(request.headers); const body = await request.text(); await dispatcher.handle(headers, body); return new Response("ok", { status: 200 }); } catch (err) { // 4xx 永久失败(签名错、timestamp skew)-- 不重试 // 5xx 临时失败 -- 平台会按 7 次指数退避重试 console.error("[hashee] dispatcher error:", err); const code = (err as any)?.statusCode ?? 500; return new Response("error", { status: code }); } },};
async function generateReply(text: string): Promise<string> { // 替换为你的 LLM 调用 return `Echo: ${text}`;}
async function cleanupUser(userId: string): Promise<void> { // 清理 KV / R2 / D1 等}wrangler.toml
name = "my-hashee-agent"main = "worker.ts"compatibility_date = "2024-09-23"
[observability]enabled = true设置 secret:
wrangler secret put HASHEE_AGENT_IDwrangler secret put HASHEE_AGENT_TOKENwrangler secret put HASHEE_WEBHOOK_SECRETwrangler secret put HASHEE_X25519_PRIVATE_BASE64wrangler secret put HASHEE_ED25519_PRIVATE_BASE64
wrangler deploy部署完成后会得到 https://my-hashee-agent.<subdomain>.workers.dev。
把 Webhook URL 注册到 Agent
在 Hashee app 跟系统 Agent Hashee 说:
把 DemoBot 的连接模式改为 webhook,url 是https://my-hashee-agent.example.workers.dev/hashee/webhook,secret 是 <你前面 wrangler secret 里设的 HASHEE_WEBHOOK_SECRET>System Agent 会用 Artifact 表单二次确认 URL 与 secret,确认后立即生效。 旧的 WebSocket 连接(如有)会被关闭。
验证
在 Hashee app 跟 DemoBot 发”hi”,几秒内应该收到回复。
调试日志:
wrangler tail my-hashee-agentHMAC 签名细节
每个 incoming POST 都带:
X-Hashee-Signature: <hex HMAC-SHA256>X-Hashee-Timestamp: <unix epoch seconds>X-Hashee-Delivery-Id: <uuid v7>签名串构造:
signed_string = timestamp + "." + delivery_id + "." + bodysignature = hex(HMAC_SHA256(webhook_secret, signed_string))SDK Dispatcher 已经替你做:
- 取 header
X-Hashee-Timestamp→ 检查|now - ts| ≤ 300s,否则拒。 - 取 header
X-Hashee-Signature→ 用webhook_secret重算 → 常时间比较。 - 取 header
X-Hashee-Delivery-Id→ 在过去 24h 内见过 → 直接 200 OK 跳过(幂等)。 - 解析 body → 解密 → 调你的 handler。
如需自己验签(不用 SDK),见 verifyWebhookSignature(secret, headers, body) 导出。
R2 引用 — 大 payload 路径
单个 webhook body 硬上限 1 MB。如果消息附带超过 1 MB 的内容(大文件 / 长 markdown / 大 artifact),后端会改用 R2 引用:
原始 payload 尺寸 = 5 MB ↓后端: - 把 5MB 的密文上传到 R2 (object_key = ref://abc123) - 给你 POST 一个 stub envelope: { "type": "ref", "object_key": "ref://abc123", "size": 5242880, "content_type": "..." } ↓SDK Dispatcher 自动: - 检测到 type=ref → 调 GET /r2/<object_key> 拿密文 - 解密 → 还原成完整 InboundMessage ↓你的 onMessage 拿到的依然是完整明文(透明)R2 引用的 GET 速率:每 Agent 5 req/s,足够正常使用。 对你完全透明——SDK 已封装。
Vercel Edge / Next.js Route Handler
import { createWebhookDispatcher } from "@hasheeai/agent-sdk-ts";
export const runtime = "edge";
const dispatcher = createWebhookDispatcher({ agentId: process.env.HASHEE_AGENT_ID!, token: process.env.HASHEE_AGENT_TOKEN!, secret: process.env.HASHEE_WEBHOOK_SECRET!, privateKeyBase64: process.env.HASHEE_X25519_PRIVATE_BASE64!, signingPrivateKeyBase64: process.env.HASHEE_ED25519_PRIVATE_BASE64!, onMessage: async (msg) => { // ... },});
export async function POST(req: Request): Promise<Response> { await dispatcher.handle(Object.fromEntries(req.headers), await req.text()); return new Response("ok");}AWS Lambda(Function URL)
import { createWebhookDispatcher } from "@hasheeai/agent-sdk-ts";
const dispatcher = createWebhookDispatcher({ /* ... 同上 */ });
export const handler = async (event: any) => { await dispatcher.handle(event.headers, event.body); return { statusCode: 200, body: "ok" };};详见 部署到 AWS Lambda。
限制与注意事项
| 项 | 上限 |
|---|---|
| 单 webhook payload | 1 MB(超过自动走 R2) |
| HTTP 响应 deadline | 10 s(超时算失败) |
| 失败重试 | 7 次指数退避,到达上限 → Agent 进入 unreachable |
| Delivery log 保留 | 30 天 |
不要在 onMessage 里同步等长任务(LLM 调用 ≤ 10s OK,更长应该 enqueue 后异步)。
推荐模式:ctx.waitUntil(dispatcher.sendReply(...)) 或者
“立即 200 OK + 把任务塞进队列 + 队列消费完成后调 REST 主动回写”。
演示视频
视频文字版逐节描述
- 00:00 – 00:10 — 终端
wrangler deploy,输出 deployment URLhttps://my-hashee-agent.example.workers.dev。 - 00:10 – 00:25 — 在 Hashee app 跟系统 Agent 说”把 DemoBot 改成 webhook 模式 url 是 …secret 是 …”,提交确认后系统 Agent 回复”已切换到 webhook 模式”。
- 00:25 – 00:40 — 在 Hashee app 跟 DemoBot 发”hello”,几秒后收到回复
Echo: hello。 - 00:40 – 00:55 — 切到
wrangler tail,看到一行POST /hashee/webhook 200 in 320ms,紧接[hashee] dispatcher handled[hashee] reply sent。
- 00:55 – 01:05 — 模拟超大消息:用户发一段 2MB 的 Markdown,后端走 R2 引用,
Worker 端日志显示
[hashee] resolving R2 ref://abc...+200 in 580ms。
下一步
- 发送回复 — typing / Markdown / reply-to / mention
- Webhook 协议参考 — 字段全表 + 示例 payload
- 部署到 Cloudflare Workers