钉钉桥接服务(Node 自建服务器版)
目标:钉钉事件回调 → Node/Express 服务(验签/防重放/格式化)→ OpenClaw Gateway
/hooks/agent。
适合:
- 你需要更复杂的逻辑(媒体、队列、长任务、持久存储)
- 你希望把“把 OpenClaw 回复发回钉钉”也做成完整闭环
0) 前置:OpenClaw 开启 hooks
json5
{
hooks: {
enabled: true,
token: "<OPENCLAW_HOOKS_TOKEN>",
path: "/hooks"
}
}假设可访问地址:
http://127.0.0.1:18789/hooks/agent(同机)- 或
https://claw-api.example.com/hooks/agent(反代后)
1) Node 服务结构(建议)
server.js:HTTP server + 路由verify.js:钉钉验签 + 防重放normalize.js:把钉钉 payload 归一化为文本
环境变量:
OPENCLAW_HOOKS_URLOPENCLAW_HOOKS_TOKENDINGTALK_SIGNING_SECRET
2) Express 示例代码(骨架)
js
import express from 'express';
const app = express();
app.use(express.text({ type: '*/*' }));
function verifyDingTalk(req, rawBody) {
// TODO: 按钉钉官方文档实现验签
// 建议:校验 timestamp + nonce + signature,并做 nonce TTL 防重放(Redis/内存LRU/SQLite)
return true;
}
function normalizeToText(payload) {
const text = payload?.text?.content ?? payload?.content ?? JSON.stringify(payload);
return `【钉钉】${text}`;
}
app.post('/dingtalk/events', async (req, res) => {
const rawBody = req.body || '';
if (!verifyDingTalk(req, rawBody)) {
return res.status(401).json({ ok: false, error: 'Bad signature' });
}
const payload = rawBody ? JSON.parse(rawBody) : {};
// TODO: 钉钉握手/校验 challenge(若协议要求)
const message = normalizeToText(payload);
const r = await fetch(process.env.OPENCLAW_HOOKS_URL, {
method: 'POST',
headers: {
'content-type': 'application/json',
'authorization': `Bearer ${process.env.OPENCLAW_HOOKS_TOKEN}`,
},
body: JSON.stringify({
name: 'DingTalk',
message,
wakeMode: 'now',
deliver: true,
channel: 'last',
}),
});
return res.json({ ok: true, forwarded: r.ok });
});
app.listen(8787, () => {
console.log('listening on http://127.0.0.1:8787');
});启动:
bash
OPENCLAW_HOOKS_URL="http://127.0.0.1:18789/hooks/agent" \
OPENCLAW_HOOKS_TOKEN="xxx" \
DINGTALK_SIGNING_SECRET="yyy" \
node server.js3) 反代与 HTTPS(必做)
钉钉回调通常要求 HTTPS 公网可达:
- 你可以用 Nginx/Caddy 做反代 + TLS
- 或 Cloudflare Tunnel 把
/dingtalk/events暴露出来
4) 双向回传(把 OpenClaw 回复发回钉钉)
要在钉钉里形成“完整对话”,Node 服务需要:
- 从 OpenClaw 获取最终输出(可通过:让 OpenClaw webhook 回调你的 Node,或你自己实现 channel plugin)
- 调用钉钉发送消息 API(机器人/应用接口)
最干净的长期方案仍是:写一个钉钉 channel plugin,把入站/出站都封装在 OpenClaw 内。
5) 安全清单
- 验签 + 防重放
- 限流(按 tenant/chat)
- 记录 requestId 与 traceId 便于排错
- 不把原始 payload 全量写入日志(避免敏感信息泄露)