從建立自建應用到在企業微信裡收到第一條 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:text + 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 最穩。傳送前確認該員工在應用可見範圍內。
下一步(按重要性排):