ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

企微 API 返回错误码:40001 / 42001 / 45009 / 301002 怎么统一处理?

企微 API 返回错误码:40001 / 42001 / 45009 / 301002 怎么统一处理?

对接企微 API 最烦的就是调不通——接口返回个 code,文档里找不到对应含义,只能瞎猜。我把过去半年踩过的所有错误码整理成一张速查表,按出现频率排序,再给一个统一处理的封装,复制到你项目里当基础设施用。

先搞清楚:企微有两套code 体系

返回结构统一是{code, data, message, detail, time},但 code 可能是数字也可能是字符串:

类型

code 示例

含义

排查方向

数字 code

0、-1

业务层返回

0 成功,-1 业务失败(看 message 字段)

字符串 code

"40001"、"42001"、"-301002"

网关层返回

token、限流、签名、网络等基础设施问题

最容易踩的坑:只做了if code == 0判断,没管字符串 code。字符串 code 出现时 code 本身不是 0,也不是 -1,会被漏掉。

高频错误码速查表

40001 / 42001 — Token 相关(最常见)

code

含义

排查步骤

40001

Token 过期或无效

1. 看 token 获取时间是否超 7200 秒2. 检查Authorization: Bearer eyk_xxx是否拼对3. token 有没有被手动重置过

42001

Token 刷新频率超限

token 还没过期就又去拉了,API 检测到并发刷新直接拒。缓存 token,到期前 300 秒再刷

我上周线上遇到过一次——部署新代码时改了 token 刷新策略,改成"每次请求都先拉 token",结果 10 分钟内连续触发了 42001,整条链路挂了。

45009 / -301002 — 限流相关(第二常见)

code

含义

排查步骤

45009

企微官方接口限流

调 sendMessage、addExternalContact 等高频接口时超过官方配额。解决方案:加延时队列、批量接口代替单条

-301002

平台侧限流

token 刷新、Webhook 回调等有独立限流。token 缓存好能解决 90%

区分两者:45009是企微原生接口返回的,-301002是平台网关层拦截的。

-1 — 业务失败(看 message 字段)

code: -1时必须读 message 字段,真正原因在里面:

resp = api.post("/wx-api/api/message/sendText", {...}) if resp["code"] == -1: print(resp["message"]) # ← 真正的错误原因在这里

常见 message 内容:

message

原因

解决

invalid chatId

chatId 传错或类型不对

chatId 是 int64,必须传 JSON number

invalid externalUserId

外部客户不存在

先拉客户列表确认 externalUserId

externalUserId forbidden

不能给这个客户发消息

客户已经删除好友了

message content too long

文本超 4096 字节

拆成两段发

no permission

接口权限没开

企微后台检查应用权限

"签名错误" 类

code

含义

排查步骤

40002

Authorization 头格式错误

必须是Authorization: Bearer eyk_xxx(Bearer 空格 token)

40003

请求签名校验失败

Webhook 回调的签名问题,用平台 SECRET 做 HMAC-SHA256

40004

echostr 验证失败

echostr 必须纯文本原样返回,不能包 JSON

不常见但要命的

code

含义

排查步骤

40005

IP 白名单校验失败

你的服务器 IP 没加到企微后台

40006

接口调用频率超限

调/wx-api/api/contact/getSyncList等接口太频繁,加缓存

40007

参数类型错误

某个字段传了字符串但要求 number,典型就是 int64 字段加了引号

统一处理:一个出口接住所有码

速查表解决"查",真正让业务代码干净的是"统一处理"——所有出站请求走同一个出口,按 code 类型分流:

def handle_wecom_response(resp): code = resp.get("code") message = resp.get("message", "") if code == 0: return resp.get("data") # 数字 code: 业务错误 if isinstance(code, int): raise WecomBizError(f"业务失败: code={code}, msg={message}") # 字符串 code: 网关错误 if isinstance(code, str): # 高频错误码特殊处理 if code in ("40001", "42001"): # token 过期,自动刷一次再重试 refresh_token() return "RETRY" if code == "-301002": raise WecomRateLimitError("平台限流,稍后重试") if code == "45009": raise WecomRateLimitError("企微官方限流,降低调用频率") raise WecomUnknownError(f"未知错误: code={code}, resp={resp}")

业务函数从此只关心成功路径,token 刷新、限流退避这些恢复动作全部收在处理层里。业务失败(-1)注意别进重试队列——参数错了重试一万次还是错,快速失败加告警才是对的。

最后一条排查经验

调接口报错时,先看原始响应。很多 HTTP 客户端会把非 2xx 状态码直接抛异常,掩盖掉企微返回的code和message。用requests时加个timeout然后手动检查resp.status_code,别让它自动抛异常。

resp = requests.post(url, json=data, timeout=15) # 别直接 resp.json(),先看状态码 if resp.status_code != 200: print(f"HTTP {resp.status_code}: {resp.text}") # 原始响应比处理后的有用

这张表我贴在 IDE 的 Snippet 里了,每次接口报错直接搜 code 就能定位,比翻文档快太多。当然速查表只是兜底,字段和错误码的权威定义还是以 Eyun 开发文档为准——实例和凭证还没配好的话,先回Eyun 官网开通再回来对表。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进