Skip to content

钉钉桥接服务(Cloudflare Worker 版)

目标:把钉钉消息接入 OpenClaw。实现路径:钉钉事件回调 → Cloudflare Worker(验签/防重放/格式化)→ OpenClaw Gateway /hooks/agent

本方案适合:

  • 你希望 无服务器运维
  • 你的 OpenClaw 网关 HTTP hooks 能被 Worker 访问到(公网域名或内网穿透)

0) 前置条件

  1. OpenClaw 开启 hooks(示例):
json5
{
  hooks: {
    enabled: true,
    token: "<OPENCLAW_HOOKS_TOKEN>",
    path: "/hooks"
  }
}
  1. OpenClaw hooks 有一个可访问 URL(例):
  • https://claw-api.example.com/hooks/agent

注意:如果你的 OpenClaw 只监听 127.0.0.1,你需要用反代/Access/Tunnel 让 Worker 能访问。安全上建议加 Cloudflare Access 或至少 IP/Token 限制。

  1. 钉钉开放平台创建机器人/应用,配置事件订阅回调 URL 指向 Worker。

1) 环境变量(Worker Secrets)

在 Worker 中配置(用 wrangler secret put):

  • OPENCLAW_HOOKS_URL:例如 https://claw-api.example.com/hooks/agent
  • OPENCLAW_HOOKS_TOKEN:OpenClaw hooks token
  • DINGTALK_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)

  1. 初始化(若你新建项目):
bash
npm create cloudflare@latest dingtalk-openclaw-bridge
cd dingtalk-openclaw-bridge
  1. 写入 Worker 代码(替换 src/index.ts)。

  2. 设置 secrets:

bash
npx wrangler secret put OPENCLAW_HOOKS_URL
npx wrangler secret put OPENCLAW_HOOKS_TOKEN
npx wrangler secret put DINGTALK_SIGNING_SECRET
  1. 部署:
bash
npx wrangler deploy
  1. 在钉钉开放平台把事件回调 URL 指向:
  • https://<your-worker>.<account>.workers.dev/

4) 安全建议(很重要)

  • 必须验签 + 防重放(否则任何人都能伪造钉钉消息触发你的 OpenClaw)
  • OpenClaw hooks URL 尽量加一层 Cloudflare Access/反代,并限制来源
  • hooks.token 不要复用任何其他 token
  • Worker 侧建议对高频请求做限流

5) 双向回传(把 OpenClaw 回复发回钉钉)

要实现“钉钉内完整对话”,需要 Worker 具备:

  • 能拿到 OpenClaw 生成的最终文本

/hooks/agent 是异步接收,最简单的办法是:

  1. Worker 把钉钉消息转发到 OpenClaw
  2. OpenClaw 把回复投递到某个渠道(例如 webchat/telegram)

如果要回到钉钉:

  • 需要你在 Worker 实现 出站发送到钉钉 的 API 调用,并且 OpenClaw 的输出要能回调到 Worker。
  • 推荐升级为“自定义渠道插件”或“Worker 做完整双向适配层”。