wechatbot
ไธญๆ–‡

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 reference
EndpointMethodPurpose
GET /get_bot_qrcode?bot_type=3GETRequest QR code for login
GET /get_qrcode_status?qrcode=...GETPoll QR scan status
POST /getupdatesPOSTLong-poll for messages (35s hold)
POST /sendmessagePOSTSend text or media message
POST /getconfigPOSTGet typing_ticket for user
POST /sendtypingPOSTShow/hide typing indicator
POST /getuploadurlPOSTRequest CDN upload parameters
POST CDN /uploadPOSTUpload AES-encrypted media
GET CDN /downloadGETDownload 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 sendmessage must 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:

  1. If 32 hex chars โ†’ hex decode โ†’ 16 bytes
  2. Else base64 decode โ†’ if 16 bytes, use directly
  3. If 32 bytes and all hex ASCII โ†’ hex decode โ†’ 16 bytes

Encrypted size: ceil((rawSize + 1) / 16) * 16

Error codes

Known error codes
CodeMeaningSDK Action
ret: 0Successโ€”
errcode: -14Session expiredClear state โ†’ re-login
ret: -2Parameter errorCheck request body
HTTP 4xxBad request / auth failureCheck token
HTTP 5xxServer errorRetry with backoff

How SDK classes map to the protocol

Protocol โ†’ SDK mapping
Protocol OperationNode.js SDKPython / Go / Rust
get_bot_qrcode โ†’ poll โ†’ confirmedbot.login()bot.login() / Login()
getupdates long-pollbot.start()bot.start() / bot.Run()
sendmessage + context_tokenbot.reply(msg, text)bot.reply() / bot.Reply()
getconfig + sendtypingbot.sendTyping(userId)bot.send_typing() / SendTyping()
getuploadurl + CDN uploadbot.sendMedia()(crypto module)
CDN download + decryptbot.downloadMedia()(crypto module)
errcode -14 handlingAutomatic (event + re-login)Automatic
context_token cachingContextStore (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
Protocol โ€” WeChat iLink Bot API