ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

企业微信文本消息接口开发全攻略:token获取、发送与回调实践

企业微信文本消息接口开发全攻略:token获取、发送与回调实践 干企业微信二开这行绕不开的第一道门槛就是文本消息接口。不管是做告警机器人、客户通知、内部系统联动还是接大模型做智能客服最终落地的动作基本都离不开把一段文本稳定地推给指定的人或群。很多人第一次接触企业微信API上来就栽在access_token过期、回调验签失败、消息发不出去这些坑里其实根子都在于没把文本消息这条链路的基本流程吃透。这篇内容我从一个实际做过企业微信集成的开发视角把文本消息接口从准备、取token、发消息到接收回调的完整流程拆开讲一遍。不讲虚的全是能直接照着用的东西。适合刚接手企业微信二开的同学也适合给那些已经在用但偶尔被奇奇怪怪问题卡住的人做个排雷手册。1. 整体思路与接口选型考量1.1 文本消息在企业微信体系里的位置企业微信的开放能力比个人微信要规范得多它本质上是一套完整的HTTP API体系所有消息收发都走JSON格式。在官方文档里消息相关的接口大致分三类应用消息企业内部应用主动推送、群机器人消息Webhook方式往群里丢消息、客户联系消息服务号或客服场景。这三条路径里文本消息都是最基本的载体也是后续扩展卡片消息、markdown消息、文件消息的基础。做二次开发时文本消息接口就好比建房子打地基。你先能稳定发文本才有资格去玩交互式卡片或者做消息回调之后的消息关联处理。很多看似复杂的应用比如告警通知、定时报表推送、业务审批提醒本质上就是一个按条件构造文本然后调用消息接口发出去的过程。1.2 三条文本消息发送路径怎么选我见过不少新手上来就问企业微信发消息用什么接口其实答案要分场景。自建应用消息调用message/send接口用企业自建应用的凭证corpid secret消息会出现在企业微信会话列表里支持发给自己、指定成员、指定部门、标签组甚至整个企业。这是做系统集成最常用的路径。群机器人Webhook不需要应用凭证只需要在群里添加一个自定义机器人拿到一个webhook地址往这个地址POST一段JSON就能往群里发文本。适合做CI/CD构建通知、监控告警甚至简单爬虫结果推送。客户联系消息走externalcontact系列的接口适合给外部客户发消息但限制比较多需要客户关系处于可互动状态还受每日频率限制。我给你的建议是先搞清楚你要发给谁。内部协作、应用通知优先走自建应用群内自动播报、简单告警用群机器人效率最高跟外部客户沟通再考虑客户联系那一套。千万别在选型阶段就用错路径否则后面改起来非常痛苦。1.3 为什么文本接口是最省心的起始点文本消息接口在整条企业微信API体系里属于轻量级操作。它不涉及素材上传、不涉及加密消息体的复杂交互至少发送侧是这样请求参数简单返回结构也相对稳定。这让它成为验证access_token流程、网络连通性、内容可见性的最佳切入点。我习惯在接任何企业微信API集成之前先用文本消息接口把链路打通。链路一通后面接什么接口都顺手链路不同做再多花活也是在半空中飘着。文本接口就像你进入一栋大厦的门禁卡先把它办明白了里面哪个房间你都能去。2. 基础准备应用创建与凭证获取2.1 自建应用的创建路径不同企业微信管理后台布局可能略有差异但大体的路径是登录企业微信管理后台 - 应用管理 - 自建 - 创建应用。创建时需要填应用名称、选择一个可见范围哪些部门或成员能看到这个应用并收到消息。这里有一个小坑可见范围不设置接口调用时你虽然能取到access_token但发送消息时系统会报60011无权限或可见范围不足。所以可见范围要第一时间设置好最好把需要接收消息的部门和成员都包含进去甚至可以直接选全员后面再按需收窄。创建完应用后你会得到两个关键参数AgentId应用ID和 Secret应用密钥。有些版本Secret是点击查看之后才展示复制时注意别把前后的空格带进配置里。2.2 corpid是什么跟AgentId怎么区分这是又一个让新手晕头转向的点。corpid是企业的唯一标识一个企业只有一个相当于你所在企业的身份证号。在企业微信管理后台我的企业 - 企业信息里可以看到。它主要用于构造获取access_token的请求参数也可以理解为API调用时的企业级用户名。AgentId是某个自建应用的编号同一企业下可以有多个自建应用每个应用的AgentId和Secret各不同。AgentId用于告诉企业微信服务器这条消息是要以哪个应用的名义发出去的。做二次开发时你经常需要同时拿着corpid和secret去换token然后拿着token和AgentId去发消息。别把AgentId当corpid用也别把secret当access_token用三者职责完全不同。2.3 权限配置与IP白名单在应用详情页往下翻通常会有一个企业可信IP配置项也就是IP白名单。这个非常关键设置了可信IP之后只有这些IP发起的请求才会被企业微信服务器接受可以在一定程度上防止secret泄露后被异地调用。如果你是本地开发调试需要把你的出口IP加进去如果是服务器部署就把服务器的公网IP加进去如果用了云函数或者负载均衡可能需要配置多个IP或者考虑使用企业微信的不限制IP模式不推荐用在生产环境。另外应用详情里通常还有一些接口权限的开关比如发送应用消息权限默认是开启的但如果你的应用需要读取用户信息或接收消息回调还得去权限管理或API接收消息里做相应配置。别等到代码写完了才发现连权限都没开。3. 核心第一步access_token的获取与维护3.1 获取access_token的标准姿势企业微信的所有业务接口调用都需要带上access_token作为身份凭证通常以query参数的形式拼接在URL后面。获取路径是GET https://qyapi.weixin.qq.com/cgi-bin/gettoken?corpid你的corpidcorpsecret你的secret返回的JSON结构一般是{ errcode: 0, errmsg: ok, access_token: xxxxx, expires_in: 7200 }expires_in是有效期单位是秒企业微信默认是7200秒也就是2小时。看到这个有效期很多刚入门的人会写每次调用接口前先gettoken一次这是最典型的反模式。原因有两点第一企业微信对gettoken接口本身有频率限制频繁调用会被限流报45009或类似错误第二token的获取和维护如果处理不好在并发场景下容易互相顶掉导致一批请求突发失效。3.2 全局缓存与主动刷新机制我推荐的做法是搞一个全局唯一的token管理器把access_token和它的过期时间缓存起来在请求业务接口前先检查本地缓存是否有未过期的token有就直接用没有或者快过期了才去请求新的。伪代码逻辑可以是这样import time import requests class WeComTokenManager: def __init__(self, corpid, secret): self.corpid corpid self.secret secret self.token None self.expire_at 0 def get_token(self): now time.time() # 提前60秒过期避免边界情况 if self.token and self.expire_at - now 60: return self.token url https://qyapi.weixin.qq.com/cgi-bin/gettoken resp requests.get(url, params{ corpid: self.corpid, corpsecret: self.secret }).json() if resp.get(errcode) 0: self.token resp[access_token] self.expire_at now int(resp[expires_in]) return self.token # 处理异常情况记录日志 raise RuntimeError(fgettoken failed: {resp})这个管理器在单机部署时完全够用。如果服务是多实例部署就得考虑把token放到Redis之类的共享存储里避免每个实例各维护一份token互相冲突。另外过期时间提前量建议设大一点比如提前60秒甚至120秒刷新因为网络传输和时钟偏差都可能让正好准时过期的token变成废票。3.3 token失效时的应急处理access_token在两种情况下会突然失效一是超过了7200秒有效期二是企业微信后台重新生成了token比如你重新调用gettoken同一个secret的新token会顶上旧的或者管理员重置了应用的secret。所以在封装发送消息函数时一定要处理token失效后的重试逻辑。标准的做法是先正常调用业务接口如果返回40014不合法的access_token或42001access_token已过期就清掉本地缓存的token重新获取一次再带着新token重试同一请求。def send_text_message(text, userid): token token_manager.get_token() resp do_send(token, text, userid) if resp.get(errcode) in (40014, 42001): token_manager.clear_token() token token_manager.get_token() resp do_send(token, text, userid) return resp重试两次就够了不要做无脑循环避免在token接口或者网络故障时把系统拖垮。4. 文本消息下发的完整实现4.1 发送消息的标准请求结构企业微信发送应用消息的接口是POST https://qyapi.weixin.qq.com/cgi-bin/message/send?access_tokenACCESS_TOKEN请求体是JSON格式发文本消息时最少需要这几个字段{ touser: userid1|userid2, msgtype: text, agentid: 1000002, text: { content: 这是一段测试文本消息 } }字段说明touser接收者的userid多个用竖线分隔。注意这个userid不是手机号也不是微信昵称而是企业微信通讯录里的账号ID。如果不是很清楚怎么查可以在管理后台通讯录里点开成员资料看或者在代码里调用通讯录接口按手机号反查。msgtype固定为text表示这是一条文本消息。agentid你的自建应用ID必须是当前token能操作的应用。text.content消息文本内容。还有一个可选字段safe默认是0。如果设为1表示该消息是保密消息接收者收到后不能转发、不能复制。在涉及敏感信息推送时可以开启。4.2 Python调用示例与异常返回下面是一个完整的Python示例演示了如何通过自建应用给某个成员发送文本消息import requests def send_text(access_token, agent_id, user_ids, content): url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{access_token} payload { touser: |.join(user_ids), msgtype: text, agentid: agent_id, text: { content: content }, safe: 0 } resp requests.post(url, jsonpayload) result resp.json() if result.get(errcode) 0: print(消息发送成功) else: print(f消息发送失败: {result}) return result调用时只要先通过token管理器拿到token再传参就行。返回的errcode是判断成功与否的唯一标准不要只看HTTP状态码。企业微信的接口即使HTTP返回200业务层面也可能报错。我自己踩过最典型的一个坑是往touser里传了电话号码结果一直报60111找不到用户。后来查文档才发现userid是通讯录里的唯一账号名不是手机号也不是邮箱。4.3 文本内容的限制与合规处理文本消息的content字段是有限制的普通文本消息的长度限制约为600字节不同版本可能有差异如果超出会被企业微信截断或直接报错。另外消息内容里如果包含链接企业微信可能会在会话中展示一个链接卡片样式但底层依然是文本消息。在做告警推送或者自动化播报时我习惯在代码里先做一次文本长度校验if len(content.encode(utf-8)) 600: content content[:570] ...(已截断)这里的编码长度是字节数中文字符在UTF-8下占3个字节所以600字节大约相当于200个汉字。别按字符数去截断否则可能截到半个汉字导致编码异常。另外要提醒一点不要把文本消息接口当日志通道用。企业微信没有海量消息推送的能力短期内高频发送大量相同文本很容易触发频控策略。告警类场景要做到告警聚合比如同一问题在5分钟内只发一次。4.4 支持按部门、标签发送除了按指定成员发送文本消息接口还支持按部门和标签发送。只需要把touser换成toparty或totag。{ toparty: 1|2, msgtype: text, agentid: 1000002, text: { content: 发给部门ID为1和2的全部成员 } }部门ID怎么拿企业微信管理后台的通讯录里每个部门后面通常能看到一个隐藏的部门编号也可以在代码里通过通讯录接口查询。这里要特别留意按部门发送时子部门是否包含取决于应用对应的可见范围配置不是说你传了部门ID它就一定能把子部门成员也覆盖到。5. 消息接收回调配置与文本应答5.1 为什么要配消息回调很多二开项目不是单向推送而是需要根据用户发给应用的消息做自动回复或者把用户消息转发给你的业务系统处理。要实现这个能力必须在应用的接收消息配置里设置回调URL并且启用相应的事件与消息接收。配置回调URL时企业微信会要求你填写三个参数URL你服务端用于接收消息的HTTP接口地址必须公网可访问。Token自定义的字符串用于生成签名校验。EncodingAESKey用于消息体加解密的密钥43位字符串。设置完成后企业微信会发一个验证请求到你填的URL你的服务端必须正确响应这个验证才能保存配置。5.2 回调验证的签名算法细节回调验证时企业微信会往你的URL上拼参数msg_signature、timestamp、nonce、echostr。你的服务端需要做的是把token、timestamp、nonce三个参数进行字典序排序。将排序后的三个字符串拼接成一个字符串。对拼接后的字符串做SHA1加密。将加密结果与请求里的msg_signature比对。如果一致用EncodingAESKey对echostr做解密返回解密后的明文。这里最容易出错的点是排序。很多人直接按参数传递顺序拼接结果签名永远对不上。必须用字典序排序后再拼接。import hashlib def verify_signature(token, timestamp, nonce, msg_signature): sort_list sorted([token, timestamp, nonce]) raw .join(sort_list).encode(utf-8) sha1 hashlib.sha1(raw).hexdigest() return sha1 msg_signature5.3 接收文本消息的XML结构解析当用户向应用发送一条文本消息企业微信会以POST方式把消息推送到你的回调URL请求体是XML格式的密文需要先解密然后再解析。解密后的明文XML大致是xml ToUserName![CDATA[corpid]]/ToUserName FromUserName![CDATA[userid]]/FromUserName CreateTime1700000000/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[你好]]/Content MsgId1234567890123456/MsgId AgentID1000002/AgentID /xml其中的Content就是用户发来的文本内容FromUserName是用户的useridMsgId是消息的唯一ID用于后续做消息去重。5.4 被动回复文本消息的姿势如果你的应用需要在收到消息后立即回复可以直接在回调接口里返回一段XML企业微信会把它作为这条消息的被动回复下发到用户会话里。被动回复的XML结构是xml ToUserName![CDATA[userid]]/ToUserName FromUserName![CDATA[corpid]]/FromUserName CreateTime1700000001/CreateTime MsgType![CDATA[text]]/MsgType Content![CDATA[收到你的消息了]]/Content /xml注意被动回复的ToUserName和FromUserName与接收消息时相反是交换过的。同时响应必须是加密后的结果也就是你所有返回给企业微信服务器的XML都要用EncodingAESKey加密处理。如果返回明文企业微信会报签名或解密错误。我在初次实现被动回复时就在这一环卡了半天一直提示aes解密失败后来才意识到返回前必须做加密封装不能直接返回解析后的明文。5.5 异步回复与主动推送的区别被动回复有5秒超时限制。如果你的业务处理超过5秒用户侧会看到服务异常或直接失败。这种情况就不要硬扛被动回复了正确的做法是先把用户消息存进队列或数据库立刻返回空串或构造一个正在处理的临时响应后台业务处理完后再调用发送消息接口主动推送结果。主动推送的方式就是我前面讲的应用消息发送权限、频率限制跟被动回复不同但你完全控制了推送时机不用担心超时。对需要接大模型或者做复杂业务判断的场景异步回复主动推送几乎是必须的架构方案。6. 常见问题与排查技巧实录6.1 高频报错清单与处理办法错误码含义常见原因处理办法40014不合法的access_tokentoken缓存没更新或已被新token顶掉清缓存重新获取检查是否多实例共用一套token42001access_token已过期超过7200秒有效期走主动刷新逻辑提前1-2分钟刷新60011无权限访问该应用可见范围没设置或当前用户不在范围内管理后台调整应用可见范围60111找不到用户userid写错或传成了手机号/邮箱通过通讯录接口反查正确的userid45009接口调用超过频率限制gettoken太频繁或消息发送太密集降低调用频率消息聚合后发送40091消息内容为空content字段没传或为空字符串检查文本消息内容构造逻辑排查这类问题时我的习惯是先把返回的errcode和errmsg完整记到日志里再去对照错误码表。千万别只记一个HTTP状态码就开工企业微信的业务错误码才是真正能定位问题的线索。6.2 回调验证失败的三种典型原因签名比对失败最常见的原因是token、timestamp、nonce的排序错误。记住三个参数必须字典序排序再拼接。URL接收不到请求服务器防火墙拦了POST请求或者URL没有使用HTTPS企业微信要求回调URL必须是HTTPS或者配置了合法的HTTP地址。本地测试可以用内网穿透工具生产环境必须上HTTPS。加解密失败EncodingAESKey填错、密文被截断、或者解密模式与官方SDK版本不匹配。建议直接用官方提供的加解密库别自己造轮子。6.3 消息已发但用户没看到的隐蔽问题有时候返回errcode为0但用户就是没收到消息这种假成功比报错更让人头疼。我遇到过的情况有touser传的不是userid而是中文名接口恰好找到了同名用户就返回成功但真正想通知的人没收到。解决方案发消息前后都到通讯录里核对一遍ID。应用类型的消息在客户端默认是收起状态用户需要点开应用会话才看得到。尤其当用户很少打开企业微信时消息触达率会很低。这种情况可以配合企业微信的应用消息提醒或短信提醒能力来提升触达。消息内容被企业微信的风控拦截但返回成功极少见通常是因为内容命中敏感词或被判定为营销内容。避免方法就是别在文本消息里发大量链接、特殊符号或明显诱导性文案。6.4 多实例部署下的token互踩问题生产环境多副本部署时如果每个实例各持一份token重启时各自调用gettoken后取的token会把先取的顶掉导致部分实例拿旧token发消息时报40014。解决办法有两个方向。一个是用Redis等公共存储统一管理token所有实例读写同一份token获取时加锁避免并发请求gettoken。另一个是在调用层实现token失效后刷新重试一次的逻辑即使个别实例拿到旧token导致第一次调用失败重试时也能自动恢复。第二个方案实现简单效果也不错我目前的生产代码就是两者结合使用。7. 实操总结与优化建议最后再分享几个我做了多年企业微信集成后沉淀下来的经验。文本消息接口虽然简单但它就像是整个企业微信二开体系的最小可用原型。把这个最小的链路跑通你把文本换成立即跳转卡片、换成markdown、换成文件消息思路都是一模一样的拿token、拼JSON、调接口、处理errcode。所以第一遍做千万不要图省事跳过细节token怎么缓存、错误码怎么处理、日志怎么记录这些基本功全在这个阶段建立。关于日志我强烈建议你在发送消息时把agentid、touser、msgtype、返回的errcode、耗时、还有消息的唯一跟踪ID都打出来。后面出了问题没有日志寸步难行。我当时在生产上排查过一次消息偶尔发不出去的诡异问题最后就是靠日志里发现某个touser忽然在通讯录里离职被删掉了才定位到根因。再一个建议是如果你们公司有把企业微信和大模型能力结合的计划比如热搜词里提到的接入deepseek做智能问答文本消息接口正好就是那座桥。上游大模型生成的回复内容最终都需要通过文本消息下发到用户会话里用户的问题也需要通过回调接口收上来。把文本消息这一层打扎实后续扩展智能客服、知识库问答都会顺滑很多。权限安全方面secret一定要放服务端别嵌在App或前端代码里。朋友圈里那种教你把secret放前端的教程谁信谁倒霉。服务端定时轮换secret也是好习惯配合IP白名单能挡掉大部分因为密钥泄露导致的风险。做企业微信二开没有多高深核心就是文档读细、流程理清、日志留足。文本消息接口是你迈出的第一步踩稳了后面的路就好走得多。
返回列表