Skip to main content

WeCom (Enterprise WeChat)

Connect Hermes to WeCom (企业微信), Tencent's enterprise messaging platform. The adapter uses WeCom's AI Bot WebSocket gateway for real-time bidirectional communication — no public endpoint or webhook needed.

Prerequisites​

  • A WeCom organization account
  • An AI Bot created in the WeCom Admin Console
  • The Bot ID and Secret from the bot's credentials page
  • Python packages: aiohttp and httpx

Setup​

1. Create an AI Bot​

  1. Log in to the WeCom Admin Console
  2. Navigate to Applications → Create Application → AI Bot
  3. Configure the bot name and description
  4. Copy the Bot ID and Secret from the credentials page

2. Configure Hermes​

Run the interactive setup:

hermes gateway setup

Select WeCom and enter your Bot ID and Secret.

Or set environment variables in ~/.hermes/.env:

WECOM_BOT_ID=your-bot-id
WECOM_SECRET=your-secret

# Optional: restrict access
WECOM_ALLOWED_USERS=user_id_1,user_id_2

# Optional: home channel for cron/notifications
WECOM_HOME_CHANNEL=chat_id

3. Start the gateway​

hermes gateway

Features​

  • WebSocket transport — persistent connection, no public endpoint needed
  • DM and group messaging — configurable access policies
  • Per-group sender allowlists — fine-grained control over who can interact in each group
  • Media support — images, files, voice, video upload and download
  • AES-encrypted media — automatic decryption for inbound attachments
  • Quote context — preserves reply threading
  • Markdown rendering — rich text responses
  • Reply-mode streaming — correlates responses to inbound message context
  • Auto-reconnect — exponential backoff on connection drops

Configuration Options​

Set these in config.yaml under platforms.wecom.extra:

KeyDefaultDescription
bot_id—WeCom AI Bot ID (required)
secret—WeCom AI Bot Secret (required)
websocket_urlwss://openws.work.weixin.qq.comWebSocket gateway URL
dm_policyopenDM access: open, allowlist, disabled, pairing
group_policyopenGroup 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)
groups{}Per-group configuration (see below)

Access Policies​

DM Policy​

Controls who can send direct messages to the bot:

ValueBehavior
openAnyone can DM the bot (default)
allowlistOnly user IDs in allow_from can DM
disabledAll DMs are ignored
pairingPairing mode (for initial setup)
WECOM_DM_POLICY=allowlist

Group Policy​

Controls which groups the bot responds in:

ValueBehavior
openBot responds in all groups (default)
allowlistBot only responds in group IDs listed in group_allow_from
disabledAll group messages are ignored
WECOM_GROUP_POLICY=allowlist

Per-Group Sender Allowlists​

For fine-grained control, you can restrict which users are allowed to interact with the bot within specific groups. This is configured in config.yaml:

platforms:
wecom:
enabled: true
extra:
bot_id: "your-bot-id"
secret: "your-secret"
group_policy: "allowlist"
group_allow_from:
- "group_id_1"
- "group_id_2"
groups:
group_id_1:
allow_from:
- "user_alice"
- "user_bob"
group_id_2:
allow_from:
- "user_charlie"
"*":
allow_from:
- "user_admin"

How it works:

  1. The group_policy and group_allow_from controls determine whether a group is allowed at all.
  2. If a group passes the top-level check, the groups.<group_id>.allow_from list (if present) further restricts which senders within that group can interact with the bot.
  3. A wildcard "*" group entry serves as a default for groups not explicitly listed.
  4. Allowlist entries support the * wildcard to allow all users, and entries are case-insensitive.
  5. Entries can optionally use the wecom:user: or wecom:group: prefix format — the prefix is stripped automatically.

If no allow_from is configured for a group, all users in that group are allowed (assuming the group itself passes the top-level policy check).

Media Support​

Inbound (receiving)​

The adapter receives media attachments from users and caches them locally for agent processing:

TypeHow it's handled
ImagesDownloaded and cached locally. Supports both URL-based and base64-encoded images.
FilesDownloaded and cached. Filename is preserved from the original message.
VoiceVoice message text transcription is extracted if available.
Mixed messagesWeCom mixed-type messages (text + images) are parsed and all components extracted.

Quoted messages: Media from quoted (replied-to) messages is also extracted, so the agent has context about what the user is replying to.

AES-Encrypted Media Decryption​

