lark-event-subscribe.md 9.9 KB

event +subscribe

Prerequisite: Read ../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

# 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 <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 <regex> 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 <dir> No Write each event as an individual file: {type}_{id}_{ts}.json
--route <spec> 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:

{"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:

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

Agent Pipeline Examples

Listen for messages and reply with Claude

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

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