How to Build a WeCom AI Bot? 3 Approaches Compared (With Code)

Before writing any code for WeCom (WeChat Work), pick the right lane — the three approaches differ sharply in data boundary, effort, and what they can actually do

2026-09-07 · Leo · Tutorial · 9 min read
TL;DR — There are three ways to put an AI bot into WeCom: Approach A: custom app + callback (full send/receive control, best for production); Approach B: group robot webhook (works in 10 minutes, but push-only — it cannot receive messages, so it can't do conversations); Approach C: third-party SaaS (fastest to launch, but conversations flow through someone else's platform). This post walks through all three with code, plus 5 common pitfalls — signature failures, duplicate replies from the 5-second timeout, token rate limits — and a pre-launch checklist.
⚠️ This is a generic tutorial; API fields follow the official WeCom documentation. Code samples are examples — adjust to your company's security and compliance requirements before going live. No customer cases involved.

TL;DR Table: The Three Approaches

DimensionA: Custom App + CallbackB: Group Robot WebhookC: Third-Party SaaS
Receive messages✅ Full in-app 1:1 send/receive❌ Push to group only✅ Platform relays both ways
Data boundaryMessages stay on your serversPush content via WeCom serversConversations via third-party platform
Time to launch1–3 days (mostly callback debugging)~10 minutesHalf a day to 1 day (config-driven)
Dev effortMedium: callback crypto + token mgmt + AI wiringMinimal: one HTTP requestLow: web config + a few APIs
Best forProduction AI assistant, FAQ, workflow queriesNotifications, alerts, daily digestsFast validation, no dev team

