飞书对接腾讯会议实战:API集成、鉴权与自动化流程
做企业内部效率工具这几年我被问得最多的问题就是能不能让飞书和腾讯会议别再“各干各的”。很多公司 IM 选了飞书视频会议又因为历史习惯、生态绑定或者采购原因一直用腾讯会议中间就跟隔了一堵墙似的用户要在飞书里拉人开会还得切到腾讯会议客户端新建会议开会产生的纪要和结论又得手动复制回飞书文档。飞书和腾讯会议对接表面上是两个平台的 API 互相调本质上是把“会前发起、会中管理、会后沉淀”这条链路彻底捋顺。这篇内容适合三类人看一类是公司内部做 OA、效率工具开发的工程师想找一个可以直接落地的对接方案一类是 IT 管理员想搞清楚飞书机器人和腾讯会议接口到底怎么配权限、怎么审流程还有一类是负责企业数字化选型的产品同学需要评估这套对接的成本和边界。我会从方案选型、应用配置、鉴权原理、核心代码、排错经验几个维度完整讲一遍尽量把我在实际项目中踩过的坑都写进来。1. 对接思路先想清楚再动手1.1 两套系统到底能对什么很多人一听“飞书对接腾讯会议”下意识以为是要把两个客户端界面融合在一起其实不是。我们做的是开放平台层面的集成两边都提供了对外接口我们要做的就是把接口能力按业务场景组装起来。飞书侧能提供的能力包括用户身份识别、群聊和单聊消息、消息卡片、云文档、审批流、日历、机器人事件订阅。腾讯会议侧能提供的能力包括会议预约、会议创建、会议查询、会议取消、会议结束、参会人列表、录制文件、会议开始和结束的事件回调。把这两组能力叠在一起就能实现很实际的功能用户在飞书里通过指令或者按钮创建腾讯会议会议信息以卡片形式回传到飞书会议开始和结束时自动通知与会人会后把参会人、时长、结论自动写入飞书云文档。这是最典型的闭环也是我后面要展开的主线。1.2 对接方案选型别一上来就写代码正式动手之前我建议先做方案选型。很多团队上来就要写后端服务其实不一定是最优解。如果业务很简单比如只是“会议结束后在飞书群里发一条固定文案”那直接用腾讯会议的 webhook 接飞书自定义机器人就能搞定不需要开发一个完整应用。如果需要对会议进行创建、取消、查询等操作就需要走开放平台 API这时候才需要一个中转了。我常用的选型对比大致是这样方案适用场景开发成本灵活度坑点飞书自定义机器人 腾讯会议 webhook纯消息通知极低低无法按用户身份操作会议只能单向推送飞书自建应用 腾讯会议开放 API完整双向对接中高高需要处理权限、签名、事件订阅现有集成市场应用不想开发零最低不一定匹配公司流程实际项目中大多数客户最后的诉求都超过“纯通知”层面所以本文按“飞书自建应用 后端服务 腾讯会议开放 API”的方案来讲。这个方案的核心思想是后端服务作为适配层飞书和腾讯会议互不感知对方存在所有转换和业务逻辑都在适配层里完成。1.3 整体链路消息从飞书到腾讯会议怎么走整个对接链路可以用一句话讲清楚用户在飞书里通过应用入口或机器人的交互卡片表达意图飞书把事件推给后端服务后端服务调用腾讯会议 API 完成会议操作再把结果以卡片或文档的形式回写到飞书。对应到具体事件流大概是这么个顺序用户在飞书群里 机器人或者点击卡片上的“创建会议”按钮。飞书开放平台把消息事件或卡片回调事件推送到后端服务。后端服务解析事件内容校验用户身份。后端服务调用腾讯会议 API创建会议。腾讯会议返回会议 ID、会议号、入会链接。后端服务把会议信息组装成飞书消息卡片发送给用户或群聊。会议开始或结束时腾讯会议把事件推送到后端服务。后端服务更新之前发出的卡片或者调用飞书云文档 API写入会议纪要和结论。这里最关键的一点是做好“两边事件回调”的幂等和校验。飞书和腾讯会议的回调机制都做到了“at least once”同一个事件可能因为网络重试而收到多次。如果代码里不做幂等处理用户就会看到重复提醒、重复生成文档。2. 飞书侧准备把应用的手和权限都打通2.1 创建企业自建应用飞书侧的第一步是在飞书开放平台里创建一个企业自建应用。你进入开发者后台之后选择“企业自建应用”填好应用名称和描述创建成功后就能看到 App ID 和 App Secret。这两个值要小心保管App ID 相当于你的身份标识App Secret 相当于你的密码一旦泄露别人就能冒充你的应用调用接口。创建应用之后有两件事必须做一是启用“机器人”能力只有开启了机器人应用才有对应的机器人账号才能在群里被 、才能发消息二是配置“可用范围”企业内部应用默认只对管理员可见你必须把部门和成员加进可用范围否则普通员工根本发现不了这个应用。我见过不少新手在这里卡住应用创建好了代码也写了但在飞书里就是找不到这个机器人。十有八九是忘记发布版本了。飞书自建应用和私人开发项目不一样改了权限、改了配置必须通过“版本管理”创建一个版本并发布发布后管理员审批通过新权限才会真正生效。这一步没有捷径只能耐心等待审批流程。2.2 权限列表与事件订阅飞书的开放接口对权限管理很严格。要让应用能收发消息、读写文档必须在“权限管理”里申请对应的权限范围。对我这套对接收发消息和写文档的场景至少要申请以下权限权限标识作用im:message获取与发送单聊、群组消息im:message:send_as_bot以机器人身份发送消息部分应用需要contact:user.base:readonly读取用户基础信息用于关联身份docx:document创建、编辑飞书云文档event:im.message.receive_v1接收用户发给机器人的消息事件申请权限的时候注意一个细节权限范围越大管理员审批时越谨慎。如果你的场景只需要读取某些字段就不要为了省事申请全部权限。比如只是想把开会人姓名写到文档里申请 contact:user.base:readonly 就够用了没必要申请 contact:contact:readonly。事件订阅是另一个容易出问题的地方。飞书允许你配置一个回调地址当用户给机器人发消息、或者有卡片操作时飞书会往这个地址推送事件。配置回调地址时有一步“URL 验证”是绕不开的飞书会往你的回调地址发一个 POST 请求请求体里带一个 challenge 字段你的服务必须原样返回这个字段验证才算通过。如果你在飞书后台开启了“加密”开关回调内容还会经过 AES 加密这时候不仅要验证 token还要用 Encrypt Key 解密才能拿到真实的事件内容。新手最容易在这里被绕晕建议开发阶段先不开加密等联调通了再考虑加密。2.3 飞书机器人消息卡片会议信息不是纯文本飞书机器人发送消息有两种常用格式文本和卡片。对接腾讯会议这种场景我强烈建议用卡片而不是纯文本。原因很简单会议信息结构化的字段太多会议主题、会议号、入会链接、时间、主持人、状态。纯文本把这些堆在一起界面很拥挤也没办法让用户直接在消息里操作。飞书的 interactive 卡片本质是一段 JSON。你可以定义标题、字段、分割线、按钮。在对接腾讯会议时我一般会在卡片上放两个按钮一个是“入会链接”点击后直接跳到腾讯会议入会另一个是“结束会议”点击后按钮回调到后端服务后端调用腾讯会议接口把会议取消。这里有件事要提前想好卡片的按钮是有回调机制按钮被点击后飞书会往一个单独的回调地址 POST 请求请求里带着你在按钮 value 里写的字段。后端要根据这个字段判断用户点了哪个按钮。这个回调地址和事件订阅地址可以共用也可以分开配置建议分开因为两种请求的格式差异很大共用会让代码分支越来越乱。还有一个非常容易踩的坑发送卡片时飞书接口返回的消息 ID 一定要存下来。后面你要更新卡片内容比如把“会议待开始”改成“会议已结束”必须带上消息 ID 去调用更新消息接口。如果没存消息 ID后续只能重新发一张新卡片群里的消息列表就会越堆越乱。3. 腾讯会议侧准备鉴权和会议接口是核心3.1 开放平台应用注册腾讯会议侧的准备工作和飞书类似也是要去开放平台注册应用。腾讯会议的企业版需要先完成企业认证认证通过后才能创建应用。创建应用后你会拿到一组凭据通常包括 App ID、Secret ID 和 Secret Key。特别提醒腾讯会议的凭据体系和飞书不一样飞书核心是 App ID App Secret 两个值腾讯会议有的接口用 Secret ID / Secret Key 做签名有的接口则走 OAuth 2.0 流程。开始写代码之前先花半小时把官方文档的鉴权部分读明白能省很多调试时间。在真实企业环境里还有一个容易忽略的点腾讯会议 API 操作会议时需要指定 userid 来说明“以谁的身份创建会议”。这个 userid 是腾讯会议侧的企业用户 ID不是飞书的用户 ID。所以在对接之前你需要准备一张映射表把飞书的用户 ID 和腾讯会议的 userid 关联起来。映射方式有三种管理员手动维护、登录时自动绑定、通过手机号或邮箱匹配。三种方式里通过同一企业统一身份源自动匹配是最可靠的但初期手动维护也完全可以跑起来。3.2 两种鉴权方式和选择腾讯会议开放 API 的鉴权方式主要是两类一类是 OAuth 2.0 授权码模式适合以某个具体用户身份调用接口另一类是应用签名模式适合服务端对服务端的调用。对飞书对接腾讯会议这种场景我更推荐应用签名模式。原因很实际OAuth 授权码模式需要每个用户都去点一次授权链接在内部工具这种场景里体验太割裂了。应用签名模式只需要在请求头里带上签名参数后端服务持有 Secret Key就可以直接代表企业应用调用接口用户无感。签名模式的核心是四个请求头请求头含义X-TC-Key应用的 App IDX-TC-Nonce随机字符串每次请求不同X-TC-Timestamp请求发起的 Unix 时间戳X-TC-Signature对请求内容和时间戳进行 HMAC-SHA256 签名后的结果签名的计算方法是把 Secret Key、时间戳、Nonce、请求方法、请求路径、请求体拼接成一个字符串再用 HMAC-SHA256 算法做哈希最后做 Base64 编码。这个拼接顺序千万不能错错了签名就校验不过。我第一次对接时就在这个拼接顺序上卡了半小时。计算签名的代码大概是这样的import time import hmac import hashlib import base64 import json def build_signature(secret_key: str, method: str, path: str, body: dict, timestamp: str, nonce: str) - str: # 注意body 必须按实际发送的序列化内容拼接不能重新 dump 一份 body_str json.dumps(body, ensure_asciiFalse, separators(,, :)) raw f{secret_key}{timestamp}{nonce}{method}{path}{body_str} digest hmac.new(secret_key.encode(utf-8), raw.encode(utf-8), hashlib.sha256).digest() return base64.b64encode(digest).decode(utf-8)有一个细节很多人不知道拼接用的 body 字符串必须和实际发送到接口的 request body 完全一致。如果你先调了一次 json.dumps 生成 body后面又用另一个序列化方式重新生成这个签名百分之百验不过。我的习惯是只做一次序列化把字符串存到一个变量里发送请求和计算签名都用同一个变量。3.3 会议生命周期接口要点腾讯会议的会议接口并不复杂难点在参数语义上。创建会议接口的核心字段有这些参数说明备注subject会议主题必填type会议类型0 代表普通会议1 代表周期会议start_time会议开始时间Unix 秒级时间戳字符串end_time会议结束时间Unix 秒级时间戳字符串userid会议创建者腾讯会议侧的用户 IDsettings会议设置入会静音、水印等有一点要特别注意start_time 和 end_time 传的是 Unix 秒级时间戳但接口要求用字符串格式。如果按照习惯传了整型或者毫秒级时间戳接口可能会直接拒绝或者创建出来的会议时间完全不对。我在对接时专门加了一步校验把时间戳统一转成字符串同时对时区做了统一处理。创建会议成功后返回值里最重要的是两个字段一个是 meeting_id这是会议的全局唯一 ID后续查询、取消、结束都要用它另一个是 meeting_code也就是用户在客户端里手动输入的会议号。还有 join_url这是参会链接飞书卡片里的入会按钮可以直接跳转这个链接。会议结束接口在腾讯会议里叫“取消会议”语义上和我们说的“结束会议”一致。调用时带上 meeting_id 和 userid 就行。注意已经结束的会议再调用取消接口会报错代码里要做好错误处理不能因为重复调用让用户看到不明不白的失败提示。3.4 回调事件腾讯会议主动推给我们的“信号”腾讯会议也支持事件回调。在企业开放平台后台配置回调地址后会议开始、会议结束、参会人加入等事件会以 POST 请求的形式推送到你的服务上。常用的回调事件类型大概是这几个事件类型触发时机meeting.started会议开始meeting.ended会议结束meeting.participant.joined有参会人加入meeting.participant.left有参会人离开回调请求带的数据足以为后续动作服务至少包含会议 ID、会议主题、开始时间、结束时间、事件类型。收到会议结束事件后就可以去调用“获取会议参会人列表”的接口把参会人名单拉出来连同会议时长一起写入飞书云文档。回调地址的安全性也要重视。腾讯会议在推送事件时会带上签名后端收到请求后要校验签名确认来源之后再做业务处理。防御性的校验能避免有人伪造请求来触发你的业务逻辑比如往你的飞书群里塞垃圾消息。4. 核心对接实现从飞书指令到腾讯会议全链路4.1 服务端骨架与路由规划我后端习惯用 Python FastAPI生态成熟、异步支持好对接两个平台的 webhook 很顺手。飞书官方提供了 lark-oapi SDK可以省去很多底层加解密、重试的重复工作。腾讯会议没有太通用的 SDK直接用 requests 调接口也够用。项目启动之前先把路由规划好。我通常会分四组路由功能/webhook/feishu/event接收飞书事件订阅比如用户发给机器人的消息/webhook/feishu/card接收飞书卡片按钮回调/webhook/tencent/meeting接收腾讯会议开始、结束等事件/health健康检查方便运维监控用户映射表是另一个必须提前规划的内容。飞书侧用 open_id 或 user_id 标识用户腾讯会议侧用 userid 标识用户。我建了一张 user_mapping 表字段包括 feishu_open_id、feishu_user_id、tencent_userid、真实姓名。会议纪要写到飞书文档时要用真实姓名调用腾讯会议接口时要用 tencent_userid。这两个字段如果在源头上就没落好后面所有功能都会受影响。4.2 用户在飞书里发起会议用户在飞书群聊里 机器人发一句“创建会议 产品周会 14:00-15:00”事件会以 im.message.receive_v1 的格式推送到后端。后端解析文本提取主题和时间然后调用腾讯会议创建会议接口。飞书事件推送的请求体是加密的用官方 SDK 处理可以省很多事。解密之后事件内容里能拿到消息文本、发送者 open_id、所处群聊的 chat_id。创建会议时有几件事要做通过 open_id 查 user_mapping 表拿到对应的腾讯会议 userid。解析用户消息里的时间转成 Unix 时间戳注意默认时区。调用腾讯会议创建会议接口。拼接飞书消息卡片 JSON把会议主题、会议号、入会链接、时间都列进去。发送卡片并保存消息 ID。核心代码大概是这样的from fastapi import FastAPI, Request import json app FastAPI() app.post(/webhook/feishu/event) async def feishu_event_handler(request: Request): payload await request.json() # 处理飞书 URL 验证 if payload.get(type) url_verification: return {challenge: payload[challenge]} # 这里以已经解密、解析后的数据结构为例 event payload.get(event, {}) message event.get(message, {}) msg_type message.get(message_type) if msg_type ! text: return {code: 0} open_id event[sender][sender_id][open_id] text message[content] # 实际是 JSON 字符串 text_content json.loads(text).get(text, ) if 创建会议 in text_content: subject, start_ts, end_ts parse_meeting_request(text_content) tencent_userid get_tencent_userid(open_id) meeting create_tencent_meeting(subject, start_ts, end_ts, tencent_userid) card build_meeting_card(meeting) send_feishu_card(open_id, card) return {code: 0}这段代码没有处理异常分支生产环境里一定要加 try/except并且对腾讯会议返回的非成功状态做明确提示比如“该时段你已有会议”让用户知道发生了什么而不是收到一条空泛的错误消息。4.3 会议状态回传卡片自动更新会议创建成功之后卡片会停留在飞书会话里。会议开始时腾讯会议会推送 meeting.started 事件会议结束时推送 meeting.ended 事件。后端收到这些事件后可以根据会议 ID 反查出之前发送的飞书消息 ID调用飞书接口更新卡片内容。更新卡片比发送卡片多一步必须把原消息 ID 传给更新接口。调用方式是 PUT /open-apis/im/v1/messages/{message_id}请求体里带上新的卡片内容。状态回传的场景里还有一个很实际的问题一场会议可能涉及多个飞书群。比如用户创建会议时在 A 群发了卡片开会时他可能又拉了个 B 群进行讨论。如果只在会议创建时所在的会话里更新卡片B 群的用户看不到状态变化。我的做法是会议和群聊的关联关系单独存一张表创建会议时记录 user_open_id 和 chat_id如果后续用户主动把会议卡片的链接分享到其他群也记录下来。会议状态变化时遍历所有关联会话逐个更新卡片。这样做的代价是代码复杂度上升但用户体验会好很多。否则用户收到“会议已结束”的通知很可能是在一个已经没人说话的旧群里。4.4 会议纪要同步飞书云文档会议结束后把参会人、时长、结论写入飞书云文档是一件非常有价值的事。飞书云文档 API 提供创建文档、写入内容的能力调用前需要用应用的身份换取 tenant_access_token然后创建 docx 文档。创建文档的接口比较简单核心参数只有文档标题。文档创建成功后返回一个 document_id。但要注意新创建的文档是空的你要往里面写内容需要通过“写入块”的接口一行一行地添加标题块、文本块。飞书的文档内容是以 block 为单位的操作起来比普通 Markdown 要繁琐。我实际写的时候会先把要写入的内容组装成一个结构化的列表比如[ {type: heading1, text: 会议纪要 - 产品周会}, {type: text, text: 时间2025-06-10 14:00-15:00}, {type: text, text: 参会人张三、李四、王五}, {type: text, text: 结论确定下周一上线}, {type: text, text: 待办张三输出测试报告} ]然后遍历这个列表逐块写入文档。写完后获取文档链接把链接追加到之前的飞书卡片里。关于飞书云文档的授权凭证这里多说一句。很多第三方系统第一次接入飞书云文档时都会问“授权凭证怎么取得”其实和你对接飞书机器人用的是同一套机制自建应用在后台申请 docx:document 权限发布版本后通过 App ID 和 App Secret 调用 tenant_access_token 接口换取凭证之后所有云文档接口都在请求头带上这个 token。整个过程不涉及用户扫码授权后台权限审批通过即可。4.5 定时任务和失败补偿接口联调完成后能跑通 demo 只是第一步。真实生产环境里事件推送可能丢失、接口可能超时、服务可能重启。如果完全依赖回调一个回调丢了会议状态就一直不更新。所以我建议加一个定时对账任务。对账逻辑不复杂每天固定时间比如早上九点跑一次从数据库查出当天所有应飞书创建的会议逐个调用腾讯会议查询接口核对状态是否一致。如果回调显示未更新但接口查询已经结束就手动补发一次通知并更新卡片。对账任务还能发现那些“预约了但没开”的会议提醒创建人及时处理。幂等性设计也要从第一步就考虑。腾讯会议回调可能重复飞书消息发送可能重试落库时要对每个事件生成唯一的业务 ID发现重复直接忽略。我常用的做法是建一张 event_log 表把收到的回调 ID 存进去主键唯一插入失败说明已经处理过。5. 常见问题与排查技巧实录5.1 问题速查表我把自己实际遇过的问题和排查思路整理成一张速查表按这个表排查大部分问题都能定位现象可能原因排查方法飞书事件订阅 URL 验证失败服务没有正确返回 challenge 字段或者加密开关导致返回格式不对先用 curl 模拟飞书请求确认响应体里包含 challenge飞书机器人消息发不出去应用权限没发版用户不在可用范围消息类型不受支持检查应用版本是否发布权限是否包含 im:message卡片按钮点击没有反应卡片回调地址未配置回调请求处理异常在回调地址日志里确认是否收到 POST检查返回 code 是否为 0腾讯会议接口返回 401 或签名错误timestamp 误差过大body 序列化不一致nonce 重复检查服务器时间确保签名前后使用同一个 body 字符串创建的会议时间差 8 小时start_time 按本地时间转时间戳接口期望的是 UTC 时间戳统一用带时区的处理函数转换时间不要手动减 8 小时会议创建成功但飞书没收到卡片发送消息时用的 open_id 不可用消息发送接口返回错误码先单独调飞书消息接口测试检查 receive_id_type 是否传对收到重复的会议结束通知回调重试机制导致重复推送没有做幂等在 event_log 表里做唯一约束重复事件直接返回成功每次排查问题的时候我建议先看原始请求和原始响应不要只盯着框架日志里的异常信息。很多对接问题比如签名错误、格式不匹配看原始报文一眼就能明白但框架层把错误信息包装过之后反而让人摸不着头脑。5.2 审批权限和回调地址的联动坑飞书应用的权限管理和腾讯会议的回调地址配置表面上互不相干实际联动起来有一个很隐蔽的坑当你对飞书应用新增了某个权限或者修改了事件订阅配置调整后的代码并不会自动生效必须重新创建一个应用版本并发布。腾讯会议侧改了回调地址通常立即生效但飞书侧有版本审批机制两边生效节奏不一致容易让人误判是代码问题还是配置问题。我的习惯是建一个大版本统一操作要改权限就集中改一次发布一次然后在联调环境里把所有功能从头到尾过一遍。不要每改一个小权限就立刻发版飞书管理员的审批流程会拖慢你的节奏。另一个联动坑是卡片回调地址和事件订阅地址的差异。飞书的卡片回调请求里按钮的 value 字段在回调后是带在 action.value 里的而不是直接在 event 里。我第一次接的时候直接在事件订阅的处理器里找按钮 value结果怎么都找不到。后来才发现必须单独配卡片回调地址接收的请求体结构完全不同。5.3 签名与鉴权的几个隐藏细节腾讯会议签名校验做到最后往往不是算法难而是细节多。以下几个细节都是我在真实场景里踩过的第一timestamp 的误差范围。腾讯会议对时间戳的容忍度大概在 5 分钟左右服务器时间偏差过大就会导致签名失效。部署服务的机器如果是内网虚拟机一定要配置 NTP 时间同步否则早晚会踩这个坑。第二请求方法大小写。签名拼接时用的是大写 POST、GET如果代码里写成了小写签名也能算出来但服务器校验时就是通不过。这个问题极其隐蔽因为日志里看不出任何异常。第三平台迁移时的密钥变更。企业有时候会从腾讯会议的某个版本升级到另一个版本应用凭据可能被重置。如果代码里硬编码了 Secret Key升级后接口突然全部报错排查起来很费劲。建议把凭据放到配置中心或者环境变量里方便一键切换。5.4 飞书卡片交互的体验优化对接功能稳定之后还可以在交互体验上做一些优化这些优化不会增加太多开发成本但对用户感知的提升非常明显。第一个建议是卡片尺寸要克制。飞书卡片能放很多内容但不要把腾讯会议返回的所有字段都塞进去。我一般只放会议主题、时间、会议号、入会链接、状态其他像主持人密码、参会密码之类的内容折叠到卡片详情里或者干脆不放。卡片一旦信息过载用户反而不愿意操作。第二个建议是结束会议这个按钮要加二次确认。飞书卡片按钮默认是点击即回调如果你把“结束会议”按钮直接暴露出来用户手滑点了一下一场进行中的会议就被取消了。我的做法是点击“结束会议”时先发一条单独的确认消息包含“确认结束”和“取消”两个按钮只有点击“确认结束”才真正调用腾讯会议接口。这个二次确认机制成本不高但能避免很多误操作。第三个建议是会议创建失败时的提示要有人情味。对接做多了你会发现腾讯会议返回的错误信息对普通用户极不友好。我在适配层里做了一层错误码翻译把“会议时长超过限制”“会议标题包含敏感词”“该用户暂无权限”这类错误映射成可读的中文提示再拼到飞书卡片里。用户看到的是“会议标题不能超过 20 个字”而不是一串 HTTP 状态码。以上这些都是对接层面之外的工作但恰恰是这些细节决定了这套系统从“能跑”到“好用”的距离。我在实际项目里反复调整最多的从来不是接口调用本身而是这些流程缝隙里的用户体验。