
简介面向企业信息化管理人员、系统集成工程师及办公自动化开发者这份资料围绕扣子机器人在飞书与钉钉平台的部署主线给出从平台注册、应用创建、权限管理、回调设置到工作流搭建、知识库关联、发布测试的完整操作指引并针对企业内网环境中的网络访问限制、数据安全、稳定性与性能优化等关键难点展开说明。资源包为单个docx文档大小约33KB内容以步骤化文字教程为主便于部署时对照阅读、标记与复用。已有140人学习下载。文中对消息发送失败、权限不足、网络连接异常等高频故障提供了系统性排查思路与解决方案同时强调了网络配置、权限管理和安全策略的预判性设置能帮助读者减少试错成本适合希望在企业内部快速落地智能问答、办公自动化及跨部门协作机器人的团队参考。1. 智能办公机器人落地的第一个坑开放平台远比机器人本身复杂做企业内部智能助手最容易被低估的不是大模型能力而是飞书、钉钉这两家开放平台本身的规则差异。同样一个扣子机器人发布到飞书要过事件订阅、权限申请、应用发布审核发布到钉钉又要面对企业内部机器人、Stream模式、加签验签这一套完全不同的协议。我在给一家中型公司做内部知识库机器人时光是把两个平台的机器人都调通就花了三个工作日其中至少一半时间在跟回调地址、Token校验和权限点较劲。这份资源解决的是“扣子平台编排好的智能体如何稳定地挂到飞书和钉钉上”这件事。它覆盖了从创建扣子工作流、配置飞书自建应用、配置钉钉企业内部机器人到消息收发调试、多维表格读写、卡片交互回调的全流程。适合两类人一类是公司内部做IT支持、想给团队加一个能查数据、能跑审批流程的机器人的工程师另一类是接外包项目、需要在两个办公平台上交付智能助手的开发者。如果你以为把扣子机器人发布到飞书钉钉就是点几个按钮的事那这份资源能帮你把预期拉回现实。2. 扣子平台的关键机制工作流、插件与发布渠道的解耦逻辑2.1 扣子机器人的本质一个被封装成HTTP服务的Agent扣子平台Coze本身提供了完整的智能体编排能力包括人设与回复逻辑、工作流、知识库、插件、触发器、数据库等模块。但很多第一次接触的人会误以为“在扣子上建了一个Bot就等于在飞书/钉钉上有了一个机器人”这个认知是错的。实际上扣子上的机器人只是你在扣子云端定义的一个逻辑体它自身不负责处理办公平台的消息推送和回调。飞书或钉钉收到用户消息后会先触发各自开放平台的事件订阅或Webhook机制再把消息内容通过扣子开放API转发给你的智能体拿到回复后再调用飞书/钉钉的消息发送接口把结果发回去。这个链路意味着你要先理解扣子机器人的“发布”机制。在扣子控制台每个Bot都有一个“发布”按钮发布时需要选择一个渠道比如“API服务”或“微信公众号”。其中飞书、钉钉这两个渠道在扣子平台上是原生支持的这是它比自建Agent服务省事的地方。但你仍然需要去飞书开放平台或钉钉开发者后台创建对应的应用拿到App ID、App Secret、验证Token、加密Key再把扣子发布配置里的回调地址填到开放平台的事件订阅URL里。用一句话概括这层关系扣子管“大脑”开放平台管“神经”开发者管“搭桥”。这份资源的绝大部分篇幅就是在教你如何把这座桥搭稳。2.2 为什么选扣子而不是自建服务状态管理与多轮对话的取舍企业在二选一时经常纠结是用扣子这种低代码Agent平台还是直接基于大模型API自建一个机器人服务我的看法是看你的对话复杂度。如果你的机器人只需要“查一下排班表”“找一下制度文档”这种单轮或简单多轮对话扣子平台的工作流编辑器和知识库组件能让你在半天内搭出一个可用的版本。但如果你的业务涉及复杂的状态机比如审批流中要等待用户上传多个附件、根据上一轮结果动态生成下一步选项那么扣子工作流的节点编排会更难维护自建服务的上下文管理反而更灵活。扣子平台还有一个值得注意的特性消息上下文是托管在平台侧的。你在工作流里配置的“历史消息轮数”会影响机器人对多轮对话的感知能力。实际操作中我一般会把“最多记住最近10轮”作为一个安全值设太大会导致Token消耗飙升设太小则用户说“刚才那个”时机器人会断片。发布到飞书、钉钉时要注意一个关键差异飞书支持长连接模式WebSocket钉钉企业内部机器人现在推广Stream模式。这两种模式都不需要公网回调地址特别适合企业内网环境。而老式的Webhook模式要求你的服务器能接收飞书/钉钉主动推送的HTTPS请求这就意味着你必须有一个公网可达的HTTPS端点或者在内网网关做反向代理。在扣子平台上发布时选择“API服务”方式扣子会生成一个公网可访问的Bot URL飞书事件订阅把消息推进这个URL扣子再响应回来。如果企业内网策略禁止出方向访问外部服务那这条链路就会断掉这是我在实际部署中遇到过的第一个大坑。2.3 扣子发布配置参数速查在扣子Bot的“发布”页面里配置飞书和钉钉渠道时有几个关键参数需要你提前准备好。下面这个表是我每次部署前都会先列出来的清单参数飞书渠道钉钉渠道说明应用凭证App ID App SecretAppKey AppSecret在各自开发者后台创建企业内部应用后获取事件订阅URL扣子生成的回调地址钉钉Stream模式无需URLWebhook模式需要两者都是HTTPS前缀验证Token由你自定义并填写到双方后台钉钉为加签密钥Secret用于校验消息来源加密方式AES加密密钥可开启加签时间戳nonce密钥SHA256飞书推荐开启加密钉钉加签必配权限配置读消息、发消息、读多维表格、上传资源机器人发送消息、读取消息、日程读写漏配权限是最常见的“不响应”原因以上参数在扣子发布成功后会生成一个“智能体ID”或“Bot ID”这个ID在后续调试API时要用到。下一章我会以飞书为例把从创建应用到发送第一条消息的完整链路展开。3. 飞书机器人全流程部署从自建应用到多维表格读写3.1 飞书开放平台侧配置自建应用的三步走飞书的后台逻辑是“应用”为中心。你要在飞书开放平台创建一个“企业自建应用”然后在这个应用里启用机器人能力、配置事件订阅、申请权限最后发布版本。只有审核通过的应用机器人才能真正在飞书工作区里被搜索到并私聊或群聊。第一步是创建应用。进入飞书开放平台点击“创建企业自建应用”填入应用名称和描述。这个名称会显示在飞书工作台的“机器人”列表里建议直接用业务名比如“订单查询助手”“人事制度助手”别用“扣子测试Bot”这类名字因为审核时会被要求补充用途说明。创建完成后在“凭证与基础信息”页面复制App ID和App Secret这两个值稍后要填到扣子发布配置里。第二步是开启机器人能力。在应用功能里找到“机器人”打开开关。这里有个细节飞书的机器人分为“应用机器人”和“回调机器人”两种使用形态企业自建应用默认启用应用机器人它会以“应用名”的身份出现在聊天里。你随后要做的是在事件订阅页面里添加一个事件名叫“接收消息”。如果不添加这个事件飞书根本不会把用户发来的消息推送到你的回调地址机器人就会一直沉默。第三步是申请权限。飞书对权限点的粒度划分非常细。发送消息对应im:message:send_as_bot读取用户发给单聊机器人的消息对应im:message:readonly读取多维表格对应bitable:app:readonly。常见做法是先在权限管理里搜索“机器人”和“多维表格”把读写相关的权限全部勾上再在创建版本后提交审核。企业自建应用的审核一般由企业管理员在管理后台处理如果管理员是自己直接在开发者后台的“版本管理与发布”页签提交并审核通过整个过程不需要飞书官方介入这一点比钉钉要轻量。3.2 扣子侧发布配置把回调地址交给飞书回到扣子控制台进入你的Bot点击“发布”渠道选择“飞书”。扣子会要求你填写两部分信息一部分是飞书应用的App ID和App Secret另一部分是你自定义的验证Token和加密Key。填完后扣子会生成一个回调URL形如https://www.coze.cn/api/feishu/callback把这个URL完整复制到飞书开放平台的“事件订阅-请求地址”里。飞书为了验证这个回调地址是你的会立刻向它发一个带Challenge参数URL验证请求的GET请求。你需要确保扣子那边的回调端点是正常运行的。在扣子上只要你填写的Token和飞书后台的验证Token一致这个Challenge校验会自动通过。这里最容易翻车的地方是你在飞书后台填写的验证Token和你在扣子发布配置里填写的Token必须一字不差。Token不一致时飞书会提示“验证URL失败”不会给你任何额外的排查日志。验证通过后把“事件订阅”里的加密策略打开。飞书的事件推送支持明文和加密两种模式扣子侧会自动适配但建议你把飞书后台的“加密策略”设为“使用加密”并在扣子发布配置里填入相同的加密Key。这个Key要求是16位或24位或32位的字符串随便生成一段即可例如abcdef1234567890这样的长度必须合规。加密模式下飞书推送的JSON里会多出一个encrypt字段解密后才是真实消息体不配置的话扣子解析不到消息内容表现为“事件订阅验证成功但机器人收不到消息”。3.3 用一段Python验证飞书消息链路是否打通配置完双方后台后我习惯先不急着在飞书里发消息而是用飞书开放API做一次主动消息推送验证App ID和App Secret的有效性以及机器人是否具备发消息权限。下面是我平时用来做“发消息”链路自检的一段脚本import json import requests app_id cli_xxxxxxxxxxxxxxxx app_secret xxxxxxxxxxxxxxxxxxxxxxxxxxxxx # 步骤1获取 tenant_access_token url https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal resp requests.post(url, json{ app_id: app_id, app_secret: app_secret }) resp.raise_for_status() token resp.json().get(tenant_access_token) if not token: print(获取token失败检查App Secret是否正确) exit(1) # 步骤2把消息主动推给某个用户的单聊 chat_id ou_xxxxxxxx # 对应某个用户的open_id send_url https://open.feishu.cn/open-apis/im/v1/messages?receive_id_typeopen_id headers { Authorization: fBearer {token}, Content-Type: application/json } payload { receive_id: chat_id, msg_type: text, content: json.dumps({text: 连通性测试如果你是手动看到这条消息说明机器人链路正常。}) } resp2 requests.post(send_url, headersheaders, jsonpayload) result resp2.json() if result.get(code) 0: print(发送成功msg_id:, result[data][message_id]) else: print(发送失败错误码:, result.get(code), result.get(msg))这段脚本里最关键的是获取tenant_access_token的接口路径。它跟获取user_access_token的接口不同后者需要OAuth授权跳转而机器人调用API只靠App ID和App Secret即可换到tenant_access_token。换到Token后发消息接口要求指定receive_id_type常用的是open_id或user_id。从飞书管理后台的“成员管理”里能查每个用户的open_id但更常见的做法是在事件订阅里把用户的sender_id记录下来。这个链路验证通过后再回到飞书客户端里直接给机器人发消息看扣子是否正常回复。如果客户端消息不走问题大概率出在事件订阅的“事件类型”没勾选或者机器人能力开关没打开。下一步我们要去处理那些“配置看起来都对但就是不响应”的诡异情况这在钉钉侧表现得更明显。4. 钉钉机器人部署实战Stream模式与卡片消息的坑4.1 钉钉机器人的三种形态你该选哪一种钉钉的开放平台跟飞书有个显著区别它把“机器人”拆成了至少三种形态选错形态会让你后面每一步都走不顺。第一种是“企业内部机器人”也叫“应用机器人”。你在钉钉开发者后台创建一个企业内部应用然后在“机器人”页签里启用机器人配置消息接收地址。这种机器人可以出现在单聊和群聊里支持在消息卡片上添加按钮交互是部署扣子智能助手的推荐形态。第二种是“自定义机器人”通过群聊里的“智能群助手”添加本质上是一个Webhook机器人。它只能往群里发消息收不了用户消息所以扣子智能助手如果要实现“问答”而不是“定时推送”用这个形态根本跑不通。很多业务方上来就说“我们在钉钉群里加一个自定义机器人就行”如果你做的是客服或问答场景一定要在第一轮打消这个想法。第三种是Stream模式机器人。这是新版本钉钉主推的形态机器人不需要配置公网回调URL而是通过长连接主动连上钉钉服务器接收消息。这个模式在扣子平台上也支持。如果你所在企业网络环境里没有可对外的HTTPS端口Stream模式是唯一行得通的路。4.2 钉钉企业内部机器人配置加签、Stream与权限以企业内部机器人为例完整的配置流程是这样。先在钉钉开放平台创建“企业内部应用”拿到AppKey和AppSecret。然后在“机器人”页签中机器人协议建议选“Stream模式”这样不需要填写消息接收地址扣子侧的发布配置也会相应简化。如果你坚持用Webhook模式那就要在机器人配置里填一个消息接收地址并打开“消息加签”开关拿到一段加签密钥。钉钉的事件推送会在HTTP头里带timestamp和sign两个字段签名算法是Base64(HmacSHA256(把timestamp \n secret作为待签名字符串))。扣子平台处理钉钉Webhook时已经内置了验签逻辑你不需要自己实现但如果你打算在自建服务里调试钉钉回调这一段就是检验消息是否合法的关键。权限方面钉钉的命名是“权限点”你需要在应用的能力配置里至少启用“消息接收”和“发送消息”。如果你的机器人要读多维表格——钉钉体系里叫“多维表”同一份权限需要单独申请。这里有个跟飞书不同的规则钉钉的权限点除基础沟通类权限外多数在发布后需等待官方审核审核时间通常为半天到两个工作日。企业内网项目如果急着上线建议提前申报而不是等到部署当天。4.3 用Python验证钉钉机器人Stream模式消息收发对于Stream模式的钉钉机器人你用Requests直接调API就能体验完整的“收消息-回消息”闭环。下面这段脚本是一个最小可运行的钉钉Stream模式示例核心思路是应用启动后用WebSocket连接到钉钉的消息通道监听robot类型的消息然后调用发送消息API回复固定内容。import requests import json app_key ding_xxxxxx app_secret xxxxxxxxxxxxxxxx # 获取企业内部应用的access_token token_url https://api.dingtalk.com/v1.0/oauth2/accessToken resp requests.post(token_url, json{ appKey: app_key, appSecret: app_secret }) token resp.json()[accessToken] # 向指定会话发送消息 # conversationId 可以是单聊会话ID也可以是群会话ID send_url https://api.dingtalk.com/v1.0/robot/groupMessages/send headers { x-acs-dingtalk-access-token: token, Content-Type: application/json } payload { msgKey: sampleText, msgParam: json.dumps({ content: 钉钉机器人连通性测试收到这条消息说明应用凭证可用。 }), openConversationId: cid_xxxxxxxxxxxxxx # 群会话的conversationId } resp2 requests.post(send_url, headersheaders, jsonpayload) if resp2.json().get(code) 0: print(发送成功) else: print(发送失败:, resp2.text)注意这段代码里我用的是openConversationId而不是老的conversationId这是钉钉开放平台升级后的字段名。在群聊场景下这个值的获取方式是先让机器人加入某个群再通过事件订阅里的conversationId字段拿到或者从群里发一条消息后从回调日志里提取。钉钉和飞书在消息类型上的差异也值得注意飞书的content是一个JSON字符串而钉钉的msgParam也是JSON字符串但msgKey决定了解析方式。比如sampleText对应纯文本sampleMarkdown对应MarkdownsampleActionCard对应带按钮的卡片。如果你要做的是多轮对话式助手建议直接用sampleText就好因为扣子返回的答案大多是文本。但如果你要把机器人的输出包装成“连续对话卡片”就得在扣子工作流的最后一步加一个“代码执行”节点把文本转成钉钉卡片所要求的JSON结构。4.4 钉钉卡片消息按钮交互的回调闭环钉钉最让人头疼的是卡片消息里的按钮回调节点。流程是机器人发一张带“同意”“拒绝”按钮的卡片用户点按钮钉钉把按钮点击事件回调到你的服务或扣子工作流的“事件接收”节点。这个回调的URL需要跟消息接收地址保持一致并且在钉钉“事件与回调”页面里单独注册一个事件类型叫“机器人卡片回调”。在扣子侧处理这个回调时我一般会在工作流的开头加一个“条件判断”节点判断messageType是text还是action_card。如果是action_card就从消息体里取用户点击按钮的value字段再根据value值决定下一步走哪个分支。这个字段在钉钉推送的JSON里叫params结构是一个字典业务自定义的键值都在里面。做这个设计时最需要留神的是卡片回调的消息体和普通文本消息结构完全不一样。如果只按普通消息的解析逻辑来处理你会发现用户点了按钮后机器人没有任何反应但抠出来的日志明明显示消息已收到。这个坑十个人里有八个会踩我们在第5章专门列出来。5. 部署避坑指南飞书钉钉调试中最常见的五个翻车现场5.1 事件订阅验证成功但机器人不回复任何消息现象在飞书后台点击验证URL提示“URL验证通过”但在飞书客户端给机器人发消息它完全沉默扣子后台运行日志也空无一物。原因事件订阅验证成功只代表你的回调地址能被飞书访问到并不代表飞书会把消息事件推给你。你没有在“事件订阅”页面里添加“接收消息”事件类型飞书默认只推送你订阅过的事件。钉钉侧类似的坑是你配置了Stream模式但没有把“机器人消息”事件关联到应用上同样导致消息进不来。解决飞书开放平台进入应用的事件订阅页面点击“添加事件”在“消息与群组”分类下勾选“接收消息im.message.receive_v1”。保存后重新执行一次“推送调试”飞书会模拟一条消息事件推送到你的回调地址。钉钉侧在应用开发的“事件与回调”里注册“机器人消息回调”并确认回调方式与你在机器人协议里选择的模式保持一致。5.2 飞书后台提示“token校验失败”但你的Token明明没填错现象配置扣子发布渠道时把扣子生成的验证Token填到飞书后台点击验证时提示校验失败。原因飞书的验证Token机制分为两层。第一层是飞书后台自己生成的“验证Token”字段当事件订阅开启加密后飞书实际上用的是“Encrypt Key”做签名校验而不是那个明文Token。第二层是扣子侧在发布渠道配置时填写的自定义Token它只参与扣子到飞书之间的连接校验。很多开发者把扣子填的Token复制到了飞书后台的“Encrypt Key”字段里导致校验失败。解决飞书后台的“验证Token”保持默认值不要动。“Encrypt Key”填一个你自己生成的16/24/32位字符串。扣子平台发布配置里填的Token与飞书后台一致复制同样的字符串加密Key也保持一致。最终以“Encrypt Key”为准不是“验证Token”。5.3 钉钉加签一直失败你拼的签名字符串顺序不对现象在钉钉开发者后台测试回调时后台提示“签名校验失败请检查签名是否正确”。原因钉钉Webhook的签名算法要求先取timestamp和nonce然后把它们与加签密钥拼接成一个字符串具体格式是timestamp \n secret作为待签名字符串再进行HMAC-SHA256运算最后Base64编码。但钉钉文档中还有另一种历史遗留的签名规则部分开发者按照老版本文档拼接了secret \n timestamp导致签名对不上。解决统一按timestamp \n secret的顺序来。如果你用扣子平台这一步不需要你手动实现但如果你是用代码调试钉钉Webhook就把GetSign函数的参数严格定义为timestamp、secret两个值。确认时间戳用的是毫秒级还是秒级——钉钉推送的timestamp是毫秒但有些SDK自带的签名工具用秒差了这一个单位签名结果完全不同。5.4 机器人能发消息但无法读取飞书多维表格数据现象机器人可以正常回复用户文本消息但当用户问到“查一下表格里的排班情况”时扣子工作流报错无权限日志里出现403。原因扣子的知识库工作流通过飞书API读取多维表格时要求应用具备bitable:app:readonly及以上权限点。你只开了消息类的权限没开多维表格权限或者开了权限但没有等待权限生效。飞书的权限点不是即时生效的需要发布新版本且管理员审批后才会在API层面放开。解决进入飞书开发者后台在“权限管理”里搜索“多维表格”勾选bitable:app:readonly和bitable:app:write然后重新创建版本并提交发布。在管理后台审核通过后等待两分钟让权限缓存刷新再在扣子工作流里测试同样的请求。这里有个窍门你先用飞书开放平台的“调试工具”直接调用一次查询表格记录API如果调试工具里返回正常那就说明App自身权限没问题问题出在扣子侧传参不正确如果调试工具都返回403那一定是权限点还没生效。5.5 钉钉卡片按钮唤起的手填地址是回调用户点完没反应现象在扣子里配好了带按钮的钉钉卡片用户点击按钮后卡片上的文字变成了“已处理”但扣子工作流没有任何反应机器人也没有后续回复。原因钉钉的卡片按钮有两种动作一种是自定义跳转URL另一种是卡片回调。你配置成跳转URL后按钮点击只会打开一个外部链接不会触发机器人逻辑。更隐蔽的问题是你在钉钉后台注册了按钮回调事件但扣子工作流没有在消息入口节点区分text和action_card两种消息类型导致回调消息被当成普通文本消息处理解析失败后静默丢弃。解决钉钉卡片消息在扣子侧的工作流入口增加一个“分支判断”节点。判断依据是消息对象里是否包含params键。如果包含说明是卡片回调从params里取出按钮对应的value再拉取当前消息的open_id具体字段名要看钉钉推送的实际JSON。这个方案有三种变体下面用一个最小示例演示其中一种message { msgtype: action_card, params: {action: approve, task_id: T1001} } if params in message: action message[params].get(action) task_id message[params].get(task_id) print(f收到卡片回调动作{action}任务编号{task_id}) # 在这里把业务状态更新到数据库再发一条新卡片 else: print(收到普通文本消息走普通问答流程)参数说明params字段只在卡片回调场景下存在普通文本消息不会携带。钉钉对卡片回调有一个额外的要求回复消息时必须在原消息conversationId上回复否则会出现“用户看到新消息但不在同一个会话线程里”的割裂现象。6. 一套工作流同时适配飞书和钉钉渠道差异封装技巧部署完成两个平台之后最常见的需求是“同一个智能助手两边都要上”。如果你直接把扣子的工作流分别发布到两个渠道会遇到一个麻烦飞书传过来的消息格式和钉钉传过来的格式在字段名上完全不同工作流里每个解析节点都要写两套逻辑维护成本翻倍。我目前比较推荐的做法是在扣子工作流里加一个“渠道适配”节点组放在消息入口之后、业务逻辑之前。这个节点组的职责就是把不同平台的消息体统一成内部标准的格式。比如飞书的event.message.content是一个JSON字符串需要先解析而钉钉的text.content是纯字符串你在适配节点里把两者都转成一个统一的{sender_id, text_content, message_type}结构。这样后面的业务节点只需要处理一种数据格式不管消息来自哪个平台逻辑都一样。具体实现可以在扣子里拖一个“代码执行”节点用Python写一个normalize_incoming_message函数。以下是我在一个项目里用过的简化版def normalize(message: dict, channel: str) - dict: if channel feishu: # 飞书事件订阅推送中消息内容在 event.message.content 字段 raw message[event][message][content] import json content json.loads(raw) return { sender_id: message[event][sender][sender_id][open_id], text_content: content.get(text, ), message_type: text } elif channel dingtalk: # 钉钉消息内容在 text.content 字段纯字符串 return { sender_id: message[senderId], text_content: message[text].get(content, ), message_type: text } else: raise ValueError(f未知渠道: {channel})注意这里有个隐藏细节飞书事件订阅推送过来的JSON有时候是加密的你需要先decrypt再执行上述解析而钉钉走Stream模式推送的消息时消息体已经是明文。为了统一处理最好在扣子发布配置里查看一下当前渠道是否开启加密如果开启就在适配节点之前单独加一个“解密预处理器”。验证工作流是否适配成功的方法也很简单在飞书里发一句测试消息再在钉钉里发同一句看两侧的回复是否一致。如果一致说明逻辑层已经统一如果一侧正常一侧报错先查看扣子工作流日志里报错节点的输入比较两个渠道消息体在那一节点的实际结构差异——90%的情况是某个字段名为空或类型不一致。我能给你的最后一条经验是别急着把扣子上的旧版本覆盖发布。每当你改完一个渠道的配置并测试通过后在扣子发布页面保留上一版本作为“回退点”。这个习惯救过我一次——当时我改钉钉卡片的回调逻辑时不小心动了飞书的发布配置导致飞书机器人崩了我直接回退到上一个版本十分钟内恢复正常。从那以后我每次部署完一个平台都要先手动把版本号记下来再继续下一个平台的调优。希望这片笔记能帮你把两个平台的部署路径一次走通。本文还有配套的精品资源点击获取