关键词: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 4 :3 秒内必须返回 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 发一个
pingframe,平台回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 :✅ 通过
getUpdateslong-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 模块,内部封装该平台特有 的接入方式:
feishuprovider → 飞书官方 WS SDK 启动 + 事件回调telegramprovider → grammY 的Bot.start()(long-poll)或webhookCallback(HTTP)whatsappprovider → Baileys 模拟客户端(QR 配对 + 持久 WS)discordprovider → Discord.js Gatewaygooglechatprovider → 内置 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:握手中,已发出 HTTPUpgrade: websocket请求;超时阈值典型 10s,超时直接转入Backoff retryConnected:socket 已建立,进入心跳循环;这时必须立即把订阅事件类型告诉平台 (飞书发subscribe帧、钉钉发pingResponse),漏了平台不会推任何消息Heartbeat OK:每 30s 客户端发pingframe(飞书 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 整个"零部署门槛"产品定位的技术地基。