Weixin (WeChat)
Connect Hermes to WeChat (微信), Tencent's personal messaging platform. The adapter uses Tencent's iLink Bot API for personal WeChat accounts — this is distinct from WeCom (Enterprise WeChat). Messages are delivered via long-polling, so no public endpoint or webhook is required.
This adapter is for personal WeChat accounts (微信). If you need enterprise/corporate WeChat, see the WeCom adapter instead.
Prerequisites
- A personal WeChat account
- Python packages:
aiohttpandcryptography - The
qrcodepackage is optional (for terminal QR rendering during setup)
Install the required dependencies:
pip install aiohttp cryptography
# Optional: for terminal QR code display
pip install qrcode
Setup
1. Run the Setup Wizard
The easiest way to connect your WeChat account is through the interactive setup:
hermes gateway setup
Select Weixin when prompted. The wizard will:
- Request a QR code from the iLink Bot API
- Display the QR code in your terminal (or provide a URL)
- Wait for you to scan the QR code with the WeChat mobile app
- Prompt you to confirm the login on your phone
- Save the account credentials automatically to
~/.hermes/weixin/accounts/
Once confirmed, you'll see a message like:
微信连接成功,account_id=your-account-id
The wizard stores the account_id, token, and base_url so you don't need to configure them manually.
2. Configure Environment Variables
After initial QR login, set at minimum the account ID in ~/.hermes/.env:
WEIXIN_ACCOUNT_ID=your-account-id
# Optional: override the token (normally auto-saved from QR login)
# WEIXIN_TOKEN=your-bot-token
# Optional: restrict access
WEIXIN_DM_POLICY=open
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
# Optional: home channel for cron/notifications
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home
3. Start the Gateway
hermes gateway
The adapter will restore saved credentials, connect to the iLink API, and begin long-polling for messages.
Features
- Long-poll transport — no public endpoint, webhook, or WebSocket needed
- QR code login — scan-to-connect setup via
hermes gateway setup - DM and group messaging — configurable access policies
- Media support — images, video, files, and voice messages
- AES-128-ECB encrypted CDN — automatic encryption/decryption for all media transfers
- Context token persistence — disk-backed reply continuity across restarts
- Markdown formatting — headers, tables, and code blocks are reformatted for WeChat readability
- Smart message chunking — long messages are split at logical boundaries (paragraphs, code fences)
- Typing indicators — shows "typing…" status in the WeChat client while the agent processes
- SSRF protection — outbound media URLs are validated before download
- Message deduplication — 5-minute sliding window prevents double-processing
- Automatic retry with backoff — recovers from transient API errors
Configuration Options
Set these in config.yaml under platforms.weixin.extra:
| Key | Default | Description |
|---|---|---|
account_id | — | iLink Bot account ID (required) |
token | — | iLink Bot token (required, auto-saved from QR login) |
base_url | https://ilinkai.weixin.qq.com | iLink API base URL |
cdn_base_url | https://novac2c.cdn.weixin.qq.com/c2c | CDN base URL for media transfer |
dm_policy | open | DM access: open, allowlist, disabled, pairing |
group_policy | disabled | Group access: open, allowlist, disabled |
allow_from | [] | User IDs allowed for DMs (when dm_policy=allowlist) |
group_allow_from | [] | Group IDs allowed (when group_policy=allowlist) |
Access Policies
DM Policy
Controls who can send direct messages to the bot:
| Value | Behavior |
|---|---|
open | Anyone can DM the bot (default) |
allowlist | Only user IDs in allow_from can DM |
disabled | All DMs are ignored |
pairing | Pairing mode (for initial setup) |
WEIXIN_DM_POLICY=allowlist
WEIXIN_ALLOWED_USERS=user_id_1,user_id_2
Group Policy
Controls which groups the bot responds in:
| Value | Behavior |
|---|---|
open | Bot responds in all groups |
allowlist | Bot only responds in group IDs listed in group_allow_from |
disabled | All group messages are ignored (default) |
WEIXIN_GROUP_POLICY=allowlist
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
The default group policy is disabled for Weixin (unlike WeCom where it defaults to open). This is intentional since personal WeChat accounts may be in many groups.
Media Support
Inbound (receiving)
The adapter receives media attachments from users, downloads them from the WeChat CDN, decrypts them, and caches them locally for agent processing:
| Type | How it's handled |
|---|---|
| Images | Downloaded, AES-decrypted, and cached as JPEG. |
| Video | Downloaded, AES-decrypted, and cached as MP4. |
| Files | Downloaded, AES-decrypted, and cached. Original filename is preserved. |
| Voice | If a text transcription is available, it's extracted as text. Otherwise the audio (SILK format) is downloaded and cached. |
Quoted messages: Media from quoted (replied-to) messages is also extracted, so the agent has context about what the user is replying to.
AES-128-ECB Encrypted CDN
WeChat media files are transferred through an encrypted CDN. The adapter handles this transparently:
- Inbound: Encrypted media is downloaded from the CDN using
encrypted_query_paramURLs, then decrypted with AES-128-ECB using the per-file key provided in the message payload. - Outbound: Files are encrypted locally with a random AES-128-ECB key, uploaded to the CDN, and the encrypted reference is included in the outbound message.
- The AES key is 16 bytes (128-bit). Keys may arrive as raw base64 or hex-encoded — the adapter handles both formats.
- This requires the
cryptographyPython package.
No configuration is needed — encryption and decryption happen automatically.
Outbound (sending)
| Method | What it sends |
|---|---|
send | Text messages with Markdown formatting |
send_image / send_image_file | Native image messages (via CDN upload) |
send_document | File attachments (via CDN upload) |
send_video | Video messages (via CDN upload) |
All outbound media goes through the encrypted CDN upload flow:
- Generate a random AES-128 key
- Encrypt the file with AES-128-ECB + PKCS#7 padding
- Request an upload URL from the iLink API (
getuploadurl) - Upload the ciphertext to the CDN
- Send the message with the encrypted media reference
Context Token Persistence
The iLink Bot API requires a context_token to be echoed back with each outbound message for a given peer. The adapter maintains a disk-backed context token store:
- Tokens are saved per account+peer to
~/.hermes/weixin/accounts/<account_id>.context-tokens.json - On startup, previously saved tokens are restored
- Every inbound message updates the stored token for that sender
- Outbound messages automatically include the latest context token
This ensures reply continuity even after gateway restarts.
Markdown Formatting
WeChat's personal chat does not natively render full Markdown. The adapter reformats content for better readability:
- Headers (
# Title) → converted to【Title】(level 1) or**Title**(level 2+) - Tables → reformatted as labeled key-value lists (e.g.,
- Column: Value) - Code fences → preserved as-is (WeChat renders these adequately)
- Excessive blank lines → collapsed to double newlines
Message Chunking
Long messages are split intelligently for chat delivery:
- Maximum message length: 4000 characters
- Split points prefer paragraph boundaries and blank lines
- Code fences are kept intact (never split mid-block)
- Indented continuation lines (sub-items in reformatted tables/lists) stay with their parent
- Oversized individual blocks fall back to the base adapter's truncation logic
Typing Indicators
The adapter shows typing status in the WeChat client:
- When a message arrives, the adapter fetches a
typing_ticketvia thegetconfigAPI - Typing tickets are cached for 10 minutes per user
send_typingsends a typing-start signal;stop_typingsends a typing-stop signal- The gateway automatically triggers typing indicators while the agent processes a message
Long-Poll Connection
The adapter uses HTTP long-polling (not WebSocket) to receive messages:
How It Works
- Connect: Validates credentials and starts the poll loop
- Poll: Calls
getupdateswith a 35-second timeout; the server holds the request until messages arrive or the timeout expires - Dispatch: Inbound messages are dispatched concurrently via
asyncio.create_task - Sync buffer: A persistent sync cursor (
get_updates_buf) is saved to disk so the adapter resumes from the correct position after restarts
Retry Behavior
On API errors, the adapter uses a simple retry strategy:
| Condition | Behavior |
|---|---|
| Transient error (1st–2nd) | Retry after 2 seconds |
| Repeated errors (3+) | Back off for 30 seconds, then reset counter |
Session expired (errcode=-14) | Pause for 10 minutes (re-login may be needed) |
| Timeout | Immediately re-poll (normal long-poll behavior) |
Deduplication
Inbound messages are deduplicated using message IDs with a 5-minute window. This prevents double-processing during network hiccups or overlapping poll responses.
Token Lock
Only one Weixin gateway instance can use a given token at a time. The adapter acquires a scoped lock on startup and releases it on shutdown. If another gateway is already using the same token, startup fails with an informative error message.
All Environment Variables
| Variable | Required | Default | Description |
|---|---|---|---|
WEIXIN_ACCOUNT_ID | ✅ | — | iLink Bot account ID (from QR login) |
WEIXIN_TOKEN | ✅ | — | iLink Bot token (auto-saved from QR login) |
WEIXIN_BASE_URL | — | https://ilinkai.weixin.qq.com | iLink API base URL |
WEIXIN_CDN_BASE_URL | — | https://novac2c.cdn.weixin.qq.com/c2c | CDN base URL for media transfer |
WEIXIN_DM_POLICY | — | open | DM access policy: open, allowlist, disabled, pairing |
WEIXIN_GROUP_POLICY | — | disabled | Group access policy: open, allowlist, disabled |
WEIXIN_ALLOWED_USERS | — | (empty) | Comma-separated user IDs for DM allowlist |
WEIXIN_GROUP_ALLOWED_USERS | — | (empty) | Comma-separated group IDs for group allowlist |
WEIXIN_HOME_CHANNEL | — | — | Chat ID for cron/notification output |
WEIXIN_HOME_CHANNEL_NAME | — | Home | Display name for the home channel |
WEIXIN_ALLOW_ALL_USERS | — | — | Gateway-level flag to allow all users (used by setup wizard) |
Troubleshooting
| Problem | Fix |
|---|---|
Weixin startup failed: aiohttp and cryptography are required | Install both: pip install aiohttp cryptography |
Weixin startup failed: WEIXIN_TOKEN is required | Run hermes gateway setup to complete QR login, or set WEIXIN_TOKEN manually |
Weixin startup failed: WEIXIN_ACCOUNT_ID is required | Set WEIXIN_ACCOUNT_ID in your .env or run hermes gateway setup |
Another local Hermes gateway is already using this Weixin token | Stop the other gateway instance first — only one poller per token is allowed |
Session expired (errcode=-14) | Your login session has expired. Re-run hermes gateway setup to scan a new QR code |
| QR code expired during setup | The QR auto-refreshes up to 3 times. If it keeps expiring, check your network connection |
| Bot doesn't respond to DMs | Check WEIXIN_DM_POLICY — if set to allowlist, the sender must be in WEIXIN_ALLOWED_USERS |
| Bot ignores group messages | Group policy defaults to disabled. Set WEIXIN_GROUP_POLICY=open or allowlist |
| Media download/upload fails | Ensure cryptography is installed. Check network access to novac2c.cdn.weixin.qq.com |
Blocked unsafe URL (SSRF protection) | The outbound media URL points to a private/internal address. Only public URLs are allowed |
| Voice messages show as text | If WeChat provides a transcription, the adapter uses the text. This is expected behavior |
| Messages appear duplicated | The adapter deduplicates by message ID. If you see duplicates, check if multiple gateway instances are running |
iLink POST ... HTTP 4xx/5xx | API error from the iLink service. Check your token validity and network connectivity |
| Terminal QR code doesn't render | Install qrcode: pip install qrcode. Alternatively, open the URL printed above the QR |