Skip to content

Backend API Protocol ​

Back to detailed guide | 简体中文

This document covers every Weixin backend endpoint used by the plugin for QR login, lifecycle notifications, messaging, and media. The two QR login requests always use Tencent's fixed service. A backend selected by the account's post-login baseurl must implement the lifecycle, messaging, and media endpoints.

QR creation and all post-login endpoints use POST; QR status polling uses GET. All requests include:

HeaderDescription
iLink-App-IdPlugin application ID
iLink-App-ClientVersionPlugin version encoded as an unsigned integer
SKRouteTagOptional configured route tag

POST requests additionally include Content-Type: application/json, AuthorizationType: ilink_bot_token, and a random base64-encoded X-WECHAT-UIN. Authenticated post-login requests also include Authorization: Bearer <bot-token>; QR status GET requests do not include these POST-specific headers.

Authenticated post-login POST bodies include base_info; QR creation does not. The message examples below omit it for readability.

json
{
  "base_info": {
    "channel_version": "<plugin version>",
    "bot_agent": "OpenClaw"
  }
}

bot_agent is for observability only. Its supported format and configuration are documented in the detailed guide.

Endpoint List ​

MethodPathDescription
POST/ilink/bot/get_bot_qrcode?bot_type=3Create a QR login session
GET/ilink/bot/get_qrcode_status?qrcode=<opaque-id>Poll QR login status; accepts optional verify_code
POST/ilink/bot/msg/notifystartNotify backend that the channel started
POST/ilink/bot/msg/notifystopNotify backend that the channel stopped
POST/ilink/bot/getupdatesLong-poll for new messages
POST/ilink/bot/sendmessageSend a message (text/image/video/file)
POST/ilink/bot/getuploadurlGet CDN upload pre-signed parameters
POST/ilink/bot/getconfigGet account config (typing ticket, etc.)
POST/ilink/bot/sendtypingSend/cancel typing status

The first two rows describe the fixed QR login service. They are not sent to the account's post-login baseurl.

QR Login and Lifecycle ​

Create a QR session with:

http
POST /ilink/bot/get_bot_qrcode?bot_type=3
Content-Type: application/json
json
{
  "local_token_list": []
}

local_token_list contains up to 10 local bot_token values from the most recently registered accounts; it is empty when no local credentials exist. The backend uses the list to recognize bots already bound to this OpenClaw and may respond with binded_redirect. Every token in the list is sensitive; never put a real value in logs or examples.

The response contains an opaque qrcode identifier and qrcode_img_content, the URL rendered as the QR code. Poll GET /ilink/bot/get_qrcode_status?qrcode=<opaque-id> until it reaches a terminal state. The optional verify_code query parameter handles verification challenges.

FieldTypeDescription
statusstringwait, scaned, need_verifycode, verify_code_blocked, expired, scaned_but_redirect, binded_redirect, or confirmed
bot_tokenstring?Bot credential returned after confirmation
ilink_bot_idstring?Required account ID after confirmation
baseurlstring?Account API base URL
ilink_user_idstring?ID of the user who scanned the QR code
redirect_hoststring?New polling host for scaned_but_redirect

After authentication, call /ilink/bot/msg/notifystart when the channel starts and /ilink/bot/msg/notifystop when it stops. Both receive the standard base_info body and return:

json
{
  "ret": 0,
  "errmsg": ""
}

QR identifiers, verification codes, bot tokens, account IDs, user IDs, and context tokens are sensitive. Never place real values in logs or examples.

getUpdates ​

Long-polling endpoint. The server responds when new messages arrive or on timeout.

Request body:

json
{
  "get_updates_buf": ""
}
FieldTypeDescription
get_updates_bufstringSync cursor from the previous response; empty string for the first request

Response body:

json
{
  "ret": 0,
  "msgs": [],
  "get_updates_buf": "<new cursor>",
  "longpolling_timeout_ms": 35000
}
FieldTypeDescription
retnumberReturn code, 0 = success
errcodenumber?Error code (e.g., -14 = stale token)
errmsgstring?Error description
msgsWeixinMessage[]Message list (structure below)
get_updates_bufstringNew sync cursor to pass in the next request
longpolling_timeout_msnumber?Server-suggested long-poll timeout for the next request (ms)

sendMessage ​

Send a message to a user.

Request body:

json
{
  "msg": {
    "to_user_id": "<target user ID>",
    "context_token": "<conversation context token>",
    "item_list": [
      {
        "type": 1,
        "text_item": { "text": "Hello" }
      }
    ]
  }
}

Response body:

json
{
  "ret": 0,
  "errmsg": ""
}

getUploadUrl ​

Get CDN upload pre-signed parameters. Call this endpoint before uploading a file; clients prefer upload_full_url and otherwise use upload_param. thumb_upload_param may be returned only when a thumbnail is requested. The current plugin always sends no_need_thumb: true, uploads only the original media, and neither requests nor uses thumbnail upload parameters.

Request body:

json
{
  "filekey": "<file identifier>",
  "media_type": 1,
  "to_user_id": "<target user ID>",
  "rawsize": 12345,
  "rawfilemd5": "<plaintext MD5>",
  "filesize": 12352,
  "no_need_thumb": true,
  "aeskey": "<32-character hex AES key>"
}
FieldTypeDescription
filekeystringPer-upload file identifier
media_typenumber1 = IMAGE, 2 = VIDEO, 3 = FILE, 4 = VOICE
to_user_idstringTarget user ID
rawsizenumberOriginal file plaintext size
rawfilemd5stringOriginal file plaintext MD5
filesizenumberCiphertext size after AES-128-ECB encryption
no_need_thumbboolean?Set true to omit thumbnail upload parameters
aeskeystring?AES-128 key as 32 hexadecimal characters
thumb_rawsizenumber?Thumbnail plaintext size when a thumbnail is requested
thumb_rawfilemd5string?Thumbnail plaintext MD5 when requested
thumb_filesizenumber?Thumbnail ciphertext size when requested

