尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

企业微信文本消息接口全解析:从发送到接收回调的实战指南

企业微信文本消息接口全解析:从发送到接收回调的实战指南 老板丢过来一句话“把咱们服务器的告警接到企业微信里出问题就在群里喊一声。”这种需求我在不同公司接过五六回看起来简单真动手才发现光“把文本消息推到企业微信”这一个动作背后就藏着好几条完全不同的通道。用错了接口、配错了参数消息要么发不出去要么连“谁发的、发给谁”都拎不清。这篇文章就以企业微信二次开发里最基础的文本消息接口为主线把前置概念、调用流程、接收回调、注意事项一次讲透。内容偏实战适合第一次接触企微接口的开发者、企业内部IT和做系统集成的朋友照着操作能少走很多弯路。1. 动手之前先分清三套消息通道很多人第一次查企业微信开发文档都会被各种接口名绕晕。其实你只要记住一句话企业微信的“消息”不是一个笼统的概念它至少分三套完全独立的体系各自有各自的凭证、接口和限制。1.1 自建应用的“应用消息”通道这是文本消息接口最正统的走法。企业在企业微信里创建一个自建应用就能通过官方API向员工发送消息也能接收员工主动发给这个应用的消息。做系统通知、工单提醒、日报推送基本都是走这条路。这个通道的关键凭证有三个CorpID、应用Secret、应用AgentId。CorpID是企业的唯一身份标识在管理后台“我的企业-企业信息”里能看到是一个以ww开头的字符串Secret相当于这个应用的密码在应用详情页获取AgentId是应用在企业里的数字编号同样是应用详情页上的一串数字。三个参数缺一不可而且必须属于同一个应用混用就会出现各种莫名其妙的报错。这个通道最大的优势是能精准指定接收人你可以按成员账号UserID发、按部门ID发、按标签ID发或者干脆不指定默认发给应用可见范围内的所有人。文本内容还支持自定义超链接、换行排版、保密模式等能力。后面章节的调用流程全部围绕这条通道展开。1.2 群机器人的Webhook通道如果你只是想把消息推到某个群里又不想建应用、不想拿access_token那就用群机器人Webhook。在任意企业微信群里添加一个“群机器人”就能拿到一个Webhook地址往这个地址POST一段JSON消息就会以机器人身份出现在群里。它最大的价值是轻量不用管CorpID和Secret拿到Webhook就能发。缺点是只能发不能收除非额外配置机器人的消息接收回调而且频率限制比应用消息严格。适合做群告警、组内通知、报表推送这类“单向广播”场景。1.3 客户联系/外部联系人通道还有一种常见误会想给外部客户发消息结果拿着应用消息接口去调发现touser填什么都是“成员不存在”。给外部联系人发通知属于“客户联系”体系走的是另一套接口比如企业群发、入群欢迎语凭证也是单独申请的客户联系Secret。这里先不展开但你脑子里必须绷一根弦内部应用消息、外部客户消息、群机器人消息是三个独立的分支不能混着调。下面的表格可以帮你快速做通道选型通道认证方式能接收消息吗典型场景自建应用消息CorpID Secret AgentId能通过回调系统通知、工单提醒、定向推送群机器人WebhookWebhook地址可配置回调群告警、组内通知、报表推送客户联系消息客户联系Secret受限给客户群发、入群欢迎语我为什么建议新手先从自建应用消息入手因为它最完整既能体验发送又能体验接收回调后面你要做机器人大模型问答、做H5免登联动都离不开这套基础能力。2. 文本消息发送从建应用到调通接口的完整链路这一章是全篇的主干。我会按一个正常项目的推进顺序带你从零把一条文本消息真实发出去。2.1 先在管理后台创建一个自建应用登录企业微信管理后台找到“应用管理-自建”点“创建应用”。填上应用名称、上传Logo、选择可见范围应用就建好了。创建完成后进入应用详情页能看到两个关键值AgentId和Secret。AgentId一般是个数字Secret需要点击查看首次查看要验证管理员身份。同时在“我的企业-企业信息”里把CorpID复制出来三个值放一起保存好。这里真心建议开发阶段就把可见范围设置成一个测试部门或者测试成员别选“全体成员”。否则调试的时候手一抖点了发送全公司都收到一条“这是一条测试消息”那画面太美我经历过。2.2 获取access_token决定了后续所有接口的流畅度所有调用企业微信API的请求都要先拿到一个access_token。它相当于临时门禁卡有效期7200秒2小时过期后必须重新获取。获取方式很简单一个GET请求curl https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的CorpIDcorpsecret你的Secret正常返回的JSON长这样{ errcode: 0, errmsg: ok, access_token: xxxxxx, expires_in: 7200 }很多新手一上来就是每次请求前都调一次gettoken这其实是有问题的。这个接口本身有频率限制而且高并发下每个请求都去拿一次token白白增加延迟。正确做法是缓存起来第一次获取后存到内存或Redis里记录过期时间在过期前几分钟主动刷新。我写了一个最简版本的TokenManager你直接抄就行import time import requests class TokenManager: def __init__(self, corpid, secret): self.corpid corpid self.secret secret self._token None self._expire_at 0 def get_token(self): if self._token and time.time() self._expire_at - 60: return self._token resp requests.get( https://qyapi.weixin.qq.com/cgi-bin/gettoken, params{corpid: self.corpid, corpsecret: self.secret}, timeout3 ).json() if resp.get(errcode) 0: self._token resp[access_token] self._expire_at time.time() resp[expires_in] return self._token raise RuntimeError(fgettoken failed: {resp})生产环境建议把token放到Redis里多实例部署时才不会出现“A实例刚刷新tokenB实例又去刷新一次”的情况。企微对token并发刷新虽然没卡得很死但没必要去挑战这个边界。2.3 封装文本消息发送请求拿到access_token之后发送文本消息的接口是POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_tokenACCESS_TOKEN请求体是最关键的部分{ touser: ZhangSan|LiSi, msgtype: text, agentid: 1000002, text: { content: 你的快递已到请携带工卡前往邮件中心领取。 }, safe: 0 }字段逐个说明touser接收人的UserID多个用|分隔。不传touser时可以用toparty部门ID或totag标签ID代替。如果三个都不传系统默认发给应用可见范围内所有成员这个操作要非常谨慎。msgtype固定填text这就是文本消息接口里“文本”的定义。想发markdown、图片、文件这个字段要换成对应的值。agentid你在应用详情页看到的那个数字ID填错会报错。text.content消息正文最长2048字节。safe0是普通消息1是保密消息。保密消息在客户端不能复制、转发、下载等适合发合同编号、工资条之类的内容。配套一个发送函数def send_text(token, agentid, touser, content): url https://qyapi.weixin.qq.com/cgi-bin/message/send payload { touser: touser, msgtype: text, agentid: agentid, text: {content: content}, safe: 0 } resp requests.post( url, params{access_token: token}, jsonpayload, timeout5 ).json() if resp.get(errcode) ! 0: print(发送失败:, resp) return resp有一个极其常见的问题返回码是0但成员就是没收到。绝大多数原因是touser填成了姓名或手机号。企业微信接口里的UserID是通讯录里设置的“账号”一般是个英文字符串不是中文名。去通讯录管理后台看一眼成员的账号是什么再回来填。2.4 高频返回码对照表errcode含义常见原因0成功无40001access_token无效或过期token缓存有问题或secret配置错误40014不合法的access_tokentoken拼写错误、带上了多余空格42001access_token超时缓存策略未按7200秒刷新40096agentid不匹配请求里的agentid和secret不是同一个应用60011成员不在可见范围应用可见范围没包含该成员60020访问IP不在白名单应用配置了企业可信IP来源IP不匹配45009接口调用超过频率限制gettoken太频繁或消息发送太密集301002成员不存在touser填了一个无效UserID开发阶段建议把返回的完整JSON打印出来看。很多问题从errmsg里能直接看出端倪别只看errcode就完事。3. 接收文本消息与被动回复回调这块最容易卡人很多需求不只是“发出去”还要“收回来”。比如员工在企业微信里对应用说“查一下工单状态”应用要能接到这句话再回一句结果。这就必须配置接收消息服务器也就是常说的“回调”。3.1 先过URL验证这一关在企业微信管理后台应用详情页往下拉找到“接收消息”设置会让你填三个东西URL、Token、EncodingAESKey。URL你自己服务器的接口地址必须公网可达并且要同时支持GET和POST请求。Token自己定的随机字符串用于签名校验相当于一个口令。EncodingAESKey43位字符串用于消息加解密可以直接用系统自动生成的。点保存时企业微信服务器会立刻往你的URL发一个GET请求带上四个参数msg_signature、timestamp、nonce、echostr。你的服务必须完成三步把Token、timestamp、nonce三个字符串按字典序排序拼接成一个字符串做SHA1签名结果要和msg_signature完全一致。用EncodingAESKey对echostr做AES解密得到解密后的明文。把解密后的明文原样返回给企业微信服务器。只要其中任何一步不对后台就会提示“URL验证失败”。URL验证失败是回调功能的第一大坎我见过有人卡在这里两三天。强烈建议不要手写AES解密逻辑直接用企业微信官方提供的加解密库比如Python版的WXBizMsgCrypt里面封装好了VerifyURL、DecryptMsg、EncryptMsg三个方法拿来就能用不要自己造轮子。3.2 收到文本消息后拿到的XML长什么样URL验证通过后每当有人给应用发消息企业微信服务器就会POST一个加密的XML到你的URL。解密之后一条文本消息的内容是这样xml ToUserName![CDATA[CorpID]]/ToUserName FromUserName![CDATA[ZhangSan]]/FromUserName CreateTime1348831860/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[你好]]/Content MsgId1234567890123456/MsgId AgentID1000002/AgentID /xml重点字段FromUserName发送消息成员的UserID你要知道“谁发的”就看它。Content文本消息正文。MsgId消息ID可用于去重。企业微信回调在网络异常时会重试同一个MsgId可能推送不止一次业务处理前一定要先判重。AgentID这条消息是发给哪个应用的。如果一台服务器同时接了多个应用的回调就靠它来区分。3.3 被动回复5秒内必须响应在回调接口里处理完收到的消息后需要“应答”企业微信服务器。如果你希望直接回一段文本给用户就构造一段被动回复消息XML加密后放在HTTP响应体里返回xml ToUserName![CDATA[ZhangSan]]/ToUserName FromUserName![CDATA[CorpID]]/FromUserName CreateTime1348831860/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[收到处理中。]]/Content /xml两个最容易写反的点ToUserName要填发送方的UserID也就是你收到的FromUserNameFromUserName要填企业CorpID。这个方向在逻辑上反直觉特别多人栽在这里。整个回调接口最好在5秒内返回。你的业务逻辑如果比较重比如要查询数据库、调外部API千万别在回调里同步等结果。先返回一个“处理中”的提示后台再用主动发送接口把结果推过去。回调超时后企业微信会重试甚至直接报错用户体验会变得很差。3.4 主动发送和被动回复场景别搞混被动回复适合“即时问答”场景。优点是响应路径短、不需要access_token缺点是有5秒的响应窗口只能回文本或图片这类基础消息而且只能回给当前会话的用户。主动发送message/send没有这些限制可以指定任意可见范围内的成员适合异步通知、定时任务、转人工后的结果推送。实际项目基本都是“回调接收 异步处理 主动推送”的混合模式——回调接口收消息、返回占位提示业务逻辑放消息队列里慢慢跑最后通过主动发送把最终结果交给用户。4. 文本消息二开发中容易踩的坑一次列全这一章我按“血泪程度”排序把开发中经常遇到、但文档里又不显眼的问题集中讲一遍。4.1 IP白名单到底卡在哪一步企业微信的自建应用里可以配置“企业可信IP”。配了之后使用该应用的Secret获取到的access_token在调用其他接口时会对来源IP做校验IP不匹配就报60020。所以排查顺序是先看报错是不是60020。是的话检查应用详情页有没有配可信IP再确认服务器出口IP是否在白名单里。一个小坑是很多服务器走NAT出口或代理实际出口IP和你以为的不一样先确认一下再改白名单。4.2 文本内容细节换行、超链接、字节长度文本消息里的换行符是\n不是br也不是字面量两个字符“反斜杠n”。很多人在代码里写的是字符串\\n结果消息里真的出现了一个字母n就是因为转义层数搞错了。超链接要用HTML标签包一层你的报告已生成a hrefhttp://example.com/report点击查看/a直接发裸链接也能点但用a标签可以自定义显示文字格式更正式也更不容易被当成垃圾链接。文本长度上限是2048字节。特别提醒一个中文字符占3字节一个emoji占4字节你按“字符数”数了半天以为没超其实早就超了。稳妥做法是发送前用字节长度判断超了截断或者改用消息卡片。4.3 群机器人Webhook别乱用群机器人Webhook虽然方便但有明确限制每个机器人每分钟最多20条消息content最长也是2048字节。超了直接报频率限制错误。另一个安全问题是Webhook地址里的key参数就是身份凭证泄露了任何人都能往你群里发消息。我见过有人把Webhook地址直接写在前端网页源码里结果群里被刷了一晚上广告。Webhook地址一定要放在服务端至少做个转发代理不要暴露到公网页面或公开仓库里。4.4 消息防抖enable_duplicate_check参数告警类消息最怕重复轰炸。某服务每隔5秒报一次错你的告警系统就跟着发一次通知一晚上几百条群里直接炸锅。message/send接口支持幂等控制请求体里加上两个参数enable_duplicate_check: 1, duplicate_check_interval: 1800表示同一个应用发送的相同内容在1800秒内不会重复送达。配合告警系统的降噪策略能把群里的告警从“轰炸”变成“一条”这个参数建议所有做告警接入的人默认加上。4.5 消息撤回的窗口很短企业微信有撤回应用消息的接口但撤回窗口并不长而且只对主动发送的应用消息有效。作为兜底手段可以接一个“撤回”入口但核心还是发送前多校验。特别是不要拿生产环境的Secret瞎测试发出去的消息想撤可没那么随意。5. 从文本消息起步能延伸出来的常见玩法文本消息接口只是地基但它能承载的玩法其实相当多。结合最近圈子里讨论比较多的几个方向简单聊聊。5.1 告警机器人群机器人 应用消息组合我接触过的运维类需求基本都是双通道并行。群机器人负责把告警推到公共告警群大家都能看到自建应用消息负责把详细工单推给当班负责人点对点触达。群机器人不需要token、零门槛应用消息又能精确指定人两者配合非常顺。5.2 接入大模型做群内自动问答比如DeepSeek群里经常有人问“企业微信接入DeepSeek怎么弄”本质就是把文本消息接口当成输入输出管道。成员在企业微信里给应用或机器人发文本消息回调把文本内容拿到转给大模型API拿到回答后再走被动回复或主动发送还回去。这里最核心的工程量其实不在模型而在消息收发、会话上下文管理、超时处理这三件事。而这些都建立在本文讲的文本消息接口基础上所以说基础打牢了上层玩法就是水到渠成的事。5.3 Webhook推送结构化数据运营数据、日报、库存表用纯文本硬排版很难看。群机器人Webhook支持markdown类型可以在content里用表格语法{ msgtype: markdown, markdown: { content: ## 今日销售数据\n| 渠道 | 单量 |\n| --- | --- |\n| 线上 | 120 |\n| 门店 | 80 | } }企业微信客户端对markdown表格的渲染基本可用但长表格记得精简列数移动端太宽的表格会被压成一长串字符阅读体验很差。5.4 与H5免登结合闭环更完整如果你有一个自建的H5页面想在企业微信里免登录打开并且操作后通过应用消息通知相关人员这套组合很常见。H5通过OAuth2静默获取成员身份前端提交业务数据到后端后端拿到操作用户的UserID后用文本消息接口给下一步处理人发提醒。文本消息接口在这里就是“人找人”的送达管道也是很多企业内部系统做移动化改造的标配姿势。把文本消息接口跑通企业微信二开的地基就算打好了。我在实际项目中的习惯是先在一个临时工程里按“拿token → 发文本 → 配回调 → 收消息 → 被动回复”的顺序把链路整个走一遍确认没毛病再往正式项目里搬。还有一句血泪提醒测试消息永远先发给自己或测试群等确认无误再扩大范围。企业微信消息发出去容易想撤可没那么随意。
返回列表