ARTICLE DETAIL

资讯详情

深耕商务建站与企业官网运营的一线实战洞察。

企业微信API发消息接口实战:私聊、群聊与媒体格式详解

企业微信API发消息接口实战:私聊、群聊与媒体格式详解 上周有个做运维的朋友跑来找我问能不能让服务器定时往员工的企业微信里推日报往部门群发告警图片甚至给客户群群发带附件的资料。聊下来发现他最困惑的点不是“接口长什么样”而是企业微信发消息这回事其实被拆成了好几条完全不同的通道给成员个人发私聊消息、按部门或标签做广播、往群里发机器人消息、给外部客户群群发各自对应的接口和权限都不一样。这篇就围绕“企业微信 API 发消息接口”这个主题把私聊、群聊、多种媒体格式以及我在真实项目里调通的完整流程、踩过的坑一次说清楚。这文章适合谁看只要你的工作里需要写代码调用企业微信接口不管是做自动化告警、报表推送、内部通知系统还是做客户运营消息触达都能从中找到可以直接抄走的思路和代码。下面所有内容都基于我在真实项目中反复验证过的方案不同基础的人照着搭都能跑通。1. 先理清楚企业微信消息接口到底有哪几类玩法很多人一听“企业微信API发消息”下意识以为就是一个接口把消息发出去就行。实际没那么简单。企业微信把“发消息”分成了好几条不同的通道每条通道的权限模型、调用方式、能发什么格式都不一样。如果不先搞清楚这些代码写了一半才发现权限不对、格式不支持返工成本非常高。1.1 “私聊”不只是点对点自建应用消息通道在标题里说到的“私聊”在企业微信API里最典型的实现路径是自建应用消息。你在企业微信管理后台创建了一个自建应用然后通过这个应用给指定成员发消息。这里的“私聊”不是指两个员工之间互相聊天而是指系统通过应用给某个员工发一对一的对话消息。这个通道能干的事情非常多。比如我做过的一个模拟项目X每天定时推送前一天的运营数据给各业务负责人用的就是这种通道。它的核心特点是按userid精确指定接收人一个接口调用可以传多个userid用|分隔也支持按部门IDpartyid和标签IDtagid批量发送这种就带了“群发/广播”的味道消息类型齐全文本、图片、语音、视频、文件、图文卡片都能发消息出现在企业微信的“工作台-应用”会话里本质上就是一次真实的企业微信对话我后来总结了一个经验如果你的业务场景是“系统主动找人”就选自建应用消息通道如果是“人主动找系统聊天”那就要再配置消息接收回调做一套对话机器人。但那是另一个项目了这里先不展开。1.2 “群聊”的几种实现路径群机器人、部门群发与客户群群发标题里的“群聊”是一个容易混淆的点因为至少有三条路都叫“群聊”。我实际测试下来三条路的适用场景差异很大这里直接给结论路径一群机器人webhook在企业微信任意一个群里可以添加一个“群机器人”。添加之后会生成一个webhook地址代码里往这个地址POST一段JSON就能把消息推到那个具体群里。这个方案最轻量不需要申请接口权限不用配可信域名是个人开发者最快能体验“发群消息”的方式。路径二给部门/标签发广播自建应用消息除了指定单人也可以指定toparty或totag。比如给“研发部”整个部门发通知给“全体成员”标签发公告。这种方式在企业内部用得最多适合做全员通知、部门公告。路径三客户群群发这是运营向的场景通过“客户联系”相关接口给外部客户的微信群做群发。这个接口的权限模型更严需要单独配置客户联系Secret而且要提前创建群发任务。如果你只是做内部自动化通知基本用不上这条路。我在文末会提一下什么时候才需要考虑它。1.3 为什么标题强调“多种媒体格式”“多种媒体格式”是企业微信消息接口相比其他聊天软件接口最实用的一点。你不只能发纯文字还能把图片、语音、视频、普通文件、图文卡片都发到成员会话或群里。这带来一个直接的业务价值告警系统不仅报一句“服务器负载高了”还能附带一张监控截图报表系统不止发一串数字还能推送一个图文卡片点进去就是完整看板运营系统给客户群发资料PDF文件直接推送。可以说媒体格式的丰富程度决定了这个接口能承载多少真实业务场景。但每种媒体格式都有自己的限制比如图片不能超过2MB、视频不能超过10MB、文件不能超过20MB而且要先用“临时素材上传接口”拿到一个media_id才能发送。这些细节我在第3部分专门展开讲。2. 接入前必须搞懂的几个关键概念调用企业微信API之前如果不先把几个核心概念理清楚后面会出现无数个“明明代码没问题但就是报错”的诡异情况。这一节是我在各项目里总结的最容易出问题的几个概念优先级极高。2.1 CorpID、AgentId、Secret 三者关系第一次接触企业微信API的人十个里有九个会被这三个ID搞晕。我用一句话帮你记住它们的关系CorpID企业ID整个企业的唯一身份标识相当于企业的“身份证号”一个企业只有一个。AgentId应用ID你在企业微信里创建的每一个自建应用都有一个独立的AgentId。一个企业可以建很多个应用每个应用各管一摊事。Secret应用密钥每个应用独立的密码调用接口时用它和CorpID换令牌。Secret一定要保密泄露相当于别人能用你的应用乱发消息。为什么要分成三个因为这样权限才能拆得细。比如人力资源部用A应用、运维部用B应用两边Secret不同发的消息互不干扰出了事也能追责到具体应用。实际项目里建议一个业务线用一个应用别把所有功能塞进一个应用里。2.2 access_token唯一通行证怎么拿、怎么用、怎么缓存调用企业微信所有消息接口之前都要先拿到access_token。它相当于你的“临时通行证”有效期默认7200秒2小时。获取方式很简单拿着corpid和corpsecret调一个GET接口import requests corp_id ww1234567890abcdef secret 你的自建应用Secret url https://qyapi.weixin.qq.com/cgi-bin/gettoken params { corpid: corp_id, corpsecret: secret, } resp requests.get(url, paramsparams) data resp.json() if data[errcode] 0: access_token data[access_token] expires_in data[expires_in] # 通常是7200秒 print(token获取成功:, access_token) else: print(获取失败:, data)这里有一个极其重要的工程经验token必须缓存不能每次请求都重新获取。官方接口虽然没写死但实际有限流策略频繁获取token很容易触发频率限制。我项目的做法是把token存到内存或Redis里过期时间设置为expires_in - 200秒提前刷新避免临界点失效调用消息接口前先从缓存取取不到再重新获取import time import requests class TokenManager: def __init__(self, corp_id, secret): self.corp_id corp_id self.secret secret self.token None self.expire_at 0 def get_token(self): if self.token and time.time() self.expire_at: return self.token url https://qyapi.weixin.qq.com/cgi-bin/gettoken params {corpid: self.corp_id, corpsecret: self.secret} data requests.get(url, paramsparams).json() if data[errcode] 0: self.token data[access_token] self.expire_at time.time() data[expires_in] - 200 return self.token raise Exception(token获取失败: str(data))这套封装看起来不复杂但能解决实际项目里90%的token管理问题。2.3 权限、可信域名、IP白名单与可见范围就算CorpID、Secret、AgentId全对了消息也不一定发得出去。这就是我最常遇到的问题权限配置不到位。企业微信对API调用是有“环境”约束的一般要检查三个点1. 应用的可见范围自建应用默认只能给“可见范围内”的成员发消息。如果某些员工不在应用可见范围里你用接口给他们的userid发消息接口可能会返回成功但对方根本收不到。这个非常坑因为不是报错而是沉默失败。2. 可信IP企业微信支持配置“企业可信IP”如果你的服务器公网IP不在白名单里调用一些敏感接口会直接返回60020之类的错误码。所以接到60020别怀疑代码先去管理后台把服务器公网IP加进去。3. 回调域名如果你需要接收用户发给应用的消息那就要配置“接收消息”的回调URL并且要校验签名、解密消息体。这一块比较复杂不是发消息的必要条件但很多人在做私聊机器人时绕不开。3. 消息类型与媒体格式字段细节和避坑点这一节是全文的硬核部分。我直接按消息类型拆开讲每一种都给出请求结构里的核心字段以及我在实际调用中遇到过的坑。3.1 文本消息最简单但最容易忽略的细节发送文本消息是最基础的但也别小看它。接口地址是message/send用POST请求JSON体大概是这样的send_url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{access_token} text_payload { touser: zhangsan|lisi, toparty: 2, totag: 3, msgtype: text, agentid: 1000002, text: { content: 这是一条来自接口的测试消息 }, safe: 0 } resp requests.post(send_url, jsontext_payload) print(resp.json())有几个细节值得注意touser、toparty、totag可以单独用也可以组合用多个值用|分隔。但注意不能三个字段都为空不然不知道发给谁。safe字段是“保密消息”开关设为1表示消息不可被转发适合发合同、薪资等敏感内容。文本内容长度有限制正常业务一般够用但要注意别把超长日志直接塞进去我遇到过因为消息过长被接口拒绝的情况。agentid是你的自建应用ID发消息时一定要带否则接口不知道该以哪个应用的身份发送。3.2 图片、语音、视频、文件临时素材与media_id机制这是“多种媒体格式”的核心。企业微信不允许你直接传一个URL或者文件内容来发消息而是要求你先把文件上传到企业微信服务器拿到一个media_id然后用这个media_id去发消息。可以理解为先存一个临时网盘再引用网盘里文件的ID。临时素材上传接口upload_url fhttps://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token{access_token}typeimage with open(screenshot.png, rb) as f: files {media: f} resp requests.post(upload_url, filesfiles) media_id resp.json().get(media_id) print(media_id:, media_id)上传时注意type参数要与消息类型一致一共有image、voice、video、file四种。上传成功后发消息时这样构造image_payload { touser: zhangsan, msgtype: image, agentid: 1000002, image: { media_id: media_id }, safe: 0 }针对不同类型我整理了一张限制表都是我实测验证过的直接抄就行媒体类型上传type值最大大小支持格式发送时字段图片image2MBjpg、png、bmp等image.media_id语音voice2MBamrvoice.media_id视频video10MBmp4video.media_id普通文件file20MB任意file.media_id关于这张表有几个实战经验值得重点说图片虽然叫图片但不要以为jpg就一定行如果你发的是截图之类超大尺寸的PNG很容易超过2MB。我以前做告警推送时监控系统生成的截图动不动就3MB后来统一在本地压缩一遍再上传才解决问题。语音格式支持amr不是mp3。微信体系里的语音基本都是amr如果你手里是mp3要先转码。临时素材默认有效期是3天但消息接口发送完基本就不用管它了因为消息已经真实抵达客户端。如果你只上传不发送注意过几天media_id就失效了。上传素材的接口本身就是单独的频率限制批量推送时必须控制并发否则会触发限流。官方推荐频繁发送的文件可以用“永久素材”接口临时素材适合一次性推送场景。多数场景下临时素材够用。3.3 图文news不用上传素材也能发卡片消息图文消息news是我个人觉得最实用的消息类型。它不需要上传素材直接在请求体里写一个articles数组就能生成一条像公众号推送一样的卡片消息。卡片上有标题、描述、封面图、链接点一下就能跳转。用来做运营通知、报表推送非常合适。news_payload { touser: zhangsan, msgtype: news, agentid: 1000002, news: { articles: [ { title: 7月数据报表已生成, description: 点击查看完整报表, url: https://example.com/report, picurl: https://example.com/pic.jpg } ] } }字段里有两个容易踩坑的点picurl必须是公网可访问的图片地址。我之前一偷懒填了个内网地址结果消息发出去了但卡片图片在手机上永远加载不出来。排查半天才发现是图片地址的问题。articles数组最多支持8个元素但建议一个卡片就主打一个核心信息别塞太多条手机端用户体验会好很多。3.4 群机器人webhook支持的媒体格式群机器人是“群聊”场景里最轻量的实现方式。webhook地址长这样https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx你可以直接在代码里POST一个JSON往指定群里发消息。举个例子发一条markdown格式的群消息webhook_url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxxxxx markdown_payload { msgtype: markdown, markdown: { content: ## 服务上线提醒 \n 版本由 **v1.2.0** 更新至 **v1.3.0** } } resp requests.post(webhook_url, jsonmarkdown_payload) print(resp.json())群机器人支持的格式也不少文本text、markdown、图片image、图文news、文件file、语音voice等。注意几点群机器人发图片/文件也需要先传素材拿media_id但要走webhook/upload_media这个接口不是上面提的media/upload。很多人第一次做把两个接口混用就会报media_id无效。群里添加机器人时有一个“安全设置”如果你勾选了“关键词”限制那消息内容必须包含指定关键词否则调用会报错。这个坑特别隐蔽报错信息也不直观我第一次遇到时查了半天才发现是关键词没写。webhook方式不需要access_token只需要webhook地址本身。所以任何人拿了这个地址都能往群里发消息地址泄露等于是把群聊的发言权交了出去要注意保护。4. 完整实操从配置应用到发出一条群聊图片前面讲了不少原理这一节带大家完整走一遍流程。我按“最可能遇到的真实场景”来串先是私聊文本然后私聊图片最后往群里发markdown消息。4.1 前置准备你需要先在企业微信管理后台完成这些操作注册企业微信获得CorpID企业ID。在“应用管理-自建”里创建一个自建应用拿到AgentId和Secret。在“管理后台-我的企业-可信信息”里配置你的服务器公网IP为企业可信IP。如果要体验群聊任意创建一个内部群在群设置里添加“群机器人”复制webhook地址。准备一个小型测试文件比如一张小于2MB的图片test.png。这些步骤都是后台点点点不涉及代码但一定要做对。尤其是第3步很多人都忽略了。4.2 获取并缓存access_token代码直接复用第2部分写的TokenManager类。如果你用Flask或FastAPI写服务可以在应用启动时初始化一个全局的TokenManager实例token_mgr TokenManager(corp_idww1234567890abcdef, secret你自己的应用Secret) # 在发送函数里获取token token token_mgr.get_token()这里再补一个细节如果你的服务是多进程或多实例部署用内存缓存会导致每个进程各自维护一个token有一半请求的token可能是过期的。生产环境建议把token放Rediskey名带上corpid和secret的哈希值实现全局共享。4.3 发送纯文本私聊消息现在开始第一发。假设你要给userid为zhangsan的员工发一条纯文本消息send_url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{token} payload { touser: zhangsan, msgtype: text, agentid: 1000002, text: { content: 你好你的工单已处理完成。 } } resp requests.post(send_url, jsonpayload) data resp.json() print(data)如果返回结果errcode为0说明消息已成功进入发送队列。注意接口返回成功不代表对方已经“已读”也不代表网络送达但正常情况下几秒内就会在客户端出现。这里有个小技巧接口返回后如果你能获取到消息的msgid可以在后续做更多追踪。不过这个接口的返回里通常不包含msgid别强求。4.4 上传临时素材并发图片消息下面来个更复杂的发一张图片。先上传素材再发消息# 第一步上传临时素材 upload_url fhttps://qyapi.weixin.qq.com/cgi-bin/media/upload?access_token{token}typeimage with open(test.png, rb) as f: resp requests.post(upload_url, files{media: f}) data resp.json() if data[errcode] ! 0: print(上传失败:, data) exit(1) media_id data[media_id] print(拿到media_id:, media_id) # 第二步发送图片消息 send_url fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{token} image_payload { touser: zhangsan, msgtype: image, agentid: 1000002, image: { media_id: media_id } } resp requests.post(send_url, jsonimage_payload) print(resp.json())我在实际开发时会把“上传素材”封装成一个通用函数内部处理文件大小超限、格式校验、失败重试等逻辑。因为在批量推送的场景里可能一次要发几十张图如果每个环节都手写代码会非常臃肿。封装之后的调用长这样def send_image(token, to_user, media_id, agent_id): payload { touser: to_user, msgtype: image, agentid: agent_id, image: {media_id: media_id} } return requests.post( fhttps://qyapi.weixin.qq.com/cgi-bin/message/send?access_token{token}, jsonpayload ).json()4.5 通过群机器人往群聊发markdown消息最后展示怎么往群里发消息。这里不通过access_token而是直接调webhookwebhook_url https://qyapi.weixin.qq.com/cgi-bin/webhook/send?keyxxxxx payload { msgtype: markdown, markdown: { content: ## 监控告警 \n 线上服务异常率超过5%请及时处理。\n 时间点2026-03-20 10:30:00 } } resp requests.post(webhook_url, jsonpayload) print(resp.json())如果群里给机器人配置了“关键词”markdown内容里必须包含那个关键词。比如你配置的关键词是“告警”上面这条消息里最好明确带“告警”两个字否则返回如下错误{errcode:93000,errmsg:invalid webhook url, please check the webhook info}这个报错非常迷惑看起来像是webhook地址无效实际是安全设置拦截。我第一次做的时候被它坑了整整一下午。5. 高频踩坑记录与排错参考把我在项目里遇到过的所有问题汇总成一张表你收藏这份以后调接口遇到报错直接对号入座。5.1 返回码快速排查表返回码含义常见原因处理建议0成功无无需处理40001access_token无效token过期、缓存被清、重新获取导致旧token失效重新获取token用新token重试40003userid不合法用户不在通讯录、不是应用的可见范围检查userid大小写、同步通讯录、扩大应用可见范围40013corpid不合法配置的corpid写错了去管理后台核对40014access_token不合法token错误或已被覆盖重新获取token41030media_id无效素材类型不匹配、素材过期、用错上传接口重新上传素材检查type参数45009接口调用超过频率限制并发过高、循环调用、token重复获取加本地队列控制调用频率批量消息分批发送60011无权限访问应用没有通讯录权限、Secret不对应检查应用权限设置确认Secret与AgentId匹配60020访问来源IP不在白名单服务器公网IP未加入可信IP去管理后台配置可信IP93000群机器人消息被拦截webhook地址不对、安全设置关键词不匹配检查webhook地址核对关键词规则5.2 几个典型问题实录除了返回码我在项目里实际踩过的几个场景也值得单独说。场景一接口返回成功但员工就是收不到消息这是最让我头疼的问题。代码检查了很多遍返回码始终是0但员工的企业微信里就是没有新消息。排查到最后发现这个应用在企业微信后台里从来没被员工“使用”过而且可见范围也跟员工无关。说白了你在后台把应用可见范围设成了“研发部”但给一个“市场部”的人发消息接口可能不会直接报错而是默默丢弃。所以收到0也别高兴太早先确认接收人是否在可见范围里。场景二给部门发广播返回60011我当时想给某个部门发通知toparty填的是部门ID但一直返回60011提示没有权限。原因是应用可见范围只勾选了“全体成员”里的某几个部门没有单独把这个部门勾进去。有些你以为是后台配置的问题其实是“可见范围”和“通讯录权限”两个概念混在一起要同时满足。场景三群机器人发图片报media_id无效前面提过群机器人上传素材的接口是/webhook/upload_media但是很多人包括我容易下意识直接用/media/upload。这两个接口返回的media_id格式都差不多但互相不通用。所以如果你确认token没有过期、图片也没超2MB但报media_id无效赶紧看看是不是用的上传接口不对。场景四文本消息内容里带中文或特殊符号出现乱码这个问题主要出现在一些老旧的HTTP客户端上设置请求头的时候没指定UTF-8编码。企业微信接口是标准的UTF-8所以Content-Type一定要写成application/json; charsetutf-8。requests库默认会处理好但如果自己用其他语言写SDK就要注意。最后分享几点我的个人体会接口文档写得很全但是“能跑通”和“在生产环境中稳定跑”之间还隔着大量细节。我在做了好几个类似项目之后最大的感受是一定要在项目初期设计一个统一的消息服务层把token缓存、素材上传、消息发送、重试逻辑全部封装好不要让业务代码直接裸调接口。这样后面接更多消息类型时只改服务层就够了不用动上层业务。另外不管你用py、Java还是Go先做小范围灰度。我的习惯是先把代码写一个命令行脚本手动发给自己和测试号逐项验证文本、图片、文件和群机器人消息类型全部跑通再接入业务系统。别一上来就做批量推送否则限流、权限、media_id过期这些问题混在一起你会被搞得怀疑人生。企业微信消息接口的价值不在于“能发消息”这个动作而在于它可以被程序化地整合进你的运维系统、报表系统、客户运营系统成为整个自动化工作流里最接近人的一环。先把私聊文本跑通再逐步加上图片、视频、群机器人你会发现这套API能做的事情远比想象中要多得多。
返回列表
PREV
查看更多资讯
NEXT
返回资讯列表