企业微信 API 开发入门:账号体系、调用凭证与请求模型一次讲清

本文系统梳理企业微信API开发的三大核心:账号体系(区分corpid、userid、external_userid等ID,强调external_userid是关系ID而非客户ID)、调用凭证(官方开放平台使用需缓存与管理的access_token,账号通道型使用需严格保密的Bearer凭证)、请求模型(统一POST+JSON格式,响应需校验errcode而非HTTP状态码,所有ID为int64硬约束)。明确两条开发路径适用场景,帮助开发者快速选型并规避高频踩坑点。

前阵子帮一个做客服中台的团队排查问题:他们接口调了三天,一直是鉴权失败,换了四种写法都没通。最后发现是方向选错了——他们要的是"让员工的企业微信账号自动给客户发消息",但照着官方开放平台的文档在写。这两条路从鉴权方式到接口形态完全不是一回事,代码写得再对也调不通。

这篇文章把企业微信对接的底层概念一次讲清:账号体系里那些 ID 分别是什么、调用凭证怎么拿怎么传、请求模型长什么样。看完你应该能在半小时内判断出自己该走哪条路,并且把第一个请求发通。


一、先看结论:企业微信开发有两条完全不同的路

这是整篇文章最重要的一节。很多人卡住不是因为代码写得差,而是选错了方向。

对比项官方开放平台 API账号通道型 API
接入主体企业(一个企业一个 corpId)账号(一个登录的企微账号一个实例标识)
身份企业管理员在后台建应用、配权限账号持有人本人扫码授权
凭证corpid + secret 换 access_token控制台领取的固定调用凭证
能做什么应用消息、通讯录、审批、打卡等企业级能力收发客户消息、管理客户群、朋友圈等账号级动作
消息方向应用推送给成员(企业 → 员工)以员工身份与客户对话(员工 ↔ 客户)
场景内部办公、审批流、自建应用客服、SCRM、私域运营、会话存档

一句话判断:如果你的需求是"企业发通知给员工",走官方开放平台;如果是"让某个员工的企微账号去跟客户聊天/拉群/发朋友圈",官方开放平台做不到,得走账号通道型。

官方开放平台的能力边界很清晰:它开放的是"企业能管什么",不包含"以员工个人身份对外沟通"。这不是权限没申请够,是设计上就不开放。


二、账号体系:这些 ID 必须分清

企业微信里的 ID 种类多,混用是最高频的错误来源。先记住这张表:

标识含义作用域常见坑
corpId企业 ID整个企业唯一和 corpid 拼写混用,字段大小写敏感
userId成员账号企业内部唯一不同企业间可重复,跨企业关联必须带 corpId
external_userid外部联系人(客户)ID对某个成员唯一,不是全局唯一同一个客户被两个成员添加,是两个不同的 external_userid
agentId应用 ID企业内唯一发应用消息必填
openid客户在某个应用下的 ID应用维度和 external_userid 是两套体系,别混
chatId / roomId会话 / 群 ID群维度群成员变动、群重建可能导致 ID 变化
uin账号自身的数字 ID登录实例维度通常是 int64,见第四节

external_userid 那个坑值得单独说

我见过不止一个团队在这上面翻车:他们按 external_userid 做客户主数据主键,结果同一个真实客户在系统里出现了七八条记录。

原因是 external_userid 的定义是"某成员与某客户的关系 ID",不是客户本身的 ID。同一个客户被公司 7 个销售加过,就有 7 个 external_userid。

正确做法:客户主表用 unionid(同一主体下客户唯一,需企业认证)或自己生成主键,external_userid 只作为"哪个员工对接这个客户"的关系字段存。


三、调用凭证:怎么拿、怎么传、怎么存

官方开放平台:access_token

GET https://qyapi.weixin.qq.com/cgi-bin/gettoken
    ?corpid={CORPID}&corpsecret={SECRET}

返回:

{
  "errcode": 0,
  "errmsg": "ok",
  "access_token": "xxx",
  "expires_in": 7200
}

三个必须遵守的约束:

  1. 必须缓存,不能每次调用都去换。官方对 gettoken 有频率限制,高频调用会被限流;正确做法是全局缓存一份,提前 5 分钟刷新。
  2. 必须全局唯一。多个服务实例各自刷新 token,会导致后刷新的把先刷新的顶掉,出现随机鉴权失败。要么用单点刷新 + 共享缓存(Redis),要么加分布式锁。
  3. 不同应用的 secret 换出来的是不同 token。通讯录的 secret 换出的 token 不能用来发应用消息。

一个能用的 Java 缓存实现:

@Component
public class TokenManager {
    private static final long REFRESH_AHEAD_MS = 5 * 60 * 1000L;
    private final RedisTemplate<String, String> redis;
    private final String redisKey = "wecom:token:" + appId;

    public String getToken() {
        String cached = redis.opsForValue().get(redisKey);
        if (cached != null) {
            return cached;
        }
        // 分布式锁,避免并发刷新互相顶掉
        RLock lock = redissonClient.getLock(redisKey + ":lock");
        lock.lock(10, TimeUnit.SECONDS);
        try {
            // 双重检查
            cached = redis.opsForValue().get(redisKey);
            if (cached != null) {
                return cached;
            }
            TokenResp resp = fetchFromWecom();
            long ttl = resp.getExpiresIn() - 300; // 提前 5 分钟过期
            redis.opsForValue().set(redisKey, resp.getAccessToken(), ttl, TimeUnit.SECONDS);
            return resp.getAccessToken();
        } finally {
            lock.unlock();
        }
    }
}

账号通道型:Bearer 凭证

这类服务把登录实例、消息收发、事件回调都封装成 HTTP 接口,业务侧只需要调接口,不用自己维护登录态——Eyun 企微 API 就属于这一类。它的凭证是控制台领取的固定字符串,放进请求头:

