Lark, Feishu, DingTalk, WeCom, QQ & Mochat
Asian enterprise and consumer messaging platforms with region and feature-flag notes.
This page is the reference for Revka’s Asian enterprise and consumer messaging channels: Lark (international) and Feishu (China), DingTalk, WeCom (Enterprise WeChat), the QQ Official Bot, and Mochat. Use it when you need exact config keys, region routing, receive-mode behavior, and the one build flag (channel-lark) that gates Lark/Feishu. For step-by-step bot creation on each platform’s developer console, follow the platform’s own onboarding, then bring the credentials here.
All of these channels are configured under [channels_config] in ~/.revka/config.toml. Edit the file directly or run revka onboard --channels-only, then restart the daemon to apply changes. For the shared channel trait, delivery modes, and allowlist model, see Channels overview.
At a glance
Section titled “At a glance”| Channel | Region | Receive mode | Public inbound port? | Build feature flag |
|---|---|---|---|---|
| Lark / Feishu | International / China | WebSocket (default) or webhook | Webhook mode only | channel-lark |
| DingTalk | China | Stream Mode WebSocket | No | — |
| WeCom | China | Outbound only (Bot Webhook) | No | — |
| QQ Official Bot | China | Bot gateway WebSocket | No | — |
| Mochat | Self-hosted | HTTP polling | No | — |
Lark/Feishu is the only channel here that is feature-gated at build time. The other four are always compiled in. Lark/Feishu in WebSocket mode, DingTalk, QQ, and Mochat all connect out to the platform, so none of them needs an inbound port — only Lark/Feishu in webhook mode opens a local HTTP listener.
Lark / Feishu
Section titled “Lark / Feishu”Lark and Feishu are the same product on two regional endpoints. Revka models them as a single channel implementation with a platform switch:
- Lark (international) routes to
open.larksuite.com. - Feishu (China) routes to
open.feishu.cn.
The channel supports two receive modes (see WebSocket vs webhook receive modes), locale-aware acknowledgment reactions, typing indicators, audio transcription (25 MB limit), image and file ingestion, and automatic tenant_access_token refresh.
Configure Lark (international)
Section titled “Configure Lark (international)”[channels_config.lark]app_id = "cli_xxx"app_secret = "xxx"encrypt_key = "" # optional: AES key for webhook event decryptionverification_token = "" # optional: webhook authenticity checkallowed_users = ["*"]mention_only = falsereceive_mode = "websocket" # websocket | webhookport = 8081 # required for webhook mode onlyuse_feishu = false # legacy: routes this section to Feishu endpointsConfigure Feishu (preferred for China)
Section titled “Configure Feishu (preferred for China)”[channels_config.feishu]app_id = "cli_xxx"app_secret = "xxx"encrypt_key = "" # optionalverification_token = "" # optionalallowed_users = ["*"]receive_mode = "websocket"| Field | Type | Default | Meaning |
|---|---|---|---|
app_id | string | — | App ID from the Lark/Feishu developer console. Required. |
app_secret | string | — | App Secret from the developer console. Required. |
encrypt_key | string | — | AES key used to decrypt encrypted webhook event payloads. Optional; webhook mode only. |
verification_token | string | — | Token checked against the token field on the webhook URL-verification challenge. Optional; webhook mode only. |
allowed_users | array | [] | Open IDs (or union IDs) allowed to interact. [] denies all; ["*"] allows everyone. |
mention_only | bool | false | In group chats, only respond when the bot is @-mentioned. Direct messages are always processed. |
receive_mode | string | "websocket" | "websocket" (long connection, no inbound port) or "webhook" (HTTP callback, needs a public URL). |
port | number | — | Local HTTP listener port. Required when receive_mode = "webhook"; ignored in WebSocket mode. |
use_feishu | bool | false | Lark section only. Legacy switch: true routes [channels_config.lark] to the Feishu endpoints. Prefer a dedicated [channels_config.feishu] section instead. |
proxy_url | string | — | Optional per-channel HTTP/SOCKS5 proxy. |
transcription.* | table | — | Voice-to-text for audio messages (25 MB limit). See Voice, TTS & transcription. |
Lark vs Feishu: which section to use
Section titled “Lark vs Feishu: which section to use”There are two ways to reach the Feishu (China) endpoint, and Revka treats them with a defined precedence:
- Preferred: add a dedicated
[channels_config.feishu]section. It always targets the Feishu endpoints and registers as Feishu. - Legacy: set
use_feishu = trueunder[channels_config.lark]. This is still supported but discouraged. On startup Revka logsUsing legacy [channels_config.lark].use_feishu=true compatibility path; prefer [channels_config.feishu].
If you configure both a [channels_config.feishu] section and use_feishu = true on the Lark section, the legacy Lark-side Feishu fallback is ignored (Revka logs a warning) and the dedicated Feishu section wins. A plain [channels_config.lark] section with use_feishu = false registers as Lark and targets the international endpoints.
Receiving and replying
Section titled “Receiving and replying”Inbound text, rich-text (post), image, file, and audio messages are decoded. Images are inlined as base64 data markers (5 MiB cap, PNG/JPEG/GIF/WebP/BMP). Text-like files are inlined (512 KiB cap); larger or binary files become attachment summaries. Audio is transcribed when a [transcription] section is configured. On each accepted message the bot adds a locale-aware acknowledgment reaction (distinct emoji sets for zh-CN, zh-TW, en, and ja).
Replies are sent as interactive Card JSON 2.0 markdown so headings, tables, blockquotes, and inline code render. Long replies are split on line boundaries to fit the card size limit. The bot caches its tenant_access_token and refreshes it automatically; on an expired-token response (business code 99991663) or a 401, it invalidates the token and retries once.
DingTalk
Section titled “DingTalk”DingTalk runs in Stream Mode. Revka registers a connection with the DingTalk gateway using client_id and client_secret, receives a WebSocket endpoint plus a ticket, and listens for chatbot callbacks over that long connection. No inbound port is required.
Replies are special on DingTalk: there is no global send API. Each incoming event carries a per-message session webhook URL, which Revka caches per chat (and per sender) and reuses to post the reply as a markdown message. Because of this, the bot can only reply to a conversation after the user has sent at least one message in it — if you try to send before a session webhook exists, the send fails with No session webhook found for chat <id>. The user must send a message first to establish a session.
Configure DingTalk
Section titled “Configure DingTalk”[channels_config.dingtalk]client_id = "ding-app-key"client_secret = "ding-app-secret"allowed_users = ["*"]proxy_url = ""| Field | Type | Default | Meaning |
|---|---|---|---|
client_id | string | — | App Key from the DingTalk developer console. Required. |
client_secret | string | — | App Secret from the developer console. Required. |
allowed_users | array | [] | DingTalk staff IDs (senderStaffId) allowed to interact. [] denies all; ["*"] allows everyone. |
proxy_url | string | — | Optional per-channel HTTP/SOCKS5 proxy. |
Both private (1:1) and group conversations are supported. Private chats are keyed by the sender’s staff ID; group chats are keyed by conversationId. The channel responds to gateway SYSTEM pings to keep the connection alive and acknowledges each event before dispatching it to the agent.
WeCom (Enterprise WeChat)
Section titled “WeCom (Enterprise WeChat)”WeCom is configured here as a Bot Webhook, which is send-only. Revka posts text messages to https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=<webhook_key> and treats errcode: 0 as success. The listen() side is a keepalive no-op — the bot does not receive messages over this channel.
Configure WeCom
Section titled “Configure WeCom”[channels_config.wecom]webhook_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"allowed_users = ["*"]| Field | Type | Default | Meaning |
|---|---|---|---|
webhook_key | string | — | The key from your WeCom group bot’s webhook URL. Required. |
allowed_users | array | [] | Reserved for future inbound support; not used while the channel is send-only. |
The health check posts a small health_check text message to the webhook, so a passing health check sends a real (if minimal) message to the bot’s chat.
QQ Official Bot
Section titled “QQ Official Bot”The QQ Official Bot uses Tencent’s bot gateway, a Discord-like WebSocket protocol. Revka authenticates against getAppAccessToken with app_id and app_secret, fetches the gateway URL, and identifies with intents for C2C (direct) and group @-messages. It supports gateway resume (opcode 6) using the last session ID and sequence number, and acknowledges the gateway Hello (opcode 10) heartbeat. No inbound port is required.
Configure QQ
Section titled “Configure QQ”[channels_config.qq]app_id = "qq-app-id"app_secret = "qq-app-secret"allowed_users = ["*"]proxy_url = ""| Field | Type | Default | Meaning |
|---|---|---|---|
app_id | string | — | App ID from the QQ Bot developer console. Required. |
app_secret | string | — | App Secret from the developer console. Required. |
allowed_users | array | [] | QQ user IDs (C2C user_openid) allowed to interact. [] denies all; ["*"] allows everyone. |
proxy_url | string | — | Optional per-channel HTTP/SOCKS5 proxy. |
Media and reply limits
Section titled “Media and reply limits”Outbound media uses [IMAGE:...], [VIDEO:...], [VOICE:...], and [FILE:...] markers, with an upload cache (up to 500 entries) that avoids re-uploading the same file within its TTL. Maximum media upload size is 10 MB.
QQ voice is natively limited to .wav, .mp3, and .silk; an [AUDIO:...] / [VOICE:...] marker pointing at any other format degrades to a plain file upload rather than a playable voice message.
Mochat
Section titled “Mochat”Mochat integrates with the open-source Mochat customer-service platform over its HTTP API. It is polling-based — there is no push. Revka polls GET <api_url>/api/message/receive on a fixed interval, deduplicates messages (10,000-entry set), and replies via POST <api_url>/api/message/send with a Bearer token.
Configure Mochat
Section titled “Configure Mochat”[channels_config.mochat]api_url = "https://mochat.example.com"api_token = "mochat-api-token"allowed_users = ["*"]poll_interval_secs = 5| Field | Type | Default | Meaning |
|---|---|---|---|
api_url | string | — | Mochat API base URL. Required. A trailing slash is stripped automatically. |
api_token | string | — | API token, sent as Authorization: Bearer <token>. Required. |
allowed_users | array | [] | Mochat user IDs (fromUserId) allowed to interact. [] denies all; ["*"] allows everyone. |
poll_interval_secs | number | 5 | Seconds between receive polls. |
The poller tracks the last seen message ID and passes it as since_id on the next request. Send success is recognized by a response code of 0 or 200. The health check hits GET <api_url>/api/health.
WebSocket vs webhook receive modes
Section titled “WebSocket vs webhook receive modes”Only Lark/Feishu exposes a configurable receive mode. The choice is set with receive_mode and changes how inbound events reach Revka:
| Mode | How it works | Inbound port | When to use |
|---|---|---|---|
websocket (default) | Revka opens a persistent WSS long connection to the Lark/Feishu open platform and receives events as protocol frames. | None | Default. Simplest to operate — works behind NAT with no public URL or reverse proxy. |
webhook | Revka runs a local HTTP callback server on port; the platform POSTs events to it (URL-verification challenge, then im.message.receive_v1 events). | Yes (port) | Use only when you cannot hold a long connection, or your org mandates HTTP callbacks. Requires a public HTTPS endpoint. |
Notes on each mode:
- WebSocket sends an immediate ping to calibrate the server’s
ping_interval, ACKs each event frame within the platform’s 3-second window, reassembles multi-fragment payloads, deduplicates message IDs seen in the last ~30 minutes, and reconnects on heartbeat timeout (300 s). - Webhook requires
portto be set — starting webhook mode without it fails withLark webhook mode requires port to be set in [channels_config.lark]. The endpoint answers the platform’s URL-verification challenge (validatingverification_tokenwhen set) before it will receive live events. Because the platform must reach your gateway, expose it over HTTPS — see Expose your gateway with a tunnel.
DingTalk and QQ are always WebSocket (no configuration), WeCom is always outbound-only, and Mochat is always polling — none of them takes a receive_mode.
The channel-lark feature flag
Section titled “The channel-lark feature flag”Lark/Feishu support is compiled behind the Cargo feature channel-lark. Only the Lark/Feishu channel is gated this way; DingTalk, WeCom, QQ, and Mochat have no feature flag and are always available.
Build with Lark/Feishu explicitly:
cargo build --release --features channel-larkIf you build with --no-default-features and a hand-picked feature list, add channel-lark back when you want Lark or Feishu. When the flag is absent and a Lark/Feishu section is present in config, Revka does not start the channel and logs that support is disabled in this build. For the complete list of channel feature flags (Matrix, Lark, Nostr, WhatsApp Web, Voice Wake), see Cargo feature flags & ADRs.
Allowlist semantics
Section titled “Allowlist semantics”Every channel on this page gates inbound senders with allowed_users:
- An empty list (
[]) denies all senders. - A single
"*"entry allows all senders. - Otherwise, only listed IDs are accepted — Lark/Feishu open IDs, DingTalk staff IDs, QQ user IDs, or Mochat user IDs respectively.
WeCom is send-only, so its allowed_users is reserved and currently unused. Start any new channel with allowed_users = ["*"] to confirm delivery, then tighten to explicit IDs.
Related pages
Section titled “Related pages”- Channels overview — the channel trait, delivery modes, and allowlist model.
- Connect a messaging channel — the general flow for enabling and testing a channel.
- Expose your gateway with a tunnel — give Lark/Feishu webhook mode a public HTTPS URL.
- Webhook ingress — gateway webhook endpoints and request handling.
- Voice, TTS & transcription — audio-attachment transcription used by Lark/Feishu and QQ.
- China-region providers — pairing these channels with China-region model providers.
- Cargo feature flags & ADRs — the
channel-larkflag and other build options.