从创建自建应用到在企业微信里收到第一条 AI 消息(含 4 个报错修法)
work.weixin.qq.com(企业管理员)→ 应用管理 → 自建 → 创建应用CorpID(我的企业 → 企业信息 → 最底部)AgentId(应用详情页,纯数字)Secret(应用详情 → Secret → 查看,关页面后要重置才能再看)Token(自己编一串随机字符串)和 EncodingAESKey(点随机生成)企业微信回调验证比飞书多一层:GET 请求带 msg_signature,echostr 是 AES 加密的,必须先解密再原样返回。这段代码直接拷:
import hashlib, base64, struct
from flask import Flask, request, Response
from Crypto.Cipher import AES
app = Flask(__name__)
TOKEN = "你填的Token"
AES_KEY = "你填的EncodingAESKey" # 43位,代码里补一个"="
CORP_ID = "你的CorpID"
def decrypt_msg(encrypted):
key = base64.b64decode(AES_KEY + "=")
cipher = AES.new(key, AES.MODE_CBC, key[:16])
plain = cipher.decrypt(base64.b64decode(encrypted))
pad = plain[-1]
plain = plain[:-pad] # 去 PKCS7 填充
plain = plain[16:] # 去 16 字节随机串
msg_len = struct.unpack("!i", plain[:4])[0]
return plain[4:4 + msg_len].decode() # 消息体
def verify_signature(signature, timestamp, nonce, echostr):
s = "".join(sorted([TOKEN, timestamp, nonce, echostr]))
return hashlib.sha1(s.encode()).hexdigest() == signature
@app.route("/wecom/callback", methods=["GET"])
def verify_url():
args = request.args
if verify_signature(args["msg_signature"], args["timestamp"],
args["nonce"], args["echostr"]):
return Response(decrypt_msg(args["echostr"]), mimetype="text/plain")
return "signature error", 403
@app.route("/wecom/callback", methods=["POST"])
def receive_msg():
# 企业微信 POST 的消息体是 { Encrypt: "..." },同样先验签再解密
# 解密后是 XML:<MsgType>text</MsgType> + Content + FromUserName...
# 这里先打印日志,第 4 步换成 AI 调用
return Response("success", mimetype="text/plain")
if __name__ == "__main__":
app.run(host="0.0.0.0", port=8080)
⚠️ 依赖:pip install flask pycryptodome。企业微信要求回调返回纯文本 success(不是 JSON),返回别的会一直重试推送。
ngrok http 8080(或 cloudflared / bore 都行)https://xxxx.ngrok-free.app 这样的 URLhttps://xxxx.ngrok-free.app/wecom/callback → 保存verify_url() 返回解密后的 echostr 就通过,看到「保存成功」即通POST 回调解密后是 XML,用 xml.etree 解析,然后调 AI,再用「发送应用消息」API 回给员工:
import requests, xml.etree.ElementTree as ET
def get_access_token():
url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken"
r = requests.get(url, params={"corpid": CORP_ID, "corpsecret": SECRET},
timeout=10)
return r.json()["access_token"] # 2 小时有效,建议缓存
def send_to_user(user_id, content):
token = get_access_token()
url = "https://qyapi.weixin.qq.com/cgi-bin/message/send"
requests.post(url, params={"access_token": token}, json={
"touser": user_id, # UserID,不是手机号/姓名
"msgtype": "text",
"agentid": AGENT_ID,
"text": {"content": content}
}, timeout=10)
# 在 receive_msg() 里(解密出 xml_str 后):
root = ET.fromstring(xml_str)
user_id = root.findtext("FromUserName") # 发消息的员工 UserID
content = root.findtext("Content") # 员工发的文字
reply = ask_ai(content) # 调你的自部署 AI 服务
send_to_user(user_id, reply)
第一次跑通后,再加:
proxy_pass http://127.0.0.1:8080)或直接部署到服务器企业微信验证 URL 时 GET 带 msg_signature,不是飞书那种 challenge 原样返回。最常见两个原因:验签字符串没按 Token/timestamp/nonce/echostr 排序后拼接;或者 echostr 没解密直接返回了密文。
回调处理必须返回纯文本 success(Content-Type: text/plain)。返回 JSON 或 HTTP 非 200,企业微信会按 3s/10s/1min 间隔反复重试,日志刷屏。
return Response("success", mimetype="text/plain")。收到消息先回 success 再去处理 AI(异步),避免 AI 慢导致超时重试。
企业微信所有 API 校验服务器出口 IP。你本地调试时 IP 一直在变,调用就报 60020。
最常见两个原因:touser 传的是姓名/手机号,但 API 只认 UserID(通讯录里那个字母数字 ID);或者应用可见范围没包含这个员工。
FromUserName 就是 UserID,直接拿来当 touser 最稳。发送前确认该员工在应用可见范围内。
下一步(按重要性排):