docs/channels/feishu.md
Feishu/Lark is an all-in-one collaboration platform where teams chat, share documents, manage calendars, and get work done together.
Status: production-ready for bot DMs + group chats. WebSocket is the default mode; webhook mode is optional.
Configure dmPolicy to control who can DM the bot:
"pairing" — unknown users receive a pairing code; approve via CLI"allowlist" — only users listed in allowFrom can chat (default: bot owner only)"open" — allow public DMs only when allowFrom includes "*"; with restrictive entries, only matching users can chat"disabled" — disable all DMsApprove a pairing request:
openclaw pairing list feishu
openclaw pairing approve feishu <CODE>
Group policy (channels.feishu.groupPolicy):
| Value | Behavior |
|---|---|
"open" | Respond to all messages in groups |
"allowlist" | Only respond to groups in groupAllowFrom or explicitly configured under groups.<chat_id> |
"disabled" | Disable all group messages; explicit groups.<chat_id> entries do not override this |
Default: allowlist
Mention requirement (channels.feishu.requireMention):
true — require @mention (default)false — respond without @mentionchannels.feishu.groups.<chat_id>.requireMention@all and @_all are not treated as bot mentions. A message that mentions both @all and the bot directly still counts as a bot mention.{
channels: {
feishu: {
groupPolicy: "open",
},
},
}
{
channels: {
feishu: {
groupPolicy: "open",
requireMention: true,
},
},
}
{
channels: {
feishu: {
groupPolicy: "allowlist",
// Group IDs look like: oc_xxx
groupAllowFrom: ["oc_xxx", "oc_yyy"],
},
},
}
In allowlist mode, you can also admit a group by adding an explicit groups.<chat_id> entry. Explicit entries do not override groupPolicy: "disabled". Wildcard defaults under groups.* configure matching groups, but they do not admit groups by themselves.
{
channels: {
feishu: {
groupPolicy: "allowlist",
groups: {
oc_xxx: {
requireMention: false,
},
},
},
},
}
{
channels: {
feishu: {
groupPolicy: "allowlist",
groupAllowFrom: ["oc_xxx"],
groups: {
oc_xxx: {
// User open_ids look like: ou_xxx
allowFrom: ["ou_user1", "ou_user2"],
},
},
},
},
}
<a id="get-groupuser-ids"></a>
chat_id, format: oc_xxx)Open the group in Feishu/Lark, click the menu icon in the top-right corner, and go to Settings. The group ID (chat_id) is listed on the settings page.
open_id, format: ou_xxx)Start the gateway, send a DM to the bot, then check the logs:
openclaw logs --follow
Look for open_id in the log output. You can also check pending pairing requests:
openclaw pairing list feishu
| Command | Description |
|---|---|
/status | Show bot status |
/reset | Reset the current session |
/model | Show or switch the AI model |
groupPolicy is not "disabled"openclaw logs --followim.message.receive_v1openclaw gateway statusopenclaw logs --followopenclaw gateway restart{
channels: {
feishu: {
defaultAccount: "main",
accounts: {
main: {
appId: "cli_xxx",
appSecret: "xxx",
name: "Primary bot",
tts: {
providers: {
openai: { voice: "shimmer" },
},
},
},
backup: {
appId: "cli_yyy",
appSecret: "yyy",
name: "Backup bot",
enabled: false,
},
},
},
},
}
defaultAccount controls which account is used when outbound APIs do not specify an accountId.
accounts.<id>.tts uses the same shape as messages.tts and deep-merges over
global TTS config, so multi-bot Feishu setups can keep shared provider
credentials globally while overriding only voice, model, persona, or auto mode
per account.
textChunkLimit — outbound text chunk size (default: 2000 chars)mediaMaxMb — media upload/download limit (default: 30 MB)Feishu/Lark supports streaming replies via interactive cards. When enabled, the bot updates the card in real time as it generates text.
{
channels: {
feishu: {
streaming: true, // enable streaming card output (default: true)
blockStreaming: true, // opt into completed-block streaming
},
},
}
Set streaming: false to send the complete reply in one message. blockStreaming is off by default; enable it only when you want completed assistant blocks flushed before the final reply.
Reduce the number of Feishu/Lark API calls with two optional flags:
typingIndicator (default true): set false to skip typing reaction callsresolveSenderNames (default true): set false to skip sender profile lookups{
channels: {
feishu: {
typingIndicator: false,
resolveSenderNames: false,
},
},
}
Feishu/Lark supports ACP for DMs and group thread messages. Feishu/Lark ACP is text-command driven — there are no native slash-command menus, so use /acp ... messages directly in the conversation.
{
agents: {
list: [
{
id: "codex",
runtime: {
type: "acp",
acp: {
agent: "codex",
backend: "acpx",
mode: "persistent",
cwd: "/workspace/openclaw",
},
},
},
],
},
bindings: [
{
type: "acp",
agentId: "codex",
match: {
channel: "feishu",
accountId: "default",
peer: { kind: "direct", id: "ou_1234567890" },
},
},
{
type: "acp",
agentId: "codex",
match: {
channel: "feishu",
accountId: "default",
peer: { kind: "group", id: "oc_group_chat:topic:om_topic_root" },
},
acp: { label: "codex-feishu-topic" },
},
],
}
In a Feishu/Lark DM or thread:
/acp spawn codex --thread here
--thread here works for DMs and Feishu/Lark thread messages. Follow-up messages in the bound conversation route directly to that ACP session.
Use bindings to route Feishu/Lark DMs or groups to different agents.
{
agents: {
list: [
{ id: "main" },
{ id: "agent-a", workspace: "/home/user/agent-a" },
{ id: "agent-b", workspace: "/home/user/agent-b" },
],
},
bindings: [
{
agentId: "agent-a",
match: {
channel: "feishu",
peer: { kind: "direct", id: "ou_xxx" },
},
},
{
agentId: "agent-b",
match: {
channel: "feishu",
peer: { kind: "group", id: "oc_zzz" },
},
},
],
}
Routing fields:
match.channel: "feishu"match.peer.kind: "direct" (DM) or "group" (group chat)match.peer.id: user Open ID (ou_xxx) or group ID (oc_xxx)See Get group/user IDs for lookup tips.
Full configuration: Gateway configuration
| Setting | Description | Default |
|---|---|---|
channels.feishu.enabled | Enable/disable the channel | true |
channels.feishu.domain | API domain (feishu or lark) | feishu |
channels.feishu.connectionMode | Event transport (websocket or webhook) | websocket |
channels.feishu.defaultAccount | Default account for outbound routing | default |
channels.feishu.verificationToken | Required for webhook mode | — |
channels.feishu.encryptKey | Required for webhook mode | — |
channels.feishu.webhookPath | Webhook route path | /feishu/events |
channels.feishu.webhookHost | Webhook bind host | 127.0.0.1 |
channels.feishu.webhookPort | Webhook bind port | 3000 |
channels.feishu.accounts.<id>.appId | App ID | — |
channels.feishu.accounts.<id>.appSecret | App Secret | — |
channels.feishu.accounts.<id>.domain | Per-account domain override | feishu |
channels.feishu.accounts.<id>.tts | Per-account TTS override | messages.tts |
channels.feishu.dmPolicy | DM policy | allowlist |
channels.feishu.allowFrom | DM allowlist (open_id list) | [BotOwnerId] |
channels.feishu.groupPolicy | Group policy | allowlist |
channels.feishu.groupAllowFrom | Group allowlist | — |
channels.feishu.requireMention | Require @mention in groups | true |
channels.feishu.groups.<chat_id>.requireMention | Per-group @mention override; explicit IDs also admit the group in allowlist mode | inherited |
channels.feishu.groups.<chat_id>.enabled | Enable/disable a specific group | true |
channels.feishu.textChunkLimit | Message chunk size | 2000 |
channels.feishu.mediaMaxMb | Media size limit | 30 |
channels.feishu.streaming | Streaming card output | true |
channels.feishu.blockStreaming | Completed-block reply streaming | false |
channels.feishu.typingIndicator | Send typing reactions | true |
channels.feishu.resolveSenderNames | Resolve sender display names | true |
Inbound Feishu/Lark audio messages are normalized as media placeholders instead
of raw file_key JSON. When tools.media.audio is configured, OpenClaw
downloads the voice-note resource and runs shared audio transcription before the
agent turn, so the agent receives the spoken transcript. If Feishu includes
transcript text directly in the audio payload, that text is used without another
ASR call. Without an audio transcription provider, the agent still receives a
<media:audio> placeholder plus the saved attachment, not the raw Feishu
resource payload.
Native Feishu/Lark audio bubbles use the Feishu audio message type and require
Ogg/Opus upload media (file_type: "opus"). Existing .opus and .ogg media
is sent directly as native audio. MP3/WAV/M4A and other likely audio formats are
transcoded to 48kHz Ogg/Opus with ffmpeg only when the reply requests voice
delivery (audioAsVoice / message tool asVoice, including TTS voice-note
replies). Ordinary MP3 attachments stay regular files. If ffmpeg is missing or
conversion fails, OpenClaw falls back to a file attachment and logs the reason.
For groupSessionScope: "group_topic" and "group_topic_sender", native
Feishu/Lark topic groups use the event thread_id (omt_*) as the canonical
topic session key. If a native topic starter event omits thread_id, OpenClaw
hydrates it from Feishu before routing the turn. Normal group replies that
OpenClaw turns into threads keep using the reply root message ID (om_*) so the
first turn and follow-up turn stay in the same session.