The WeChat iLink Bot API is an HTTP/JSON protocol used by the WeChat ClawBot feature. Base URL: https://ilinkai.weixin.qq.com. CDN: https://novac2c.cdn.weixin.qq.com/c2c.
Overview
The protocol has three phases: login (QR scan), messaging (long-poll + send), and media (CDN upload/download with AES encryption). The critical concept is context_token โ every reply must echo it back from the incoming message.
QR login flow
Login uses a QR code scan-and-confirm pattern, similar to WeChat Web login.
Status machine: wait โ scaned โ confirmed (or expired โ request new QR)
The bot_token is a Bearer token used for all subsequent API calls. baseurl may differ from the default โ always use the returned value.
Message receive/send loop
Messages are received via long-polling (getupdates), not WebSocket. The server holds the connection for ~35 seconds.
Key rules:
- First request:
get_updates_buf: ""(empty string) - Each response returns a new
get_updates_bufโ treat it as an opaque cursor ret: 0= success;ret: -14= session expired- Messages contain
context_tokenโ must echo it back in replies
Typing indicator flow
Showing "ๅฏนๆนๆญฃๅจ่พๅ
ฅไธญ" requires two API calls: first get a typing_ticket, then send it.
Tips: Cache typing_ticket per user (valid ~24h). status: 1 = start, status: 2 = stop. For long operations, send status: 1 every 5 seconds as keepalive.
Media upload/download
Media files (images, videos, files, voice) are encrypted with AES-128-ECB before upload to the WeChat CDN.
Session expiry & recovery
Sessions expire after some time. The server returns errcode: -14 on any API call when this happens.
All SDKs handle this automatically โ the user just needs to scan a new QR code.
Common request headers
Every business POST request requires these headers:
Content-Type: application/json
AuthorizationType: ilink_bot_token
Authorization: Bearer <bot_token>
X-WECHAT-UIN: <base64(String(random_uint32))>
All request bodies include:
{ "base_info": { "channel_version": "2.0.0" } }
X-WECHAT-UIN is generated fresh per request: random 4 bytes โ uint32 โ decimal string โ base64.
API endpoints
| Endpoint | Method | Purpose |
|---|---|---|
| GET /get_bot_qrcode?bot_type=3 | GET | Request QR code for login |
| GET /get_qrcode_status?qrcode=... | GET | Poll QR scan status |
| POST /getupdates | POST | Long-poll for messages (35s hold) |
| POST /sendmessage | POST | Send text or media message |
| POST /getconfig | POST | Get typing_ticket for user |
| POST /sendtyping | POST | Show/hide typing indicator |
| POST /getuploadurl | POST | Request CDN upload parameters |
| POST CDN /upload | POST | Upload AES-encrypted media |
| GET CDN /download | GET | Download AES-encrypted media |
context_token โ the critical concept
context_token is the most important concept in the iLink protocol. It's not optional โ without it, replies cannot be routed to the correct WeChat conversation.
Rules:
- Every incoming message contains a
context_token - Every outgoing
sendmessagemust include it - Cache per
(userId)โ the latest token for each user - Persist across restarts (Node.js SDK does this via storage)
- Clear on session expiry (
-14) or re-login - Don't forge or reuse across users
How SDKs handle it:
// Node.js โ automatic via reply() bot.onMessage(async (msg) => { // SDK extracts and caches msg._contextToken internally await bot.reply(msg, "Hello") // SDK injects context_token automatically })
# Python โ same pattern @bot.on_message async def handle(msg): await bot.reply(msg, "Hello") # context_token managed by SDK
// Go โ same pattern bot.OnMessage(func(msg *wechatbot.IncomingMessage) { bot.Reply(ctx, msg, "Hello") // context_token managed by SDK })
AES-128-ECB encryption
All media on the WeChat CDN is encrypted with AES-128-ECB + PKCS7 padding.
Three key encoding formats (all SDKs decode all three):
| Format | Example | Source |
|--------|---------|--------|
| base64(raw 16 bytes) | ABEiM0RVZneImaq7zN3u/w== | CDNMedia.aes_key (format A) |
| base64(hex string) | MDAxMTIyMzM0NDU1NjY3Nzg4OTlhYWJiY2NkZGVlZmY= | CDNMedia.aes_key (format B) |
| direct hex (32 chars) | 00112233445566778899aabbccddeeff | image_item.aeskey |
Decoding algorithm:
- If 32 hex chars โ hex decode โ 16 bytes
- Else base64 decode โ if 16 bytes, use directly
- If 32 bytes and all hex ASCII โ hex decode โ 16 bytes
Encrypted size: ceil((rawSize + 1) / 16) * 16
Error codes
| Code | Meaning | SDK Action |
|---|---|---|
| ret: 0 | Success | โ |
| errcode: -14 | Session expired | Clear state โ re-login |
| ret: -2 | Parameter error | Check request body |
| HTTP 4xx | Bad request / auth failure | Check token |
| HTTP 5xx | Server error | Retry with backoff |
How SDK classes map to the protocol
| Protocol Operation | Node.js SDK | Python / Go / Rust |
|---|---|---|
| get_bot_qrcode โ poll โ confirmed | bot.login() | bot.login() / Login() |
| getupdates long-poll | bot.start() | bot.start() / bot.Run() |
| sendmessage + context_token | bot.reply(msg, text) | bot.reply() / bot.Reply() |
| getconfig + sendtyping | bot.sendTyping(userId) | bot.send_typing() / SendTyping() |
| getuploadurl + CDN upload | bot.sendMedia() | (crypto module) |
| CDN download + decrypt | bot.downloadMedia() | (crypto module) |
| errcode -14 handling | Automatic (event + re-login) | Automatic |
| context_token caching | ContextStore (persisted) | Dict / sync.Map / HashMap |
All SDKs follow the same design patterns established by Telegraf (middleware), Discord.js (EventEmitter), and python-telegram-bot (decorator handlers):
- Event-driven โ register handlers, SDK dispatches
- Auto context management โ
reply()handles context_token,send()uses cached token - Session recovery โ automatic re-login on
-14 - Smart chunking โ text split at natural boundaries before send
- Typing lifecycle โ start before processing, stop after reply