跳转到内容

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 API

Cloudflare 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:

Terminal window
wrangler secret put HASHEE_AGENT_ID
wrangler secret put HASHEE_AGENT_TOKEN
wrangler secret put HASHEE_WEBHOOK_SECRET
wrangler secret put HASHEE_X25519_PRIVATE_BASE64
wrangler 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”,几秒内应该收到回复。

调试日志:

Terminal window
wrangler tail my-hashee-agent

HMAC 签名细节

每个 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 + "." + body
signature = hex(HMAC_SHA256(webhook_secret, signed_string))

SDK Dispatcher 已经替你做:

  1. 取 header X-Hashee-Timestamp → 检查 |now - ts| ≤ 300s,否则拒。
  2. 取 header X-Hashee-Signature → 用 webhook_secret 重算 → 常时间比较。
  3. 取 header X-Hashee-Delivery-Id → 在过去 24h 内见过 → 直接 200 OK 跳过(幂等)。
  4. 解析 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

app/api/hashee/webhook/route.ts
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)

handler.ts
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 payload1 MB(超过自动走 R2)
HTTP 响应 deadline10 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 URL https://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

下一步