OpenClaw 接入飞书 / 钉钉 / 企业微信:从 HTTP Webhook 到 WebSocket 长连接

关键词:OpenClaw、Feishu/Lark、DingTalk Stream、企业微信、HTTP Webhook、WebSocket、长连接、心跳、断线重连

适用人群:在做企业 IM 机器人接入、对比"回调 vs 长连接"该选哪个、想理解 OpenClaw channels.feishu 为什么不需要公网域名的开发者。


0. 一图看懂演进

企业 IM 的机器人接入方式,最近两三年正在经历一次明显的范式切换:从「平台主动推 → 开发者公网服务器接」的 HTTP Webhook,演进到「客户端主动连 → 平台反向推」的 WebSocket / Stream 长连接 。具体节奏上,国内三家平台并不同步------钉钉 Stream 模式 2023 年 GA,飞书事件订阅 WebSocket SDK 在 2023~2024 年 陆续放出,企业微信截至 2026 年仍只提供 HTTP 回调;海外则早走了好几年,Discord 自 2015 年 起原生就是 WebSocket Gateway,Slack 在 2021 年 推出 Socket Mode,Telegram 至今还是 long-poll 为主。所以这并不是什么"十年长跑",而是 2023 年起国内 IM 集体补课、到 2025 年趋于稳定的一次集中迁移,背后是「NAT 普及 + 内网部署需求 + 实时性要求 + 安全合规」几股力量同时推着走。

OpenClaw 作为多渠道 AI Agent 网关,在飞书插件 @openclaw/feishu 里直接放弃了传统 Webhook 模式,强制使用飞书官方的 WebSocket 事件订阅------这不是偷懒,而是这种"反向连接"模式恰好把 OpenClaw 大量部署在用户笔记本、家用服务器、公司内网这种"没有公网 IP"环境的痛点彻底解掉了。

下图把两种模式的核心差异并排放在一张图里:左边是传统 Webhook 必须翻越的"公网 IP / HTTPS 证书 / IP 白名单"三座山,右边是长连接"客户端主动建连 + 心跳保活 + 自动重连"的简洁结构。

关键要点(结合上图逐项对照):

  • 方向相反 :Webhook 是 Platform → Server (平台主动推到你公网 URL),长连接是 Client → Platform(你主动连到平台 WSS 端点),方向反转是一切差异的起点
  • 可达性需求:Webhook 模式下你必须公网可达------独立 IP 或域名 + HTTPS 证书 + 备案(国内);长连接模式下你完全可以在 NAT 后面、家庭网络、Docker 容器里跑
  • 失败回退 :Webhook 推送失败时平台会重试,但重试有上限 (飞书 3 次、钉钉 5 次),超时整条消息丢失;长连接断开后重连就行,不丢事件(平台侧有 broker 缓存)
  • OpenClaw 选型 :飞书插件直接锁死 WebSocket,文档明确写 "connects OpenClaw to a Feishu/Lark bot using the platform's WebSocket event subscription so messages can be received without exposing a public webhook URL"

设计取舍:长连接不是没有代价------它要求客户端持续维护 socket、跑心跳线程、处理断线状态机;但比起"必须有公网域名"这种部署门槛,前者是工程问题,后者是用户门槛问题。OpenClaw 选择把工程复杂度吃在自己代码里,把"零部署门槛"留给用户。


1. 早期:HTTP Webhook 模式(飞书旧版 / 钉钉旧版 / 企业微信至今)

1.1 它是怎么工作的

HTTP Webhook 是几乎所有 IM 开放平台第一代的设计------简单、直接、符合 HTTP 直觉:开发者在平台后台填一个 Event Callback URL,平台把所有用户消息事件 HTTP POST 到这个 URL,开发者收到后处理并返回 200。钉钉机器人、企业微信在 2017 年、飞书机器人在 2018 年推出时都是这套范式,因为当时大家做的都是"部署在云 ECS 上的 PHP/Java 后台",公网 IP 唤手即来。

但实际接过的人都知道,Webhook 远不止"暴露一个 URL"这么简单。下图把一次正常 Webhook 投递的全链路拆开:

