Skip to content

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.

ChannelRegionReceive modePublic inbound port?Build feature flag
Lark / FeishuInternational / ChinaWebSocket (default) or webhookWebhook mode onlychannel-lark
DingTalkChinaStream Mode WebSocketNo
WeComChinaOutbound only (Bot Webhook)No
QQ Official BotChinaBot gateway WebSocketNo
MochatSelf-hostedHTTP pollingNo

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 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.

[channels_config.lark]
app_id = "cli_xxx"
app_secret = "xxx"
encrypt_key = "" # optional: AES key for webhook event decryption
verification_token = "" # optional: webhook authenticity check
allowed_users = ["*"]
mention_only = false
receive_mode = "websocket" # websocket | webhook
port = 8081 # required for webhook mode only
use_feishu = false # legacy: routes this section to Feishu endpoints
[channels_config.feishu]
app_id = "cli_xxx"
app_secret = "xxx"
encrypt_key = "" # optional
verification_token = "" # optional
allowed_users = ["*"]
receive_mode = "websocket"
FieldTypeDefaultMeaning
app_idstringApp ID from the Lark/Feishu developer console. Required.
app_secretstringApp Secret from the developer console. Required.
encrypt_keystringAES key used to decrypt encrypted webhook event payloads. Optional; webhook mode only.
verification_tokenstringToken checked against the token field on the webhook URL-verification challenge. Optional; webhook mode only.
allowed_usersarray[]Open IDs (or union IDs) allowed to interact. [] denies all; ["*"] allows everyone.
mention_onlyboolfalseIn group chats, only respond when the bot is @-mentioned. Direct messages are always processed.
receive_modestring"websocket""websocket" (long connection, no inbound port) or "webhook" (HTTP callback, needs a public URL).
portnumberLocal HTTP listener port. Required when receive_mode = "webhook"; ignored in WebSocket mode.
use_feishuboolfalseLark section only. Legacy switch: true routes [channels_config.lark] to the Feishu endpoints. Prefer a dedicated [channels_config.feishu] section instead.
proxy_urlstringOptional per-channel HTTP/SOCKS5 proxy.
transcription.*tableVoice-to-text for audio messages (25 MB limit). See Voice, TTS & transcription.

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 = true under [channels_config.lark]. This is still supported but discouraged. On startup Revka logs Using 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.

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 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.

[channels_config.dingtalk]
client_id = "ding-app-key"
client_secret = "ding-app-secret"
allowed_users = ["*"]
proxy_url = ""
FieldTypeDefaultMeaning
client_idstringApp Key from the DingTalk developer console. Required.
client_secretstringApp Secret from the developer console. Required.
allowed_usersarray[]DingTalk staff IDs (senderStaffId) allowed to interact. [] denies all; ["*"] allows everyone.
proxy_urlstringOptional 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 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.

[channels_config.wecom]
webhook_key = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
allowed_users = ["*"]
FieldTypeDefaultMeaning
webhook_keystringThe key from your WeCom group bot’s webhook URL. Required.
allowed_usersarray[]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.

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.

[channels_config.qq]
app_id = "qq-app-id"
app_secret = "qq-app-secret"
allowed_users = ["*"]
proxy_url = ""
FieldTypeDefaultMeaning
app_idstringApp ID from the QQ Bot developer console. Required.
app_secretstringApp Secret from the developer console. Required.
allowed_usersarray[]QQ user IDs (C2C user_openid) allowed to interact. [] denies all; ["*"] allows everyone.
proxy_urlstringOptional per-channel HTTP/SOCKS5 proxy.

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 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.

[channels_config.mochat]
api_url = "https://mochat.example.com"
api_token = "mochat-api-token"
allowed_users = ["*"]
poll_interval_secs = 5
FieldTypeDefaultMeaning
api_urlstringMochat API base URL. Required. A trailing slash is stripped automatically.
api_tokenstringAPI token, sent as Authorization: Bearer <token>. Required.
allowed_usersarray[]Mochat user IDs (fromUserId) allowed to interact. [] denies all; ["*"] allows everyone.
poll_interval_secsnumber5Seconds 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.

Only Lark/Feishu exposes a configurable receive mode. The choice is set with receive_mode and changes how inbound events reach Revka:

ModeHow it worksInbound portWhen to use
websocket (default)Revka opens a persistent WSS long connection to the Lark/Feishu open platform and receives events as protocol frames.NoneDefault. Simplest to operate — works behind NAT with no public URL or reverse proxy.
webhookRevka 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 port to be set — starting webhook mode without it fails with Lark webhook mode requires port to be set in [channels_config.lark]. The endpoint answers the platform’s URL-verification challenge (validating verification_token when 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.

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:

Terminal window
cargo build --release --features channel-lark

If 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.

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.