# event +subscribe > **Prerequisite:** Read [`../lark-shared/SKILL.md`](../../lark-shared/SKILL.md) first to understand authentication, global parameters, and safety rules. Subscribe to Lark events via WebSocket long connection, outputting NDJSON to stdout. Supports compact (agent-friendly) format, regex-based routing, and file output. **Identity / Risk:** - **Identity**: bot-only — uses App ID + App Secret to establish the WebSocket connection. No user login needed (`lark-cli config init` is sufficient). - **Risk**: read — this is a read-only subscription command. It receives events but does not modify any resources. **Platform-side configuration** (must be done in the Lark Open Platform console): 1. Events & Callbacks → Subscription method → Select "Use long connection to receive events" 2. Add the events you need (e.g. `im.message.receive_v1`) 3. Enable the corresponding permissions (e.g. `im:message:receive_as_bot`) ## Commands ```bash # Subscribe to all registered events (catch-all mode, 25 common event types) lark-cli event +subscribe # Subscribe to specific event types only lark-cli event +subscribe --event-types im.message.receive_v1 # Subscribe to multiple event types lark-cli event +subscribe --event-types im.message.receive_v1,calendar.calendar.event.changed_v4 # Client-side regex filter (applied after SDK receives events) lark-cli event +subscribe --filter "^im\." # Agent-friendly format (parse content, strip noise fields) lark-cli event +subscribe --event-types im.message.receive_v1 --compact --quiet # Pretty-print JSON output lark-cli event +subscribe --json # Write each event to a file lark-cli event +subscribe --output-dir ./events # Route events to different directories by regex lark-cli event +subscribe \ --route '^im\.message=dir:./im/' \ --route '^contact\.=dir:./contacts/' # Route IM events to files, other events to stdout lark-cli event +subscribe --route '^im\.=dir:./im-events/' # Preview configuration without connecting lark-cli event +subscribe --dry-run ``` ## Parameters | Parameter | Required | Description | |------|------|------| | `--event-types ` | No | Comma-separated event types to register with the SDK dispatcher. Only these types will be received. Omit for catch-all mode (24 common types) | | `--filter ` | No | Client-side regex filter on event_type, applied after the SDK delivers events. Can be combined with `--event-types` | | `--compact` | No | Agent-friendly output: flatten structure, extract human-readable content, strip noise fields (schema, token, tenant_key, app_id) | | `--json` | No | Pretty-print JSON instead of NDJSON (one line per event) | | `--output-dir ` | No | Write each event as an individual file: `{type}_{id}_{ts}.json` | | `--route ` | No | Regex-based event routing. Format: `regex=dir:./path`. Repeatable. Unmatched events fall through to `--output-dir` or stdout | | `--quiet` | No | Suppress stderr status messages | | `--force` | No | Bypass single-instance lock. **UNSAFE**: the server splits events randomly across connections, each instance only receives a subset | | `--dry-run` | No | Print configuration only, do not connect to WebSocket | ## Output Formats ### Default (raw NDJSON) One event per line, all fields included: ```json {"schema":"2.0","header":{"event_id":"xxx","event_type":"im.message.receive_v1","create_time":"1773491924409","app_id":"cli_xxx"},"event":{"message":{"chat_id":"oc_xxx","content":"{\"text\":\"Hello\"}","message_id":"om_xxx","message_type":"text"},"sender":{"sender_id":{"open_id":"ou_xxx"},"sender_type":"user"}}} ``` ### `--compact` (agent-friendly) Flattened key-value output with semantic fields. The exact fields depend on the event type and its processor. **IM message events** (`im.message.receive_v1`) have deep compact processing: ```json {"type":"im.message.receive_v1","id":"om_xxx","message_id":"om_xxx","chat_id":"oc_xxx","chat_type":"p2p","message_type":"text","content":"Hello","sender_id":"ou_xxx","create_time":"1773491924409","timestamp":"1773491924409"} ``` - `event.message.content` (double-encoded JSON like `"{\"text\":\"Hello\"}"`) is parsed and converted to human-readable text via convertlib → output as `content` - `event.sender.sender_id.open_id` → flattened to `sender_id` - `schema`, `token`, `tenant_key`, `app_id` stripped - Supports all message types: text, post, image, file, card, etc. **Other IM events** (reactions, chat member changes, chat updates) also have specialized compact processors with relevant semantic fields. **Non-IM events** (contact, calendar, approval, task, drive, application) use the generic compact processor: - Parses the event payload as a flat map - Injects `type` (event_type), `event_id`, and `timestamp` from the event header - All original event fields are preserved as-is Agent pipelines should **always use `--compact --quiet`**. ## Catch-all Event Types The following 24 event types are registered in catch-all mode (when `--event-types` is omitted): ### IM | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `im.message.receive_v1` | Receive message | `im:message:receive_as_bot` | | `im.message.message_read_v1` | Message read | `im:message:receive_as_bot` | | `im.message.reaction.created_v1` | Reaction added | `im:message:receive_as_bot` | | `im.message.reaction.deleted_v1` | Reaction removed | `im:message:receive_as_bot` | | `im.chat.member.bot.added_v1` | Bot added to chat | `im:chat:readonly` | | `im.chat.member.bot.deleted_v1` | Bot removed from chat | `im:chat:readonly` | | `im.chat.member.user.added_v1` | User added to chat | `im:chat:readonly` | | `im.chat.member.user.withdrawn_v1` | User add withdrawn | `im:chat:readonly` | | `im.chat.member.user.deleted_v1` | User removed from chat | `im:chat:readonly` | | `im.chat.updated_v1` | Chat info updated | `im:chat:readonly` | | `im.chat.disbanded_v1` | Chat disbanded | `im:chat:readonly` | ### Contact | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `contact.user.created_v3` | User created | `contact:user.base:readonly` | | `contact.user.updated_v3` | User updated | `contact:user.base:readonly` | | `contact.user.deleted_v3` | User deleted | `contact:user.base:readonly` | | `contact.department.created_v3` | Department created | `contact:department.base:readonly` | | `contact.department.updated_v3` | Department updated | `contact:department.base:readonly` | | `contact.department.deleted_v3` | Department deleted | `contact:department.base:readonly` | ### Calendar | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `calendar.calendar.acl.created_v4` | Calendar ACL created | `calendar:calendar.acl:readonly` | | `calendar.calendar.event.changed_v4` | Calendar event changed | `calendar:calendar:readonly` | ### Approval | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `approval.approval.updated` | Approval status updated | `approval:approval:readonly` | ### Application | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `application.application.visibility.added_v6` | App visibility added | `application:application.app_visibility:readonly` | ### Task | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `task.task.update_tenant_v1` | Task updated (tenant) | `task:task:readonly` | | `task.task.update_user_access_v2` | Task updated (user access) | `task:task:readonly` | | `task.task.comment_updated_v1` | Task comment updated | `task:task:readonly` | ### Drive | Event Type | Description | Required Scope | |-----------|-------------|---------------| | `drive.notice.comment_add_v1` | Drive comment added | `drive:drive:readonly` | See the full list at [Lark Event List](https://open.feishu.cn/document/server-docs/event-subscription-guide/event-list). ## Agent Pipeline Examples ### Listen for messages and reply with Claude ```bash lark-cli event +subscribe \ --event-types im.message.receive_v1 --compact --quiet \ | while IFS= read -r line; do content=$(echo "$line" | jq -r '.content // empty') message_id=$(echo "$line" | jq -r '.message_id // empty') [[ -z "$content" ]] && continue # Generate reply with Claude answer=$(claude -p "Reply concisely: $content" < /dev/null 2>/dev/null) # Reply as bot reply_data=$(jq -n --arg t "$answer" '{msg_type:"text",content:({text:$t}|tojson)}') lark-cli api POST "/open-apis/im/v1/messages/$message_id/reply" \ --data "$reply_data" --as bot --format data done ``` ### Listen for messages and log to a Lark document ```bash lark-cli event +subscribe \ --event-types im.message.receive_v1 --compact --quiet \ | while IFS= read -r line; do content=$(echo "$line" | jq -r '.content // empty') [[ -z "$content" ]] && continue lark-cli docs +update --doc "DOC_URL" --mode append --markdown "- $content" done ``` ## Notes - **Events must be configured in the Open Platform console** — the CLI cannot dynamically subscribe to event types - `--event-types` controls which types the SDK dispatcher registers for. Unregistered types are silently dropped even if the server sends them. Omitting this flag registers 24 common types (catch-all) - `--filter` is a pure client-side regex filter applied after the SDK delivers events. It does not affect which events are registered - `--force` bypasses the single-instance lock per app. Without it, only one `+subscribe` process is allowed per app to prevent the server from splitting events across connections - WebSocket auto-reconnects on disconnection (SDK built-in) - `Ctrl+C` gracefully shuts down and prints the total event count - Reply to messages with `lark-cli api ... --as bot` — no user login needed ## References - [lark-im](../../lark-im/SKILL.md) — Messaging commands - [lark-doc-update](../../lark-doc/references/lark-doc-update.md) — Update Lark documents - [lark-shared](../../lark-shared/SKILL.md) — Authentication and global parameters