钉钉桥接服务(Cloudflare Worker 版)
目标:把钉钉消息接入 OpenClaw。实现路径:钉钉事件回调 → Cloudflare Worker(验签/防重放/格式化)→ OpenClaw Gateway
/hooks/agent。
本方案适合:
- 你希望 无服务器运维
- 你的 OpenClaw 网关 HTTP hooks 能被 Worker 访问到(公网域名或内网穿透)
0) 前置条件
- OpenClaw 开启 hooks(示例):
json5
{
hooks: {
enabled: true,
token: "<OPENCLAW_HOOKS_TOKEN>",
path: "/hooks"
}
}- OpenClaw hooks 有一个可访问 URL(例):
https://claw-api.example.com/hooks/agent
注意:如果你的 OpenClaw 只监听 127.0.0.1,你需要用反代/Access/Tunnel 让 Worker 能访问。安全上建议加 Cloudflare Access 或至少 IP/Token 限制。
- 钉钉开放平台创建机器人/应用,配置事件订阅回调 URL 指向 Worker。
1) 环境变量(Worker Secrets)
在 Worker 中配置(用 wrangler secret put):
OPENCLAW_HOOKS_URL:例如https://claw-api.example.com/hooks/agentOPENCLAW_HOOKS_TOKEN:OpenClaw hooks tokenDINGTALK_SIGNING_SECRET:钉钉回调签名密钥(如果你选择“签名校验”模式)
钉钉签名细节取决于你使用的“机器人类型/事件回调协议”。本文提供的是桥接结构与安全要点;签名算法请按钉钉官方文档实现并替换
verifyDingTalkSignature()。
2) Worker 代码(示例)
src/index.ts(最小可用骨架):
ts
export interface Env {
OPENCLAW_HOOKS_URL: string;
OPENCLAW_HOOKS_TOKEN: string;
DINGTALK_SIGNING_SECRET: string;
}
function json(data: unknown, init?: ResponseInit) {
return new Response(JSON.stringify(data), {
headers: { "content-type": "application/json; charset=utf-8" },
...init,
});
}
async function verifyDingTalkSignature(req: Request, env: Env, rawBody: string) {
// TODO: 按钉钉官方文档实现签名验证
// - 校验时间戳/nonce
// - HMAC 或 RSA(取决于你选的回调类型)
// - 做重放保护(KV/D1 记录 nonce + TTL)
return true;
}
function normalizeToText(payload: any) {
// TODO: 把钉钉事件结构整理成纯文本
// 建议保留:来源(钉钉)、群/私聊、发送人、文本
const text = payload?.text?.content ?? payload?.content ?? JSON.stringify(payload);
return `【钉钉】${text}`;
}
export default {
async fetch(req: Request, env: Env): Promise<Response> {
if (req.method !== "POST") return json({ ok: false, error: "Method Not Allowed" }, { status: 405 });
const rawBody = await req.text();
const ok = await verifyDingTalkSignature(req, env, rawBody);
if (!ok) return json({ ok: false, error: "Bad signature" }, { status: 401 });
const payload = rawBody ? JSON.parse(rawBody) : {};
// 钉钉握手/校验回调(如果有 challenge 字段)
// TODO: 按钉钉协议返回 challenge
const message = normalizeToText(payload);
// 转发到 OpenClaw Hooks: /hooks/agent
const r = await fetch(env.OPENCLAW_HOOKS_URL, {
method: "POST",
headers: {
"content-type": "application/json",
"authorization": `Bearer ${env.OPENCLAW_HOOKS_TOKEN}`,
},
body: JSON.stringify({
name: "DingTalk",
message,
wakeMode: "now",
deliver: true,
channel: "last",
}),
});
// 这里返回 200 给钉钉即可(OpenClaw 侧是异步接收)
return json({ ok: true, forwarded: r.ok });
},
};3) 部署步骤(wrangler)
- 初始化(若你新建项目):
bash
npm create cloudflare@latest dingtalk-openclaw-bridge
cd dingtalk-openclaw-bridge写入 Worker 代码(替换
src/index.ts)。设置 secrets:
bash
npx wrangler secret put OPENCLAW_HOOKS_URL
npx wrangler secret put OPENCLAW_HOOKS_TOKEN
npx wrangler secret put DINGTALK_SIGNING_SECRET- 部署:
bash
npx wrangler deploy- 在钉钉开放平台把事件回调 URL 指向:
https://<your-worker>.<account>.workers.dev/
4) 安全建议(很重要)
- 必须验签 + 防重放(否则任何人都能伪造钉钉消息触发你的 OpenClaw)
- OpenClaw hooks URL 尽量加一层 Cloudflare Access/反代,并限制来源
hooks.token不要复用任何其他 token- Worker 侧建议对高频请求做限流
5) 双向回传(把 OpenClaw 回复发回钉钉)
要实现“钉钉内完整对话”,需要 Worker 具备:
- 能拿到 OpenClaw 生成的最终文本
纯 /hooks/agent 是异步接收,最简单的办法是:
- Worker 把钉钉消息转发到 OpenClaw
- OpenClaw 把回复投递到某个渠道(例如 webchat/telegram)
如果要回到钉钉:
- 需要你在 Worker 实现 出站发送到钉钉 的 API 调用,并且 OpenClaw 的输出要能回调到 Worker。
- 推荐升级为“自定义渠道插件”或“Worker 做完整双向适配层”。