One line: conversations need A, notifications fit B, quick validation uses C. The classic mistakes: trying to build a chatbot on B (it can't receive messages), or jumping to C without checking the data boundary.

Approach A: Custom App + Callback to Your Own AI Service (Recommended)

This is the officially supported full path: an employee messages your custom app → WeCom POSTs the encrypted message to your callback URL → your service calls your AI → the reply goes back through the app message API. Everything stays on your servers; permissions, logs, and audit are under your control.

Step 1: Create the custom app (~5 min)

In the admin console → "Applications" → "Custom" → "Create". For the visible range, start with one small department as a canary. Write down three values:

Step 2: Configure the message callback (~30 min, incl. debugging)

App detail → "Receive Messages" → "API endpoint". Fill in: URL (your HTTPS endpoint), Token, and EncodingAESKey (can be auto-generated). On save, WeCom sends a GET verification request to your URL: you must verify the signature and return the decrypted echostr as-is.

# callback_verify.py — callback URL verification (GET)
# Signature: sha1 of the sorted concatenation of token, timestamp, nonce, ciphertext
import hashlib

def check_signature(token, timestamp, nonce, encrypt_msg):
    items = sorted([token, timestamp, nonce, encrypt_msg])
    return hashlib.sha1("".join(items).encode()).hexdigest()

@app.route("/wecom/callback", methods=["GET"])
def verify():
    encrypt = request.args["echostr"]
    expect = request.args["msg_signature"]
    actual = check_signature(TOKEN, request.args["timestamp"],
                             request.args["nonce"], encrypt)
    if actual != expect:
        return "bad signature", 403
    # AES-decrypt with the official crypto library, return plaintext as-is
    return decrypt_echostr(encrypt, AES_KEY)

The three usual causes of verification failure: wrong Token, server clock drift (run NTP), or implementing POST only and forgetting the GET verification. During debugging, log the four strings that go into the signature — the mismatch becomes obvious.

Step 3: Receive → call AI → reply (the 5-second limit is the key)

When an employee messages the app, WeCom POSTs an encrypted XML to your callback URL. Two hard constraints: you must respond within 5 seconds (otherwise WeCom retries, up to three times), and retried messages are identical (no dedup means duplicated replies). The standard pattern: dedupe and return immediately, process asynchronously, then push the reply through the send-message API.

# wecom_bot.py — receive: MsgId dedup + async processing
import threading

replying = set()          # use Redis in production for multi-instance safety

@app.route("/wecom/callback", methods=["POST"])
def on_message():
    xml = decrypt_msg(request.data, request.args)   # official crypto library
    msg_id = xml.get("MsgId")
    if msg_id in replying:
        return "success"        # retried duplicate — swallow it
    replying.add(msg_id)
    threading.Thread(target=handle, args=(xml["FromUserName"],
                                          xml["Content"], msg_id)).start()
    return "success"            # respond immediately, don't wait for the AI

def handle(user, question, msg_id):
    try:
        answer = ask_ai(question)     # your own AI / knowledge-base service
        send_text(user, answer)       # push via the app message API
    except Exception:
        send_text(user, "Sorry, that request timed out. Please try again.")
    finally:
        replying.discard(msg_id)
Step 4: Cache access_token and send messages

The access_token is exchanged with corpid + Secret, valid for 7200 seconds, with a refresh rate limit — fetching it fresh at every call site is an anti-pattern. Cache centrally, refresh from one place. Also: the outbound IP of your server must be added to the app's trusted IP list, or send calls fail with error 60020.

# wecom_client.py — centralized token cache + send text message
import time, requests

_token, _ts = "", 0

def get_token():
    global _token, _ts
    if _token and time.time() - _ts < 7000:      # 200s safety margin
        return _token
    r = requests.get("https://qyapi.weixin.qq.com/cgi-bin/gettoken",
                     params={"corpid": CORP_ID, "corpsecret": SECRET},
                     timeout=5).json()
    _token, _ts = r["access_token"], time.time()
    return _token

def send_text(user, content):
    requests.post(
        "https://qyapi.weixin.qq.com/cgi-bin/message/send",
        params={"access_token": get_token()},
        json={"touser": user, "msgtype": "text", "agentid": AGENT_ID,
              "text": {"content": content[:2000]}},     # truncate/split long text
        timeout=5)

That's Approach A working end to end. Swap ask_ai() for your RAG knowledge base or LLM service and you have a usable enterprise assistant.

Approach B: Group Robot Webhook (10 minutes, push-only)

Group settings → "Group Robot" → "Add" gives you a webhook URL. POST JSON to it and the message lands in the group:

curl 'https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=YOUR_KEY' \
  -H 'Content-Type: application/json' \
  -d '{"msgtype":"text","text":{"content":"Deploy done: order service v2.3 is live"}}'

Its role is one-way notification: CI/CD results, monitoring alerts, daily digests. Two hard limits: it cannot receive group messages (no conversations), and each robot has a send rate limit (aggregate high-frequency alerts). Text, markdown, and rich-card types are supported. Good for "tell humans what the system did" — wrong for "humans ask, system answers".

Approach C: Third-Party SaaS (fastest — but check the data boundary first)

Managed platforms let you authorize WeCom and configure a knowledge base from a web console — no callback code to write, live within half a day. Two situations fit: no dev capacity, or you want to validate the value of an AI assistant before committing engineering time.

But ask four questions before signing, or you'll pay for it later:

  1. Where is data stored: do employee questions and documents live on their platform? In which region?
  2. Can you export: if you switch later, can the knowledge base and conversation history leave with you?
  3. Is on-prem possible: after validation, can it migrate to the self-hosted Approach A form?
  4. How is it billed: per seat or per call? Who pays the underlying AI/LLM cost?

Five Common Pitfalls and Fixes

Pitfall 1: Callback signature keeps failing (403)

Symptom: the endpoint config never saves, or pushes never arrive.

Cause: Token mismatch, server clock drift, or POST implemented without the GET verification.

✅ Fix: copy the Token exactly; run NTP; on failure log the four signed strings and diff them one by one.
Pitfall 2: access_token rate-limited or silently invalidated

Symptom: occasional token fetch failures, or a new token instantly killing the old one.

Cause: multiple code paths refreshing the token and evicting each other's cache; no lock on the central cache.

✅ Fix: centralize token management in one client/Redis layer, refresh under a lock with a 200-second expiry margin.
Pitfall 3: One message answered two or three times

Symptom: when the AI is slow, the employee gets duplicate answers to the same question.

Cause: synchronous AI calls exceed 5 seconds, WeCom retries, and the server doesn't dedupe by MsgId.

✅ Fix: dedupe by MsgId (Redis SETNX with TTL) + async processing + proactive reply; the callback always returns instantly.
Pitfall 4: errcode 60020, not allow to access from your ip

Symptom: works locally, fails with 60020 after deploying to the server.

Cause: the outbound IP is not on the app's trusted IP list.

✅ Fix: pin the server's egress IP (NAT/elastic IP) and add it to the trusted list; watch out for floating egress IPs in container deployments.
Pitfall 5: Long replies fail or get truncated

Symptom: longer AI answers never arrive, or only the first half shows up.

Cause: text messages have a length cap; over-limit content errors out or gets cut.

✅ Fix: split long content into multiple messages by paragraph, or reply with a summary plus a link to the full document.

Pre-Launch Checklist (8 items)

  1. Is the callback URL on HTTPS with a valid chain, passing both GET verification and POST delivery?
  2. Is MsgId dedup in place (Redis/in-memory), shared across instances?
  3. Is access_token centrally cached, refreshed under lock, with expiry margin?
  4. Is the server's egress IP pinned and added to the app's trusted IP list?
  5. Does the AI call have a timeout fallback (reply with a notice instead of leaving users hanging)?
  6. Are long replies split or summarized to stay under the message length cap?
  7. Is there sensitive-word/permission filtering before sending, and is app visibility scoped by department?
  8. Are logs sanitized and Secrets kept in environment variables instead of the codebase?

Final Thoughts

No approach is universally better — the choice comes down to three things: how strict your data boundary is, how much dev capacity you have, and what scenario you're solving. Don't spend engineering on Approach A for a notification feed; don't build a chatbot on Approach B (it can't receive); don't sign up for Approach C without asking the four data-boundary questions. Many teams' real path: validate quickly with C or B, then migrate to A as the long-term self-hosted home.

If you only keep one line: conversations → A, notifications → B, validation → C — don't mix them.

💡 Further reading: For other IM platforms, see Feishu AI Bot: 30-Minute Quickstart; for a unified multi-IM strategy, see AI Agent in Enterprise IM: Wire Up Feishu/DingTalk/WeCom in 4 Steps.