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": []
}

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 to obtain upload_param and thumb_upload_param.

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_idnumber?Unique message ID
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

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. If a thumbnail is needed (image/video), calculate the thumbnail's plaintext and ciphertext parameters as well
  3. Call getUploadUrl to get upload_full_url or upload_param (and optional thumb_upload_param)
  4. Encrypt the file content with AES-128-ECB and POST it to the CDN URL as application/octet-stream
  5. Encrypt and upload the thumbnail in the same way when requested
  6. Read x-encrypted-param from the CDN response and use it as encrypt_query_param in the CDNMedia reference
  7. Include the reference in the MessageItem and send

Community-maintained OpenClaw WeChat channel plugin · Released under the MIT License.