POST {BASE_URL}/api/message/sendText
Authorization: Bearer <你的调用凭证>
Content-Type: application/json

和 access_token 的关键区别:它不会过期,但泄露即等于账号被接管。所以:

  • 不进代码仓库,走配置中心或环境变量
  • 不进前端,任何前端代码里出现它都是事故
  • 日志脱敏,打印请求头时把 Authorization 抹掉
import os, requests

BASE_URL = os.environ["WECOM_BASE_URL"]
TOKEN = os.environ["WECOM_TOKEN"]

def send_text(appid: str, conversation_id: int, content: str):
    resp = requests.post(
        f"{BASE_URL}/api/message/sendText",
        headers={
            "Authorization": f"Bearer {TOKEN}",
            "Content-Type": "application/json",
        },
        json={
            "appid": appid,
            "conversationId": conversation_id,
            "content": content,
        },
        timeout=10,
    )
    resp.raise_for_status()
    body = resp.json()
    if body.get("code") != 0:
        raise RuntimeError(f"send failed: code={body.get('code')} msg={body.get('message')}")
    return body["data"]

四、请求模型:一个能力一个路径,POST + JSON

无论是哪条路,现代企微接口基本都收敛成了同一个形态:POST + JSON,一个能力一个 URL 路径。

POST {BASE_URL}/api/{模块}/{动作}

模块和动作都是名词化的,看路径就知道在干什么:

路径含义
/api/message/sendText发文本消息
/api/message/sendImage发图片消息
/api/contact/list拉联系人列表
/api/room/create建群
/webhook/set配置回调地址

这个形态的好处是不需要记方法名,接口清单就是目录。坏处是路径多了以后容易拼错,所以建议在项目里用常量集中管理,别到处写裸字符串。

响应封套

统一返回一个信封,业务数据放在 data 里:

{
  "code": 0,
  "data": { "id": 1730000000000001 },
  "detail": "",
  "message": "ok",
  "time": "2026-10-28 15:04:05"
}

判断成功的正确写法是 code == 0,不要用 HTTP 状态码判断——网络层的 200 只代表请求到了网关,不代表业务成功。很多"我明明返回 200 了为什么没发出去"的问题都出在这。

int64:所有 ID 都是大整数,这是个硬约束

uin、conversationId、roomId、消息 ID 这些都是 int64,取值范围超过 JavaScript 的安全整数范围。

请求时必须传 JSON number,不能传字符串:

// 正确
{ "conversationId": 1688855874759204 }

// 错误,会报类型错误
{ "conversationId": "1688855874759204" }

解析时不能经过浮点数。Python 的 json 模块没有这个问题(Python int 任意精度),但 JavaScript 会:

// 1688855874759204 会被解析成 1688855874759200
JSON.parse('{"conversationId": 1688855874759204}')
// → { conversationId: 1688855874759200 }  ← 尾数已经丢了

后端还要注意:Java 里用 Long 接,别用 int;MySQL 里用 BIGINT,别用 INT;序列化给前端时考虑转成字符串(这一层怎么做我单独写了一篇,见文末链接)。


五、第一次调用:一个能跑通的最小流程

按这个顺序走,能避开 90% 的新手问题:

1. 拿到凭证
   ├─ 官方:corpId + secret → access_token(记得缓存)
   └─ 通道:控制台领取 → 存进环境变量

2. 确认实例在线
   └─ 通道型需要先扫码登录,账号不在线时所有调用都会失败

3. 给自己发一条消息(最安全的测试)
   └─ 不要一上来就给客户发,先给自己的 uin 发

4. 看返回的 code
   ├─ 0 → 成功,把返回的 message id 记下来(撤回要用)
   └─ 非 0 → 按 message 字段查原因,不要瞎改参数重试

5. 配回调地址
   └─ 只发不收,等于系统长期处于"大概发出去了"的状态

第 3 步特别说一下:第一个请求永远发给自己。给自己发消息不需要任何客户资源,失败了也不会打扰到别人,是验证凭证、网络、参数格式是否都正确的最快方式。


六、踩坑清单

  1. 把 access_token 写进每个请求的地方重新获取 → 触发限流,且多实例下会互相顶掉。必须全局缓存 + 加锁刷新。
  2. 用 external_userid 当客户主键 → 同一个客户产生多条记录。改用 unionid 或自建主键,external_userid 只作关系字段。
  3. ID 传成字符串 → 报类型错误。int64 必须传 JSON number。
  4. 只看 HTTP 状态码判断成功 → 网关层 200 不等于业务成功,必须判断业务 code。
  5. 凭证进前端或进仓库 → 泄露即账号被接管。走环境变量 + 配置中心,日志脱敏。
  6. 一上来就批量给客户发 → 触发风控,且失败原因排查困难。先自聊验证,再小批量,最后放开。
  7. 没配回调就开始上量 → 发出去不知道成没成功,出问题无法对账。回调和发送同等重要,应该一起上线。

小结

企业微信对接的第一道门槛不是代码,是分清你在走哪条路:

  • 企业发通知给员工 → 官方开放平台,corpId + secret 换 access_token,注意全局缓存
  • 员工账号跟客户对话 → 账号通道型,Bearer 凭证,注意别泄露

然后是三个硬约束:int64 传 number 不传字符串、用业务 code 判断成功而不是 HTTP 200、external_userid 是关系 ID 不是客户 ID。这三条占了我见过的新手问题的绝大部分。

下一篇聊具体的调用模型和鉴权细节展开。如果你正在搭企微相关的系统,我这个系列会按「跑通 → 收回调 → 上量 → 存档」的顺序写下去,索引在文末。


系列导航

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值