关键要点(结合上图逐步拆解):

  • Step 0:URL 校验(图中未画) :平台后台第一次保存 callback URL 时,会发一个 challenge 请求,开发者必须原样返回 challenge 字段------证明你确实控制这个 URL,防止被人乱填
  • Step 1:用户在 IM 客户端发消息 → 平台 IM 服务接收
  • Step 2 :平台事件中心把消息打包成 JSON,带上 HMAC 签名头 (飞书 X-Lark-Signature / 钉钉 timestamp+sign / 企微 msg_signature)POST 到开发者 URL;payload 通常还会做一次 AES 加密 (企微 EncodingAESKey / 飞书 encrypt_key
  • Step 3:开发者服务器三件事必须做:① 校验 IP 是否在平台白名单 ② 用 secret 重算签名比对 ③ 用 AES key 解密 body 拿到明文事件
  • Step 43 秒内必须返回 HTTP 200------否则平台认为你超时,会触发重试(最多 N 次后丢消息)
  • Step 5 :开发者用平台 Send API(HTTP)反向调用,把 LLM 生成的回复发回去------这一步和 Webhook 无关,永远是 HTTP

典型 URL 校验代码(飞书 v2 协议示例,所有平台大同小异):

python 复制代码
def feishu_callback(request):
    body = request.json()
    # URL 校验请求
    if body.get("type") == "url_verification":
        return {"challenge": body["challenge"]}

    # 签名校验
    timestamp = request.headers["X-Lark-Request-Timestamp"]
    nonce = request.headers["X-Lark-Request-Nonce"]
    signature = request.headers["X-Lark-Signature"]
    raw = (timestamp + nonce + ENCRYPT_KEY + request.body).encode()
    expect = base64.b64encode(hashlib.sha256(raw).digest()).decode()
    if signature != expect:
        return Response(401, "bad signature")

    # AES 解密 + 业务处理
    event = aes_decrypt(body["encrypt"], ENCRYPT_KEY)
    handle_message(event)
    return {"code": 0}

设计取舍:Webhook 把"网络可达性"完全外包给开发者。平台只负责推,能不能收到是你自己的事------这对于"有专门后端团队 + 独立服务器"的场景没问题;但对"想把 AI 助手装在自己电脑上"的现代用法是灾难。

1.2 Webhook 的 6 大痛点

如果你只是接一个公司内部的考勤机器人,Webhook 完全够用。但当你想做一个像 OpenClaw 这样"装在用户笔记本上"的 Agent,下面这 6 个痛点会一个不漏地全部撞上:

关键要点(结合上图,每个痛点的真实场景):

  • ① 公网域名 / IP :你必须有一个 bot.example.com 这种可解析域名。家庭宽带的动态 IP?路由器后面的内网?Docker 容器?全都跑不了 Webhook
  • ② HTTPS 证书 :所有主流 IM 平台强制要求 HTTPS(飞书甚至强制 TLS 1.2+),自签证书不收,必须 Let's Encrypt 或 CA 签发证书 + 续期
  • ③ IP 白名单 :钉钉、企业微信会要求开发者把"自己服务器的出口 IP"加到平台白名单------但平台推过来的 IP 池 也时常变动,你还得反查 event:ip_list 这种文档
  • ④ NAT 不友好:99% 的家庭/企业内网都在 NAT 后面,没有公网入口;只能上"frp / ngrok / cloudflare tunnel"这种隧道方案,又引入新的故障点
  • ⑤ 重放攻击风险 :Webhook URL 一旦泄露,攻击者可以截获 + 重放 payload;你必须自己实现 timestamp + nonce 防重放(5 分钟窗口 + nonce 集合去重),漏一个就出事
  • ⑥ 服务宕机 = 消息丢失 :你机器关机 5 分钟、SSL 证书过期没续、Nginx 配置写错------这 5 分钟的所有用户消息永久丢失。平台侧不缓存,也不会"等你恢复了重发"
bash 复制代码
# 真实世界里 Webhook 部署常见的 4 件套
1. 域名 (~50 元/年) + 备案 (国内) + 解析
2. 服务器 (~100 元/月) 或 frp 内网穿透
3. Let's Encrypt 证书自动续期 (certbot)
4. 防重放中间件 (Redis 存 nonce, TTL 5min)

设计取舍 :上面这 4 件套对一个给最终用户用的开源工具 而言完全是反人类的------OpenClaw 的目标用户是"想在自己笔记本上跑 Claude/GPT"的开发者,不是"运维"。这就是为什么飞书插件没有提供 Webhook 选项,只走长连接。


2. 现在:WebSocket / Stream 长连接(飞书 SDK / 钉钉 Stream)

2.1 长连接是怎么工作的

长连接模式把"消息推送"这件事的发起方掉了个个:不再是平台 → 服务器,而是客户端 → 平台 。客户端用 App ID + App Secret 鉴权后,主动发起一个 WSS(WebSocket over TLS) 连接到平台的事件订阅端点(飞书 wss://...feishu.cn/callback/ws/...,钉钉 wss://wss-open-connection.dingtalk.com/connect),握手成功后这条 socket 就一直保持,平台有事件就从这条已经建好的隧道里推下来。

下图展示了这个"反向建连 + 持久隧道 + 双向心跳"的核心结构:

关键要点(结合上图逐层解释):

  • WSS 握手 :客户端先用 App ID + App Secret 调一次 HTTP 接口换 tenant_access_token(飞书)或 accessToken(钉钉),然后带着 token 发起 WebSocket 升级请求 GET /callback/ws/... HTTP/1.1 + Upgrade: websocket
  • 持久隧道 :握手成功后 socket 不关闭,平台侧把"原本要 POST 给 Webhook URL 的事件"直接以 WS frame 形式发过来,frame 内容仍然是 JSON(im.message.receive_v1 等)
  • 心跳保活 :客户端每 20~30s 发一个 ping frame,平台回 pong;如果连续 N 个心跳周期没收到对端回复,判定连接已死,立即触发重连
  • NAT 穿透原理 :因为是客户端主动发起的出站连接,NAT 设备会自动建立映射表,平台的回包顺着映射表回来------和 HTTP 出站请求是一个原理,完全不需要在 NAT 上打洞
  • token 刷新:长连接本身不需要刷新 token,但断线重连时要拿当时的有效 token 重新握手,所以客户端得有 token 自动续期逻辑

飞书插件实际接收事件的伪代码 (基于飞书官方 lark-oapi-py SDK,OpenClaw @openclaw/feishu 里 TS 版本逻辑相同):

python 复制代码
import lark_oapi as lark

def on_p2p_message(data: lark.im.v1.P2ImMessageReceiveV1):
    msg = data.event.message
    user_id = data.event.sender.sender_id.open_id
    # 转交 OpenClaw AgentLoop
    agent_loop.dispatch(channel="feishu",
                        peer_id=user_id,
                        text=msg.content)

ws = lark.ws.Client(
    app_id=APP_ID,
    app_secret=APP_SECRET,
    event_handler=lark.EventDispatcherHandler.builder()
        .register_p2_im_message_receive_v1(on_p2p_message)
        .build(),
)
ws.start()  # 内部 = 握手 + 心跳 + 重连,全部托管

设计取舍 :长连接的复杂度全部沉到 SDK 里------ws.start() 一行后面藏着握手 / token 刷新 / ping-pong / 重连 / 事件分发 / 消费确认的整套状态机。OpenClaw 直接复用官方 SDK,不自己实现协议------这是聪明的,别和平台官方 SDK 较劲

2.2 三平台现状对比:飞书 / 钉钉 / 企业微信

虽然我们一直在说"演进到长连接",但三个平台的进度并不一致。这里是非常重要的事实分歧:

关键要点(结合上图,每平台一句话总结当前最佳实践):

  • 飞书 / Lark :✅ 2023~2024 年陆续切到 WebSocket 。开发后台「事件订阅」面板直接提供 "使用长连接接收事件" 选项,OpenClaw 文档原文 "Choose Use long connection to receive events (WebSocket)" ;旧的 HTTP 回调仍保留兼容,但官方推荐长连接。截至 2026 年这条路径已稳定运行 2~3 年
  • 钉钉 :✅ 2023 年推出 Stream 模式(WebSocket) 。SDK 包名 dingtalk-stream,端点 wss-open-connection.dingtalk.com/connect;但老应用大量仍在用 outgoing webhook/v1/robot/send 反向 HTTP);钉钉同时支持两种,新接入推荐 Stream
  • 企业微信(WeCom) :⚠️ 截至 2026 年仍是 HTTP 回调模式receive message API 必须配置回调 URL + Token + EncodingAESKey,AES 加密 + SHA1 签名;没有公开的长连接订阅 SDK------这是企业微信开放平台节奏偏保守的体现
  • Google Chat :⚠️ 仅 HTTP webhook,OpenClaw googlechat.md 明确写 "via HTTP webhook"
  • Telegram :✅ 通过 getUpdates long-polling(特殊形态的"长连接")或 webhook 二选一;OpenClaw 通过 grammY 默认走 long-poll
  • Discord / Slack:✅ 原生就是 WebSocket(Discord Gateway / Slack Socket Mode),从来没用过 webhook 接消息
text 复制代码
平台          推荐模式                     OpenClaw 接入状态
--------------------------------------------------------------------
Feishu/Lark   WebSocket (im.message.*)      ✅ 官方插件 @openclaw/feishu
DingTalk      Stream (WSS)                  ⚠️ wiki 暂未列入官方 channel
WeCom         HTTP callback (无替代)         ⚠️ wiki 暂未列入官方 channel
Telegram      long-poll / webhook           ✅ grammY 走 long-poll
Discord       Gateway WSS                   ✅ Discord.js
Slack         Socket Mode WSS               ✅ Bolt SDK

设计取舍 :企业微信至今没出长连接 SDK,反映了它定位是"严管严控的企业 IT 渠道"------必须有公网服务器、必须备案、必须白名单,反而是它要的安全模型;而飞书定位"现代协作 + 开发者友好",钉钉定位"中小企业自动化",两者都需要降低接入门槛,所以走长连接是必然选择。


3. OpenClaw 网关侧:把"渠道差异"统一抽象掉

不同 IM 平台的接入方式天差地别------飞书用 WSS、企微用 HTTP 回调、Telegram 长轮询、Discord 原生 Gateway------但 OpenClaw 的 AgentLoop(即"决定怎么回话"的核心循环)不应该感知这些差异 。这就是 src/channels/* 这一层抽象存在的全部理由。

下图展示了 OpenClaw 的 channel 分层结构:

关键要点(结合上图,自顶向下解读):

  • 顶层 AgentLoop :只关心 "有人发了一条消息,我用 LLM 生成回复",不知道也不需要知道消息从哪个平台来
  • Channels 抽象层 :定义统一的 Message 信封------{channel, peer: {kind, id}, text, attachments, ts};每个平台 provider 进来的事件都被翻译成这个信封
  • Provider 多态 :每个平台一个 provider 模块,内部封装该平台特有 的接入方式:
    • feishu provider → 飞书官方 WS SDK 启动 + 事件回调
    • telegram provider → grammY 的 Bot.start()(long-poll)或 webhookCallback(HTTP)
    • whatsapp provider → Baileys 模拟客户端(QR 配对 + 持久 WS)
    • discord provider → Discord.js Gateway
    • googlechat provider → 内置 HTTP server 等回调
  • bindings 路由 :上层用 bindings: [{agentId, match: {channel, peer}}] 决定"这条 Feishu 群消息走哪个 agent"------这个匹配只看抽象信封,不看协议细节
  • outbound send :发回消息时同样统一接口 provider.send(envelope),每个 provider 内部再调平台 Send API

OpenClaw 飞书插件的 binding 配置真例 (来自 feishu.md):

json5 复制代码
{
  agents: { list: [{ id: "main" }, { id: "clawd-fan", workspace: "..." }] },
  bindings: [
    { agentId: "main",
      match: { channel: "feishu",
               peer: { kind: "direct", id: "ou_xxx" } } },
    { agentId: "clawd-fan",
      match: { channel: "feishu",
               peer: { kind: "group", id: "oc_zzz" } } },
  ],
}

设计取舍 :OpenClaw 没有发明新的"统一 IM 协议"(这是个反复有人尝试但都失败的方向,例如 Matrix Bridge),而是承认"每个平台 SDK 是事实标准",只在自己代码侧做一层信封翻译。复杂度藏在 provider 内部,不污染核心循环------这是工程上务实的选择。


4. 长连接的硬骨头:心跳 + 断线重连状态机

长连接看起来很美好,但工程实现上最容易翻车的就是心跳 + 重连。一个不注意,要么心跳太频繁烧服务器、要么重连风暴打挂平台、要么"假死连接"导致几小时收不到消息但日志一切正常。

下图是飞书 / 钉钉 / Discord / Slack 等所有长连接渠道通用的状态机模板:

关键要点(结合上图,每个状态的语义和必踩的坑):

  • Connecting :握手中,已发出 HTTP Upgrade: websocket 请求;超时阈值典型 10s,超时直接转入 Backoff retry
  • Connected :socket 已建立,进入心跳循环;这时必须立即把订阅事件类型告诉平台 (飞书发 subscribe 帧、钉钉发 pingResponse),漏了平台不会推任何消息
  • Heartbeat OK :每 30s 客户端发 ping frame(飞书 SDK 默认 25s),平台回 pong关键技巧:要用应用层 ping,不要只依赖 TCP keepalive------TCP keepalive 在 Linux 默认 2 小时才生效,IM 场景不可接受
  • Disconnected :连续 2 个心跳周期没收到 pong TCP socket 直接 RST/FIN;这时不能立即重连,否则会形成重连风暴
  • Backoff retry :指数退避 1s, 2s, 4s, 8s, ... 上限 30s,加 ±20% 随机抖动避免羊群效应;重要:达到最大次数(典型 10 次)后要触发告警,不能无限重连掩盖配置错误(比如 secret 错了)

典型实现伪代码(生产级简化版):

ts 复制代码
async function runWithReconnect() {
  let attempt = 0;
  while (true) {
    try {
      const ws = await connectWithToken();        // Connecting
      attempt = 0;                                 // 成功后重置
      startHeartbeat(ws, 30_000);                  // Connected -> Heartbeat OK
      await ws.waitForClose();                     // 阻塞直到断开
    } catch (err) {
      log.warn("ws disconnected", err);
    }
    // Backoff retry
    const delay = Math.min(30_000, 1000 * 2 ** attempt) * (0.8 + Math.random() * 0.4);
    attempt = Math.min(attempt + 1, 10);
    if (attempt === 10) alertOps("ws reconnect storm");
    await sleep(delay);
  }
}

function startHeartbeat(ws, interval) {
  let missed = 0;
  setInterval(() => {
    ws.ping();
    setTimeout(() => {
      if (!ws.lastPongWithin(interval)) {
        missed++;
        if (missed >= 2) ws.terminate();           // 主动断开触发重连
      } else { missed = 0; }
    }, interval - 1000);
  }, interval);
}

设计取舍:心跳间隔不能太短(30s 是工业界共识,太短烧电池/烧服务器)也不能太长(>60s 移动网络 NAT 表项可能已经被运营商回收,回包回不来);指数退避必须加抖动,否则一千个机器人同时断线会同时重连把平台打死------这是分布式系统的基本功,但每年都有团队栽在这。


5. 总结:什么时候用什么

场景 推荐模式 理由
公司内部 IT 后台机器人,已有公网服务器 HTTP Webhook 简单直接,既有运维能 cover
给最终用户的桌面/笔记本 AI 助手 长连接 零部署门槛,用户不需要懂运维
需要超低延迟(流式输出) 长连接 服务端推送无需轮询,延迟 <100ms
平台只支持 Webhook(如企微) HTTP Webhook + 内网穿透 没有别的选择,frp / cloudflare tunnel 兜底
高并发集群 视情况 Webhook 易水平扩;长连接需要 sticky session

OpenClaw 的选型逻辑很清晰 :它的目标用户是"在自己机器上跑 Agent"的开发者和最终用户,所以只要平台提供长连接选项就坚决用长连接------飞书插件锁死 WebSocket,Telegram 默认 long-poll,WhatsApp 走 Baileys WS。只有像 Google Chat 这种纯 webhook 平台才退而求其次。

理解了这套演进逻辑,再回头看 OpenClaw 文档里那句不起眼的提示------"Use long connection to receive events"------你就会明白:这不是一个无关紧要的勾选项,而是 OpenClaw 整个"零部署门槛"产品定位的技术地基。

相关推荐
x-cmd1 天前
[260612] x-cmd v0.9.8:x feishu 发送消息支持 Markdown + 卡片,让 x claw 接入飞书后消息不再干巴巴
飞书·agent·claude·命令行·x-cmd·openclaw
诗词在线1 天前
专业的飞花令网站
飞书
Kimgoeunlaogong1 天前
Clawdbot汉化版从零开始:Clawdbot前端控制台二次开发+UI主题定制
企业微信·前端开发·ai助手·clawdbot
TMT星球1 天前
钉钉发布DingTalk A1豆蔻医生版,售价999元
人工智能·深度学习·钉钉
金融Tech趋势派2 天前
企业微信私域实现高效增长的3步策略:精准获客+粘性留存+高效转化
大数据·人工智能·企业微信
金融Tech趋势派2 天前
企业微信SCRM哪个好?2026年企业微信客户管理工具服务商选型测评与金融汽车零售等行业实战指导
金融·汽车·企业微信
Linlingu2 天前
OpenClaw接入钉钉企业内部机器人完整实操教程(Stream模式无公网部署)
人工智能·windows·机器人·钉钉·办公自动化·小龙虾
@Ma2 天前
企业微信外部群机器人接入 AI:调用API接口自动回复 实战
人工智能·机器人·企业微信
tianxiaxue13 天前
企业微信 SCRM 自动打标签功能使用教程
企业微信