WeCom encrypts some inbound media attachments with AES-256-CBC. The adapter handles this automatically:

  • When an inbound media item includes an aeskey field, the adapter downloads the encrypted bytes and decrypts them using AES-256-CBC with PKCS#7 padding.
  • The AES key is the base64-decoded value of the aeskey field (must be exactly 32 bytes).
  • The IV is derived from the first 16 bytes of the key.
  • This requires the cryptography Python package (pip install cryptography).

No configuration is needed — decryption happens transparently when encrypted media is received.

Outbound (sending)​

MethodWhat it sendsSize limit
sendMarkdown text messages4000 chars
send_image / send_image_fileNative image messages10 MB
send_documentFile attachments20 MB
send_voiceVoice messages (AMR format only for native voice)2 MB
send_videoVideo messages10 MB

Chunked upload: Files are uploaded in 512 KB chunks through a three-step protocol (init → chunks → finish). The adapter handles this automatically.

Automatic downgrade: When media exceeds the native type's size limit but is under the absolute 20 MB file limit, it is automatically sent as a generic file attachment instead:

  • Images > 10 MB → sent as file
  • Videos > 10 MB → sent as file
  • Voice > 2 MB → sent as file
  • Non-AMR audio → sent as file (WeCom only supports AMR for native voice)

Files exceeding the absolute 20 MB limit are rejected with an informational message sent to the chat.

Reply-Mode Stream Responses​

When the bot receives a message via the WeCom callback, the adapter remembers the inbound request ID. If a response is sent while the request context is still active, the adapter uses WeCom's reply-mode (aibot_respond_msg) with streaming to correlate the response directly to the inbound message. This provides a more natural conversation experience in the WeCom client.

If the inbound request context has expired or is unavailable, the adapter falls back to proactive message sending via aibot_send_msg.

Reply-mode also works for media: uploaded media can be sent as a reply to the originating message.

Connection and Reconnection​

The adapter maintains a persistent WebSocket connection to WeCom's gateway at wss://openws.work.weixin.qq.com.

Connection Lifecycle​

  1. Connect: Opens a WebSocket connection and sends an aibot_subscribe authentication frame with the bot_id and secret.
  2. Heartbeat: Sends application-level ping frames every 30 seconds to keep the connection alive.
  3. Listen: Continuously reads inbound frames and dispatches message callbacks.

Reconnection Behavior​

On connection loss, the adapter uses exponential backoff to reconnect:

AttemptDelay
1st retry2 seconds
2nd retry5 seconds
3rd retry10 seconds
4th retry30 seconds
5th+ retry60 seconds

After each successful reconnection, the backoff counter resets to zero. All pending request futures are failed on disconnect so callers don't hang indefinitely.

Deduplication​

Inbound messages are deduplicated using message IDs with a 5-minute window and a maximum cache of 1000 entries. This prevents double-processing of messages during reconnection or network hiccups.

All Environment Variables​

VariableRequiredDefaultDescription
WECOM_BOT_ID✅—WeCom AI Bot ID
WECOM_SECRET✅—WeCom AI Bot Secret
WECOM_ALLOWED_USERS—(empty)Comma-separated user IDs for the gateway-level allowlist
WECOM_HOME_CHANNEL——Chat ID for cron/notification output
WECOM_WEBSOCKET_URL—wss://openws.work.weixin.qq.comWebSocket gateway URL
WECOM_DM_POLICY—openDM access policy
WECOM_GROUP_POLICY—openGroup access policy

Troubleshooting​

ProblemFix
WECOM_BOT_ID and WECOM_SECRET are requiredSet both env vars or configure in setup wizard
WeCom startup failed: aiohttp not installedInstall aiohttp: pip install aiohttp
WeCom startup failed: httpx not installedInstall httpx: pip install httpx
invalid secret (errcode=40013)Verify the secret matches your bot's credentials
Timed out waiting for subscribe acknowledgementCheck network connectivity to openws.work.weixin.qq.com
Bot doesn't respond in groupsCheck group_policy setting and ensure the group ID is in group_allow_from
Bot ignores certain users in a groupCheck per-group allow_from lists in the groups config section
Media decryption failsInstall cryptography: pip install cryptography
cryptography is required for WeCom media decryptionThe inbound media is AES-encrypted. Install: pip install cryptography
Voice messages sent as filesWeCom only supports AMR format for native voice. Other formats are auto-downgraded to file.
File too large errorWeCom has a 20 MB absolute limit on all file uploads. Compress or split the file.
Images sent as filesImages > 10 MB exceed the native image limit and are auto-downgraded to file attachments.
Timeout sending message to WeComThe WebSocket may have disconnected. Check logs for reconnection messages.
WeCom websocket closed during authenticationNetwork issue or incorrect credentials. Verify bot_id and secret.