企业微信API消息推送实战:从access_token到消息发送的完整指南

发布时间:2026/9/16 22:57:10
企业微信API消息推送实战:从access_token到消息发送的完整指南 先说个背景。最近我给团队内部的一个监控告警系统接上了企业微信API消息推送目标很朴素服务一有异常第一时间把告警消息推到相关同事的企业微信上。这套东西表面上就是“调一个API发一条消息”真上手之后才发现里面的细节不少——凭证要配、IP白名单要加、不同消息类型参数不一样、还要核对返回的errcode判断是否真的发送成功。这篇把完整流程和踩坑过程一次性写明白适合第一次接触企业微信API的开发者、运维同学也适合正准备给自己的内部系统加消息通知能力的同行参考。1. 整体设计先想清楚消息要推给谁、用什么方式推1.1 场景拆解什么情况下会用到企业微信消息推送企业微信API消息推送简单说就是通过调用企业微信服务端接口让自建应用把消息主动发给企业内部的成员。日常接触最多的场景有三类。第一类是监控告警通知。服务器CPU飙高、接口响应变慢、订单队列积压这些事件一旦发生系统需要立刻触达负责人。大多数公司不一定有全天盯着监控大屏的运维告警消息主动推到手机上反而是最快的方式。第二类是业务流程提醒。比如审批通过、工单被分配、任务超时未处理这些消息如果不主动推送很容易被埋没在邮件里。接入企业微信API之后系统可以在关键节点自动发一条通知给对应员工。第三类是定时报表推送。每天早晨把前一天的运营数据汇总成图文卡片发给管理层省去手动查询报表的功夫。我自己做的是这几种场景的集合把内部的监控平台、任务系统都接到企业微信上用统一的推送服务管理所有消息。如果你想接入的也是这种内部通知类场景这篇内容可以直接照搬。1.2 方案选型自建应用推送和群机器人Webhook怎么选真正开始做之前不少人会纠结一个问题企业微信消息推送到底用自建应用还是群机器人这两个方案经常被混在一起但它们其实是完全不同的两条路。自建应用的消息推送走的是企业微信服务端API你需要在企业微信管理后台创建一个应用然后调用message/send接口按userid、部门或者标签把消息发给指定成员。它的优势是支持定向推送、消息撤回而且发送记录会留在应用的消息列表里。群机器人则是往一个群里添加机器人通过Webhook地址直接POST一段JSON就能发消息。它不需要复杂的鉴权流程也不限制谁能调用只要拿到Webhook地址就能发。适合那种“不需要定向、往群里扔一条通知就行”的简单场景比如把某个CI构建结果发到技术群。我画过一个简单的判断方式给你对照一下。维度自建应用消息推送群机器人Webhook目标对象指定成员、部门、标签一个固定群聊鉴权方式CorpId Secret获取access_token直接使用Webhook URL消息类型文本、markdown、图文、文件、语音等文本、markdown、图片、图文、文件等是否支持撤回支持按msgid撤回不支持适用范围业务系统定向通知群组内消息播报实现成本中等极低大多数情况下如果你的消息需要让指定的人收到而不是往群里一扔了之那就应该选自建应用方案。我这次的项目就是典型的“分人推送”告警消息要发给值班同事审批提醒要发给审批人用群机器人根本没法做定向。所以最终选了自建应用API路线接下来的内容全部围绕这个方案展开。2. 推送前的准备工作三个关键参数和一个能跑通的HTTP请求2.1 在管理后台创建自建应用拿齐CorpId、AgentId和Secret正式写代码之前需要先做一次后台配置。这一步不复杂但很多人会在这一步卡住原因是找不到入口或者不知道去哪看参数。登录企业微信管理后台路径是“应用管理-应用-自建”点击“创建应用”。创建的时候会让你上传应用Logo、填写应用名称然后选择可见范围。可见范围直接影响后面按部门、标签推送时的成员集合建议先把范围设置到位省得后面推送时发现有些同事怎么也收不到。创建完成后你会在应用详情页看到两个关键信息AgentId和Secret。AgentId是应用的唯一标识后面调用任何API都需要带上Secret是应用的密钥相当于这个应用调用API的密码。另外还需要CorpId这个东西在“我的企业-企业信息”里是一个企业级的唯一身份标识。这三个参数就是后续所有API调用的身份证缺一个都跑不通。有一个容易被忽略的细节创建完应用之后需要在“企业微信管理后台-应用管理-自建应用-API接收消息”里配置接收消息的URL吗如果只是做消息推送不要求接收事件回调那这一步可以跳过。但如果后面需要接收用户的回复消息或者事件通知就必须配置回调URL并通过验证。我这个项目初期只做单向推送所以没有配回调省了不少事。如果你用的是Linux服务器做定时任务或服务端调用这个流程同样适用。企业微信API不区分操作系统只要能发起HTTPS请求就行Python、Go、Java、Shell脚本都可以。我自己是在Linux服务器上用Python脚本做推送配合crontab实现定时告警整个过程没有遇到兼容性问题。2.2 获取并缓存Access_token别让服务被限流拿到三个参数之后第一件要做的事是获取access_token。这个token是调用所有企业微信API的通行证获取接口是这个GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的CorpIdcorpsecret你的Secret用Python的requests库写的话一行代码就能拿回来import requests corpid 你的CorpId secret 你的Secret url fhttps://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid{corpid}corpsecret{secret} resp requests.get(url).json() if resp.get(errcode) 0: access_token resp[access_token] print(token获取成功:, access_token) else: print(获取失败:, resp)这里就有一个坑了access_token的有效期是7200秒也就是两个小时。如果你每次发消息都现取一次token短时间内频繁调用会触发企业微信接口的频控限制返回类似“1101”或“45009”的错误。正确做法是第一次取到token后缓存下来快到有效期了再重新获取。缓存方案不用上Redis存内存或者本地文件都可以。比如在Flask或FastAPI应用里用一个全局变量加时间戳import time import requests _token None _token_expire_time 0 def get_access_token(): global _token, _token_expire_time if _token and time.time() _token_expire_time - 60: return _token url fhttps://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid{corpid}corpsecret{secret} resp requests.get(url).json() if resp.get(errcode) 0: _token resp[access_token] _token_expire_time time.time() resp[expires_in] return _token raise Exception(f获取token失败: {resp})这里我预留了60秒的提前量避免token刚好过期边缘的时候请求失败。这个习惯在对接其他开放API时也通用token类的凭证尽量提前几十秒刷新千万不要卡点。3. 消息推送的核心实现从文本消息到多媒体消息3.1 发送文本消息从参数构造到成功判定拿到access_token之后就可以调用发送消息的接口了。发送接口是POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_tokenACCESS_TOKEN请求体是JSON格式核心字段包括touser接收成员的userid列表多个用竖线分隔比如zhangsan|lisitoparty接收部门的部门ID列表totag接收标签的标签ID列表msgtype消息类型这里固定为textagentid自建应用的AgentIdtext文本内容对象里面放content字段safe是否为保密消息0表示否一个完整的文本消息请求体大概长这样{ touser: zhangsan|lisi, msgtype: text, agentid: 1000002, text: { content: 服务告警订单处理队列积压超过500条请及时处理。 }, safe: 0 }注意一个细节touser、toparty、totag这三个字段至少填一个但不能三个都留空。如果同时指定多个取的是并集而不是交集也就是说这几种目标范围内的人都会收到。实际项目中我一般只指定touser因为在我们的场景里就是要推给指定的几个值班人用部门推送反而容易误伤。发送之后企业微信会返回一个JSON响应关键看errcode字段{ errcode: 0, errmsg: ok, msgid: xxxxx }当errcode为0时说明企业微信已经接收了这条消息请求返回的msgid是这条消息的唯一标识。很多人以为errcode为0就代表消息一定送到对方手机上了其实不是这样。errcode为0只说明你的请求被服务端接受并进入下发流程如果对方不是企业微信活跃用户、或者该成员不在应用可见范围内仍然可能收不到。判断是否真正发送成功需要在后续通过message/get_statistics之类的统计接口或者用户的阅读数据来进一步确认但大多数内部通知场景下errcode为0已经足够作为正常下发的依据。如果你需要批量发送给一群人企业微信提供了异步发送的接口叫message/send的批量版本可以通过传入touser包含多个userid一次性发给最多1000人。如果人数更多就要分批调用并且注意频控限制。我写了一个简单的封装函数按批次拆分并间隔发送避免一次发太多触发限流def send_text_message(user_list, content, batch_size500): access_token get_access_token() send_url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{access_token} for i in range(0, len(user_list), batch_size): batch user_list[i:ibatch_size] payload { touser: |.join(batch), msgtype: text, agentid: agentid, text: {content: content}, safe: 0 } resp requests.post(send_url, jsonpayload).json() print(f批次{i//batch_size 1}发送结果:, resp) time.sleep(1)这里的间隔1秒是我根据实际测试得出的保守值批量发送时保持一定间隔更能避开频控限制。3.2 进阶消息类型markdown、文本卡片与图文消息怎么构造文本消息是最基础的但实际项目里用得更多的往往是markdown消息、文本卡片和图文消息因为它们的信息呈现效果明显更好。markdown消息企业微信客户端原生支持markdown渲染所以你可以用它来组织带标题、加粗、引用、列表的富文本内容。发送方法就是在msgtype里填markdown然后markdown对象里放content字段。{ touser: zhangsan, msgtype: markdown, agentid: 1000002, markdown: { content: ## 告警通知\nfont color\warning\订单处理异常/font\n 队列积压数量500\n 触发时间10:32:15\n请[点击查看详情](https://your-system.example.com/alerts) } }企业微信的markdown语法比较受限支持加粗、font colorinfo、font colorcomment、font colorwarning这类快捷颜色标签也支持引用块和链接。但不支持完整的HTML渲染复杂的表格、代码块可能显示异常。所以如果你要发的内容结构比较复杂建议采用文本卡片。文本卡片消息这种消息在手机端展示为一个卡片样式有标题、描述和跳转链接非常适合做审批提醒、工单分配这类需要用户点击处理的场景。{ touser: zhangsan, msgtype: textcard, agentid: 1000002, textcard: { title: 你有新的审批待处理, description: 申请人李四\n类型请假审批\n时间明天上午9:00, url: https://your-system.example.com/approval/12345, btntxt: 去处理 } }文本卡片有个好处可以附带跳转链接用户在卡片上点一下就能跳转到你的系统H5页面进行操作。这个特性在做审批、工单、任务处理类消息时非常实用。但注意企业微信的域名校验规则对卡片跳转链接有要求——作为自建应用发送图文或卡片消息时如果链接需要企业微信客户端内置浏览器打开通常要求跳转域名通过“企业微信管理后台-应用管理-自建应用-网页授权及JS-SDK”里的可信域名校验。如果域名没有备案或者没有通过校验链接很可能打不开或被拦截。我初期就是直接丢了一个内网IP地址进url字段结果同事点击后始终跳转不了后来改成已备案域名并通过校验才解决。图文消息news适合发送一条带封面图、标题、摘要和链接的富媒体卡片。这是运营场景里最常用的消息类型。{ touser: zhangsan, msgtype: news, agentid: 1000002, news: { articles: [ { title: 日报昨日订单量创新高, description: 昨日订单量突破5万单环比增长12%, url: https://your-system.example.com/daily/20250101, picurl: https://your-system.example.com/images/cover.png } ] } }图片和文件消息相对简单分别传图片资源的media_id和文件的media_id而media_id需要先通过media/upload接口上传素材获取。如果你经常发图片或文件建议把上传素材和发送消息做成两个独立函数不然后面复用会特别痛苦。4. 实操中最容易踩的坑权限校验、频控限制与消息撤回4.1 IP白名单没配置报错会让你一脸懵我一开始在本地调试的时候一切正常代码搬到服务器上后突然就开始报错了。报错信息是这样的{errcode: 60020, errmsg: not allow to access from your ip}这个报错的原因非常明确企业微信自建应用默认开启了IP白名单校验只有配置在白名单里的IP地址才能调用该应用的API。我当时在本地调试是因为没有开白名单校验或者本地IP恰好被允许但服务器IP不在白名单里所以直接被拒绝了。解决办法也很简单在企业微信管理后台“应用管理-自建应用-企业可信IP”里把应用所在服务器的公网IP添加进去。如果你是动态IP或者使用云函数这类无法固定出口IP的场景需要确保出口IP稳定或者考虑在固定IP的服务器上做一层代理转发。这个点看起来不起眼但影响非常大。很多人在测试环境Push通之后一到生产环境就被60020拦住第一反应是以为代码写错了排查半天才发现是IP白名单的问题。所以我的建议是服务器部署前先把出口公网IP查出来配好不然上线的那一刻就是踩坑的开始。4.2 频次限制与消息体大小批量推送前必须了解企业微信API对消息推送有比较严格的频控策略。具体数值在官方文档里写得很清楚几个核心限制包括每个应用对同一个成员的主动消息发送频率默认是每个成员每分钟不超过500条虽然正常业务用不到这么大量但批量通知场景要注意。每个应用的调用次数限制和企业的规模及认证状态有关。消息体的content字段文本内容长度一般建议控制在2048字节以内超出部分可能被截断。我遇到过的最典型问题是在做全员通知的时候一次性把几千人的userid放到一个请求里发送结果返回了45009接口调用超过频率限制或40058参数不合法。后来改成每500人一个批次、逐批发送并加上延时问题就消失了。批量推送场景下建议写一个重试机制对返回45009的请求等待60秒后重试对40058这类参数错误不要盲目重试先检查代码。还有一个小细节touser字段如果传了不存在的userid接口不会整体报错而是返回errcode为0但带上一个invaliduser字段列出无效的成员ID。信息很隐蔽如果你不打印完整响应根本不会发现有一部分人其实没收到消息。所以批量发送的时候一定要检查响应里的invaliduser字段。4.3 发送成功不代表用户已读消息撤回也有讲究正如前面所说errcode0只能证明企业微信服务端接受了下发指令不能证明用户已经看到消息。企业微信提供消息撤回接口调用方式如下POST https://qyapi.weixin.qq.com/cgi-bin/message/recall?access_tokenACCESS_TOKEN请求体里填入msgid即可{ msgid: xxxxx }我实际使用中发现消息撤回有一定的时效限制官方说明是默认撤回时间应该是消息发出后的一定时间内不同版本可能会调整。如果消息已经发出很久撤回会失败。所以在设计系统时如果要给用户提供“撤销发送”的功能要趁早调用撤回接口并且把撤回也纳入日志记录。我还踩过一个跟“确认是否发送成功”有关的坑。有一次系统报警接口返回errcode0但值班同事说没收到消息。后来一查是应用可见范围没包含那位同事的部门。errcode0并不代表消息一定会被所有目标用户收到应用可见范围、成员是否激活企业微信、是否开启了免打扰模式都会影响最终的触达。所以重要通知类的推送我一律在系统里增加一个“已读回执”统计能力来自企业微信的“消息推送-接收消息”相关的数据接口用于事后确认多少人真正点开了消息。5. 常见问题与排查技巧实录5.1 高频报错速查表我把这段时间实际遇到的错误码整理成了一个速查表方便你对接时快速定位问题。errcode含义常见原因解决办法0请求成功无无40001access_token无效token过期或错误重新获取access_token40014不合法的access_token缓存混乱、token取错清缓存重新获取检查Secret42001access_token过期超过7200秒重新获取token40003不合法的UserIDtouser里存在错误ID校验用户ID注意区分大小写40058参数不合法touser、toparty等请求体格式错误按文档检查JSON格式60020来源IP不在白名单服务器IP未配置在管理后台添加可信IP45009超过频率限制短时间内大量调用分批、限速、延时重试300001无效的媒体IDmedia_id错误或素材过期重新上传素材获取新的media_id40098消息内容过长content超长压缩或分段发送这里面加密界最常见的是60020和45009。60020多半是部署问题45009则是设计问题——推送系统的调用频率没有控制好。这里重点强调一下如果你的推送服务架构是“多个业务模块共用一个应用凭证”一定要在最上层做统一的频控和缓存否则几个模块同时调用很容易触发45009。5.2 排查思路从慢请求到消息丢失的定位办法排查企业微信API问题时我的常规套路是分层定位先看请求是否到达、再看响应是否正确、最后看触达是否有问题。具体步骤是用curl直接调用一次API排除代码层问题。比如获取tokencurl https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的CorpIdcorpsecret你的Secret这一步能快速区分是网络问题、DNS解析问题还是鉴权问题。检查响应日志。我通常会记录完整的请求体和响应体尤其是errcode、msgid、invaliduser这几个字段。如果只记录一个状态码很多问题根本没法定位。如果接口返回正常但用户没收到去后台看应用可见范围和成员状态。在管理后台的“成员与部门”里查看是否包含目标用户再确认对方是否激活了企业微信。还有一类比较少见的坑消息内容触发了企业微信的内容安全校验。如果文本内容包含一些敏感关键词可能返回400或errcode非0的“content exists risk”类错误。这在做外部系统对接时要特别小心比如从外部采集的消息直接推到企业微信很容易踩中内容安全红线。解决方式是先做一层内容过滤再决定是否推送。调试时还有一个很实用的小技巧先用一个测试成员的userid试发确认链路通了再批量发送。我每次改动消息模板或者参数结构的时候都会先发给自己确认手机端渲染效果没问题再切到正式推送逻辑。这个方法帮我避免了好几次“消息发出去了但格式全乱”的尴尬。6. 个人落地经验与扩展建议整个项目从零开始到稳定运行我最深的感受是企业微信API消息推送的官方文档其实写得比较全但真正耗时间的不是看懂接口而是处理那些文档之外的环境问题。IP白名单、token缓存、可见范围、频控策略每一个看起来都是小事叠加在一起就会变成拦路虎。有一个经验可以分享如果你的团队用的是Linux服务器而且网络环境相对简单直接写Python脚本配合crontab是最快的落地方式。但如果后续消息量变大、接入的业务系统变多建议把推送模块独立成一个服务提供统一的HTTP接口给其他系统调用这样所有凭证管理和频控逻辑只需要维护一份。扩展方向上企业微信API还能做很多事接收用户回复的消息、获取用户信息、管理通讯录、发送应用消息到外部联系人。我现在的推送服务只是用了最基础的消息发送能力下一步准备接入素材上传和打卡数据统计把更多的办公流程自动化。这个接口值得好好研究越往后用越觉得空间很大。