website/docs/user-guide/messaging/weixin.md
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.
:::info This adapter is for personal WeChat accounts (微信). If you need enterprise/corporate WeChat, see the WeCom adapter instead. :::
:::warning iLink bot identity — ordinary WeChat groups may not work
QR login connects Hermes to an iLink bot identity (e.g. [email protected]), not a fully scriptable ordinary personal WeChat account. Consequences:
@-mentions of the personal account used for QR login) to the gateway for most bot-type accounts.@-mentioning the personal WeChat account used to scan the QR code is not the same as @-mentioning the iLink bot — the bot is a separate identity.WEIXIN_GROUP_POLICY / WEIXIN_GROUP_ALLOWED_USERS settings below only take effect when iLink actually returns group events for your account type. If it doesn't, group messages will never reach Hermes regardless of policy.In practice, most deployments only get DMs to the iLink bot working reliably. If group delivery doesn't work after configuration, the limitation is on the iLink side, not in Hermes. The gateway logs a WARNING at startup whenever WEIXIN_GROUP_POLICY is set to anything other than disabled.
:::
aiohttp and cryptographymessaging extraInstall the required dependencies:
pip install aiohttp cryptography
# Optional: for terminal QR code display
pip install hermes-agent[messaging]
The easiest way to connect your WeChat account is through the interactive setup:
hermes gateway setup
Select Weixin when prompted. The wizard will:
~/.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.
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: restore legacy multiline splitting behavior
# WEIXIN_SPLIT_MULTILINE_MESSAGES=true
# Optional: home channel for cron/notifications
WEIXIN_HOME_CHANNEL=chat_id
WEIXIN_HOME_CHANNEL_NAME=Home
hermes gateway
The adapter will restore saved credentials, connect to the iLink API, and begin long-polling for messages.
hermes gateway setupSet 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) |
split_multiline_messages | false | When true, split multi-line replies into multiple chat messages (legacy behavior). When false, keep multi-line replies as one message unless they exceed the length limit. |
text_batch_delay_seconds | 3.0 | Quiet period (seconds) before a buffered burst of rapid text messages is flushed as one combined request. iLink delivers messages individually, so this debounce avoids one agent invocation per fragment. Set 0 to dispatch each message immediately. |
text_batch_split_delay_seconds | 5.0 | Extended flush delay used when the latest fragment is near the split threshold (long messages iLink may have chunked). |
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
WEIXIN_ALLOWED_USERS is an inbound filter, not an invitation system. QR
login connects one iLink bot identity to Hermes. Other people do not scan the
Hermes QR code with their own accounts; they must message the connected iLink
bot/contact through WeChat, and Hermes will process the DM only if the sender's
Weixin user ID is present in WEIXIN_ALLOWED_USERS.
A practical setup flow is:
hermes gateway setup and note the connected iLink bot
account.WEIXIN_ALLOWED_USERS, then restart the gateway.If only the account that scanned the QR code can talk to Hermes, verify that the other users are messaging the iLink bot identity itself, not the personal WeChat account that performed the QR login. The iLink bot is a separate identity, and ordinary WeChat contact/group routing can be limited by Tencent's iLink behavior.
Controls which groups the bot responds in when iLink delivers group events for the connected identity. For QR-login iLink bot identities (e.g. [email protected]), group events are typically not delivered at all, so this policy may have no effect — see the iLink bot limitation warning at the top of the page.
| Value | Behavior |
|---|---|
open | Bot responds in all groups (if events are delivered) |
allowlist | Bot only responds in group IDs listed in group_allow_from (if events are delivered) |
disabled | All group messages are ignored (default) |
WEIXIN_GROUP_POLICY=allowlist
# NOTE: this is a comma-separated list of group chat IDs, NOT member user IDs,
# despite the variable name containing "USERS". Keep this in mind when configuring.
WEIXIN_GROUP_ALLOWED_USERS=group_id_1,group_id_2
:::note
The default group policy is disabled for Weixin (unlike WeCom where it defaults to open). This is intentional — personal WeChat accounts may be in many groups, and iLink bot identities typically can't receive ordinary WeChat group messages at all. The gateway logs a WARNING at startup if you set WEIXIN_GROUP_POLICY to anything other than disabled.
:::
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.
WeChat media files are transferred through an encrypted CDN. The adapter handles this transparently:
encrypted_query_param URLs, then decrypted with AES-128-ECB using the per-file key provided in the message payload.cryptography Python package.No configuration is needed — encryption and decryption happen automatically.
| 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:
getuploadurl)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:
~/.hermes/weixin/accounts/<account_id>.context-tokens.jsonThis ensures reply continuity even after gateway restarts.
WeChat clients connected through the iLink Bot API can render Markdown directly, so the adapter preserves Markdown instead of rewriting it:
#, ##, ...)Messages are delivered as a single chat message whenever they fit within the platform limit. Only oversized payloads are split for delivery:
The adapter shows typing status in the WeChat client:
typing_ticket via the getconfig APIsend_typing sends a typing-start signal; stop_typing sends a typing-stop signalThe adapter uses HTTP long-polling (not WebSocket) to receive messages:
getupdates with a 35-second timeout; the server holds the request until messages arrive or the timeout expiresasyncio.create_taskget_updates_buf) is saved to disk so the adapter resumes from the correct position after restartsOn 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) |
Inbound messages are deduplicated using message IDs with a 5-minute window. This prevents double-processing during network hiccups or overlapping poll responses.
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.
| 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 chat IDs (not member user IDs) for group allowlist. The variable name is legacy — it expects group IDs, not user IDs. |
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) |
| 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 — but note that QR-login iLink bot identities ([email protected]) typically cannot receive ordinary WeChat group messages at all. If the gateway logs show no raw inbound events for group messages, the limitation is on the iLink side, not in Hermes. |
| 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 | Reinstall with the messaging extra: pip install hermes-agent[messaging]. Alternatively, open the URL printed above the QR |