Response body:

json
{
  "upload_param": "<original image upload encrypted parameters>",
  "upload_full_url": "https://cdn.example.test/upload",
  "thumb_upload_param": "<optional thumbnail upload parameters>"
}
FieldTypeDescription
upload_paramstring?Parameters used to construct the CDN upload URL
upload_full_urlstring?Complete CDN upload URL; takes precedence over upload_param
thumb_upload_paramstring?Optional thumbnail upload parameters

getConfig ​

Get account configuration, including the typing ticket.

Request body:

json
{
  "ilink_user_id": "<user ID>",
  "context_token": "<optional, conversation context token>"
}

Response body:

json
{
  "ret": 0,
  "errmsg": "",
  "typing_ticket": "<base64-encoded typing ticket>"
}

sendTyping ​

Send or cancel the typing status indicator.

Request body:

json
{
  "ilink_user_id": "<user ID>",
  "typing_ticket": "<obtained from getConfig>",
  "status": 1
}
FieldTypeDescription
statusnumber1 = typing, 2 = cancel typing

Response body:

json
{
  "ret": 0,
  "errmsg": ""
}

Message Structure ​

WeixinMessage ​

FieldTypeDescription
seqnumber?Message sequence number
message_idstring | number?Unique integer message ID; getUpdates preserves integer tokens as exact strings
from_user_idstring?Sender ID
to_user_idstring?Receiver ID
client_idstring?Client-generated message ID
create_time_msnumber?Creation timestamp (ms)
update_time_msnumber?Update timestamp (ms)
delete_time_msnumber?Deletion timestamp (ms)
session_idstring?Session ID
group_idstring?Group ID
message_typenumber?1 = USER, 2 = BOT
message_statenumber?0 = NEW, 1 = GENERATING, 2 = FINISH
item_listMessageItem[]?Message content list
context_tokenstring?Conversation context token, must be passed back when replying
run_idstring?OpenClaw run ID for generated replies

getUpdates also preserves integer msg_id tokens as strings, including in referenced items. It leaves message text and other numeric fields unchanged. Replay dedupe and MessageSid accept exact decimal integer strings and legacy safe integer numbers; 42 and "42" keep the same existing key. Unsafe numeric IDs are not reliable identities and use the existing fallback chain instead. The account namespace, 24-hour replay TTL and persisted key format are unchanged.

MessageItem ​

FieldTypeDescription
typenumber1 TEXT, 2 IMAGE, 3 VOICE, 4 FILE, 5 VIDEO, 11 TOOL_CALL_START, 12 TOOL_CALL_RESULT
create_time_msnumber?Item creation timestamp
update_time_msnumber?Item update timestamp
is_completedboolean?Whether a progress item is complete
msg_idstring?Item message ID
text_item{ text: string }?Text content
image_itemImageItem?Image (with CDN reference and AES key)
voice_itemVoiceItem?Voice (SILK encoded)
file_itemFileItem?File attachment
video_itemVideoItem?Video
ref_msgRefMessage?Referenced message
tool_call_start_item{ tool_name?: string; tool_call_id?: string }?Tool invocation metadata
tool_call_result_item{ tool_name?: string; tool_call_id?: string; status?: string }?Tool completion metadata

Nested Item Structures ​

StructureFields
RefMessagemessage_item?: MessageItem, title?: string
ImageItemmedia?: CDNMedia, thumb_media?: CDNMedia, aeskey?: string, url?: string, mid_size?: number, thumb_size?: number, thumb_height?: number, thumb_width?: number, hd_size?: number
VoiceItemmedia?: CDNMedia, encode_type?: number, bits_per_sample?: number, sample_rate?: number, playtime?: number, text?: string
FileItemmedia?: CDNMedia, file_name?: string, md5?: string, len?: string
VideoItemmedia?: CDNMedia, video_size?: number, play_length?: number, video_md5?: string, thumb_media?: CDNMedia, thumb_size?: number, thumb_height?: number, thumb_width?: number
ToolCallStartItemtool_name?: string, tool_call_id?: string
ToolCallResultItemtool_name?: string, tool_call_id?: string, status?: string

CDN Media Reference (CDNMedia) ​

All media types (image/voice/file/video) are transferred via CDN using AES-128-ECB encryption:

FieldTypeDescription
encrypt_query_paramstring?Encrypted parameters for CDN download/upload
aes_keystring?Base64-encoded AES-128 key
encrypt_typenumber?Encryption metadata mode
full_urlstring?Complete download URL returned by the backend

CDN Upload Flow ​

  1. Calculate the file's plaintext size, MD5, and ciphertext size after AES-128-ECB encryption
  2. Call getUploadUrl with no_need_thumb: true
  3. Prefer upload_full_url; otherwise construct the CDN URL from upload_param
  4. Encrypt the original media with AES-128-ECB and POST it to the CDN URL as application/octet-stream
  5. Read x-encrypted-param from the CDN response and use it as encrypt_query_param in the CDNMedia reference
  6. Include the reference in the MessageItem and send

The protocol also defines thumb_rawsize, thumb_rawfilemd5, thumb_filesize, and thumb_upload_param for implementations that set no_need_thumb: false and upload a thumbnail. The current plugin does not enter that branch.