飞书对话机器人
飞书企业自建应用可以订阅“接收消息”事件,并通过消息 API 回复。本文以长连接作为快速接入路径,同时说明 HTTP 回调的生产要求;核验日期为 2026-09-14,未在真实企业租户发布应用。
- 自定义机器人 Webhook 只用于向固定群投递,不能接收群成员消息。
- 双向对话需要创建带机器人能力的企业自建应用,订阅
im.message.receive_v1。 - 群聊中机器人通常围绕
@机器人消息工作;私聊事件与群聊事件可见范围受应用权限、版本和发布范围控制。 - 长连接适合不想提供公网回调地址的企业自建应用;HTTP 回调适合已有统一入口、队列和验签体系的生产环境。
- 两种接收方式都只负责把平台事件交给应用。普通命令和 AI 回复最终仍调用相同的消息 API。
- 同一应用不应让长连接和 HTTP 回调同时处理同一事件,否则即使平台只投递一次,也可能产生双重回复。
- 有权限在飞书开放平台创建企业自建应用、启用机器人能力、申请权限并发布版本。
- 有一个测试用户和测试群,应用可用范围覆盖这些成员,机器人已经加入测试群。
- 准备 App ID、App Secret;HTTP 模式还要按控制台配置保存 Verification Token 和 Encrypt Key。
- 长连接模式准备官方服务端 SDK;HTTP 模式准备公网 HTTPS 地址、原始请求体读取能力和事件队列。
- 已定义群聊触发规则、事件去重存储和稳定会话键,模型服务不可用时有非 AI 降级响应。
如何选择接收方式
Section titled “如何选择接收方式”| 条件 | 长连接 | HTTP 回调 |
|---|---|---|
| 公网入口 | 不需要,由服务主动连接飞书 | 需要稳定公网 HTTPS 地址 |
| 推荐实现 | 使用官方 SDK 处理鉴权、心跳和重连 | 使用官方 SDK 处理 challenge、验签和解密 |
| 扩缩容 | 控制连接实例和事件消费归属 | API 网关后可横向扩展,靠事件 ID 去重 |
| 适用阶段 | 本地开发、内网部署、快速联调 | 已有网关、WAF、队列和统一回调平台 |
不要自行实现长连接协议。HTTP 回调如果不能安全保存原始请求体,也不应跳过验签后直接解析 JSON。
1. 创建应用和机器人
Section titled “1. 创建应用和机器人”- 在开发者后台创建企业自建应用。
- 在添加应用能力中启用机器人,设置名称与基本信息。
- 在权限管理申请接收与发送单聊、群组消息所需权限;按调用身份选择最小权限。
- 把可用范围先限制在测试成员,并由管理员完成必要审批。
2. 配置事件接收
Section titled “2. 配置事件接收”- 进入事件与回调,选择长连接或 HTTP 回调。
- 长连接模式按官方 SDK 初始化 App ID 和 App Secret,在代码中注册
im.message.receive_v1处理器。 - HTTP 模式填写 HTTPS 请求地址,处理平台的 URL challenge,并按当前配置校验 Verification Token 或解密加密事件。
- 添加事件
im.message.receive_v1(接收消息)。
URL challenge 只用于验证回调地址,不能进入正常业务队列。事件回调先验签或解密,再以 header.event_id 落去重记录并快速返回成功响应。
3. 发布并联调
Section titled “3. 发布并联调”- 创建并发布应用版本,确认机器人能力、权限、事件和可用范围都已经生效。
- 把 Bot 加入测试群,确认群设置允许机器人发言。
- 在群内发送
@机器人 /ping;私聊场景直接发送/ping。 - 先返回固定
pong,再接业务 API,最后接入模型服务。
修改权限、事件或可用范围后通常需要重新发布版本才会对用户生效。不要只在开发者后台保存配置就开始排查代码。
事件 v2 的稳定去重标识位于事件头 header.event_id,事件类型为 im.message.receive_v1。正文中的 message.message_id 用于回复原消息,message.chat_id 标识会话,sender.sender_id 标识发送者。
message.content 是按消息类型编码的 JSON 字符串,文本消息需先 JSON 解析再读取 text。message.mentions 给出提及标记和用户标识,应按 mentions[].key 移除机器人 mention,而不是用正则删除所有 @ 文本。不要把整段事件直接作为提示词。
{ "header": { "event_id": "evt_xxx", "event_type": "im.message.receive_v1" }, "event": { "sender": { "sender_id": { "open_id": "ou_xxx" } }, "message": { "message_id": "om_xxx", "chat_id": "oc_xxx", "chat_type": "group", "message_type": "text", "content": "{\"text\":\"@_user_1 /ping\"}" } }}使用 header.event_id 做事件去重。会话键推荐 feishu:{tenant}:{chat_id};话题内对话可加入 thread_id 或根消息 ID,私聊需要按租户和用户隔离。显示名不能作为身份或会话主键。
最小回复验证
Section titled “最小回复验证”先取得 tenant access token,再调用回复消息接口。下面假设已经把 Token 放入环境变量,仅验证平台回复路径:
curl -X POST 'https://open.feishu.cn/open-apis/im/v1/messages/MESSAGE_ID/reply' \ -H 'Authorization: Bearer TENANT_ACCESS_TOKEN' \ -H 'Content-Type: application/json; charset=utf-8' \ -d '{"msg_type":"text","content":"{\"text\":\"pong\"}"}'const response = await fetch(`https://open.feishu.cn/open-apis/im/v1/messages/${process.env.MESSAGE_ID}/reply`, { method: 'POST', headers: { Authorization: `Bearer ${process.env.TENANT_ACCESS_TOKEN}`, 'Content-Type': 'application/json; charset=utf-8', }, body: JSON.stringify({ msg_type: 'text', content: JSON.stringify({ text: 'pong' }) }),});const result = await response.json();if (!response.ok || result.code !== 0) throw new Error(JSON.stringify(result));import osimport requests
response = requests.post( f"https://open.feishu.cn/open-apis/im/v1/messages/{os.environ['MESSAGE_ID']}/reply", headers={"Authorization": f"Bearer {os.environ['TENANT_ACCESS_TOKEN']}"}, json={"msg_type": "text", "content": '{"text":"pong"}'}, timeout=15,)response.raise_for_status()result = response.json()if result.get("code") != 0: raise RuntimeError(result)普通 API 双向回复
Section titled “普通 API 双向回复”收到文本事件后先依据 mentions 移除平台生成的机器人标记,再解析 /ping、/status 等命令。用原 message_id 调用“回复消息”可以保留对话关系;需要以话题形式回复时设置 reply_in_thread=true。主动发送到其他会话则使用“发送消息”接口并明确 receive_id_type。
长连接和 HTTP 回调都可能发生重复投递。业务处理前为 event_id 建唯一记录,处理成功后保存回复消息 ID。HTTP 入口应尽快确认,不在回调线程中等待慢业务;长连接处理器也应快速入队并按 SDK 要求确认。
回复与主动发送接口对同一用户限频为 5 QPS,同一群的机器人共享 5 QPS。使用可选 uuid 可以让同一发送或回复请求在一小时内至多成功一次,适合超时重试。
AI 对话扩展
Section titled “AI 对话扩展”const conversationKey = `feishu:${tenantKey}:${message.chat_id}`;const answer = await generateReply({ messages: await history.withUserMessage(conversationKey, normalizedText), signal: AbortSignal.timeout(20_000),});await replyMessage(message.message_id, answer);- 群聊默认仅处理明确
@机器人的内容;私聊可直接进入对话。 - 富文本、图片和文件需先按类型解析、下载和安全扫描,不能假设
content.text永远存在。 - 模型超时后返回简短错误或转人工;不要让平台回调等待整轮模型生成。
- AI 不需要额外“平台 AI 权限”,但模型服务、知识库和工具各自仍需鉴权与合规评估。
- 长回复按飞书消息大小限制切分;代码块和卡片结构不能在任意字节位置截断。
安全与可靠性
Section titled “安全与可靠性”- HTTP 模式使用官方 SDK 验证请求、处理 URL challenge 和加密事件;先保留原始请求体。
- 长连接使用官方 SDK,维护连接生命周期,不把 App Secret 输出到日志。
- 使用
event_id做事件去重,对同一会话串行处理,避免 AI 回复乱序。 - App ID 与 App Secret 只在服务端;租户 Token 缓存时按应用和租户隔离。
- 对用户输入实施提示词注入防护,工具调用在服务端按用户和群权限重新鉴权。
- 忽略机器人自身发送的事件,限制文本、附件、单用户并发和每日模型预算。
- 保存平台请求 ID、事件 ID 和回复消息 ID 的关联,但不记录完整消息正文和凭据。
收不到事件:检查机器人能力、事件订阅、权限、应用版本是否已发布,以及用户/群是否在可用范围内。
群内不响应普通文字:推荐让用户 @机器人;不要假设 Bot 可以读取群内所有消息。
回复接口无权限:核对消息权限、Token 所属应用与应用是否仍在会话中。
同一消息回复多次:以 header.event_id 做持久化去重,不要只在单进程内存中记录。
URL challenge 通过后仍收不到事件:检查事件是否已添加、应用版本是否重新发布、用户是否在可用范围,以及 HTTP 入口是否在验签前修改了原始请求体。
机器人回复打断群内话题:使用原消息 ID 调用回复接口;需要话题回复时设置 reply_in_thread=true,不要改成向整个 chat_id 主动发送。
高峰期出现限流:按用户或群维度排队,合并重复请求,并对平台限流响应做退避,不并发重试。