Eyun技术顾问头像

企业微信 Webhook 接收不到消息?网络连通性、URL 签名验签与 API 返回错误码排查指南

回调不通,八成不是代码问题。这篇按"连通性、验签、错误码"三层走,每层给可执行的验证命令。

第一层:先确认请求到没到你这里

别急着看代码。先看有没有请求进来。

从外网 curl。找一台不在你内网的机器执行:

curl -v -X POST https://your-domain.com/wx-api/webhook/ \
  -H "Content-Type: application/json" \
  -d '{"data":{"content":"test"}}'

看到 HTTP/1.1 200 说明链路通。看到 Connection refused 就是端口不通,看到 SSL certificate problem 就是证书有问题。

在服务器上抓包。如果 curl 通但回调不来,抓包看有没有请求到达:

sudo tcpdump -i any -n port 443 -A | grep -i "POST /wx-api"

没抓到,问题在平台侧或中间网络。抓到了,问题在你的应用。

常见的拦路虎:

现象

原因

外网 curl 超时

安全组/防火墙没放行

证书报错

证书链不全,或用了自签名

8080 能通、443 不通

只开了 HTTP,回调要求 HTTPS

偶发 502

反向代理超时,应用处理太慢

回调必须公网可达、HTTPS、证书有效。自签名证书和 IP 直连都不行。

第二层:URL 验证过没过

回调地址配置时,平台会发一次验证请求,带 echostr。这一步不过,后面所有消息都不会推。

验证请求的硬性要求:

原样返回 echostr。不能包 JSON,不能加引号,不能带换行。

@app.route("/wx-api/webhook/", methods=["GET", "POST"])
def webhook():
    echostr = request.args.get("echostr")
    if echostr is None and request.is_json:
        echostr = request.json.get("echostr")

    if echostr:
        return echostr, 200, {"Content-Type": "text/plain"}   # 纯文本
    ...

三个容易踩的点:

  • 返回 jsonify({"echostr": ...}) → 失败

  • 返回时加了 \n → 失败

  • 只支持 POST,验证请求发的是 GET → 405

手动模拟验证请求:

curl -i "https://your-domain.com/wx-api/webhook/?echostr=abc123"

返回体应该只有 abc123,不能有别的字符。

第三层:验签对不对

验证过了、消息也来了,但返回 403,说明验签失败。

curl -i -X POST https://your-domain.com/wx-api/webhook/ \
  -H "Content-Type: application/json" \
  -H "X-Eyun-Signature: wrong-signature" \
  -d '{"data":{}}'

预期返回 403。如果返回 200,说明你的验签根本没生效——比验签失败更危险。

验签三原则:原始字节、UTF-8、compare_digest。框架提前读过 body 是最常见的坑。

raw = request.get_data(cache=True)     # 不是 request.json
expected = hmac.new(SECRET.encode(), raw, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, request.headers.get("X-Eyun-Signature", "")):
    return "forbidden", 403

第四层:API 返回的错误码怎么读

消息收到但发不出去,看响应码。企微的 code 有两种类型,处理方式完全不同。

code

类型

含义

处理

0

数字

成功

继续

-1

数字

业务失败,具体码在 message

读 message,不重试

-301002

数字

限流

指数退避重试

字符串(如 "401")

字符串

网关/鉴权错误

检查 Token 和路径

判断要按类型分:

code = resp.get("code")
if code == 0:
    return resp["data"]
if isinstance(code, str):
    raise AuthError(resp)          # 别重试,先查凭证
if code == -301002:
    backoff_and_retry()

把字符串码当业务码重试,会一直撞墙。这是排查时最常见的误判。

最后一种情况:其实收到了,只是你没看见

有两个"看起来没回调"的经典场景:

接口自己发的消息不回推。你用 message/sendText 发出去的消息,平台不会推正文给你,只推一条 2001 已读回执。想看自己发的消息,得查历史消息接口。

只推了回执事件。事件类型是已读回执,正文在另一条事件里。按 X-Eyun-Event 分流时,把回执事件单独处理,别丢。

一套可复用的排查顺序

  1. 外网 curl,确认 200

  2. tcpdump,确认请求到达

  3. 手动请求 echostr,确认纯文本返回

  4. 故意发错签名,确认返回 403

  5. 发一条真实消息,看日志里 code 的类型

  6. 确认不是"自己发的消息"或"只收到回执"

六步走完,问题基本能定位到具体一层。

回调配置和错误码说明在开发文档里能查到,实例的密钥和回调地址在 Eyun 控制台的实例配置里改。

转载自 CSDN-专业IT技术社区

原文链接:https://blog.csdn.net/2603_96320844/article/details/168882518

文章来源转载

评论

评论列表

微信小程序
QQ小程序

关于作者

点赞数:0
关注数:0
粉丝:0
文章:0
关注标签:0
加入于:--