OpenKakao

watch

Real-time message monitoring with auto-reconnect.

Introduction

watch is now the event boundary for OpenKakao automation.

  • Stay local with --hook-cmd when possible.
  • Use --webhook-url only when you explicitly want message events to leave the machine.
  • Treat this as best-effort delivery, not a guaranteed queue.

Real-time events

Move from scheduled reads into event-driven workflows when latency starts to matter.

Local hooks

Keep automation close to the machine with --hook-cmd and normalized event JSON over stdin.

Webhook delivery

Forward matched events over HTTP only when you want the trust boundary to expand beyond the machine.

Best-effort, not event bus

watch reconnects and retries the socket, but it is not a queue with delivery guarantees.

This is the strongest automation surface in the CLI

watch is where reconnect behavior, signing, unattended policy, and side effects all meet. Treat it like an operator surface, not a toy notification script.

Usage

openkakao-rs watch [OPTIONS]

Options

FlagDescriptionDefault
--chat-id <ID>Filter by specific chatAll chats
--rawShow raw BSON bodyfalse
--read-receiptSend NOTIREAD for incoming messagesfalse
--max-reconnect <N>Max reconnect attempts (0 = disabled)5
--download-mediaAuto-download media attachmentsfalse
--download-dir <DIR>Directory for downloadsdownloads
--hook-cmd <CMD>Run a local shell command for matched message eventsdisabled
--webhook-url <URL>POST matched message events to a webhookdisabled
--webhook-header "Name: Value"Add an HTTP header to webhook requestsnone
--webhook-signing-secret <SECRET>Sign webhook payloads with HMAC-SHA256disabled
--hook-chat-id <ID>Restrict hook execution to specific chat IDsall chats
--hook-keyword <TEXT>Restrict hook execution to messages containing keywordall messages
--hook-type <TYPE>Restrict hook execution to specific message type codesall types
--hook-fail-fastExit watch if a hook command failsfalse
--resumeResume from last saved watch statefalse
--jsonOutput NDJSON (one JSON object per event line)false
--captureOutput unknown LOCO push packets as NDJSON for protocol reverse engineeringfalse

If you enable --read-receipt, --hook-cmd, or --webhook-url, you must also allow unattended watch side effects:

  • CLI: --unattended --allow-watch-side-effects
  • or config: mode.unattended = true and watch.allow_side_effects = true

The side-effect path is also guarded by config-backed defaults:

  • safety.min_hook_interval_secs
  • safety.min_webhook_interval_secs
  • safety.hook_timeout_secs
  • safety.webhook_timeout_secs
  • safety.allow_insecure_webhooks

By default, remote webhooks must use HTTPS. Plain HTTP is only accepted for localhost unless you explicitly loosen the policy.

High-Value Patterns

Local notifications

Good first step when you want event-driven behavior without changing the network boundary.

Review queues

Send matched events into a local file, queue, or operator process before any outbound action happens.

Signed webhook delivery

Useful when another service needs events, but only after you accept the new retention and access model.

Selective chat monitoring

Keep scope narrow with --chat-id, --hook-chat-id, keywords, and type filters.

Examples

# Watch all chats
openkakao-rs watch

# Watch specific chat with read receipts
openkakao-rs --unattended --allow-watch-side-effects watch --chat-id 382416827148557 --read-receipt

# Auto-download media
openkakao-rs watch --download-media --download-dir ./media

# Maximum reliability
openkakao-rs --unattended --allow-watch-side-effects watch --max-reconnect 20 --read-receipt --download-media

# Trigger a local command when "urgent" arrives in one chat
openkakao-rs watch \
  --unattended \
  --allow-watch-side-effects \
  --chat-id 382416827148557 \
  --hook-cmd 'jq -r .message | osascript -e "display notification (do shell script \"cat\") with title \"OpenKakao\""' \
  --hook-keyword urgent

# Deliver matched events to a webhook
openkakao-rs watch \
  --unattended \
  --allow-watch-side-effects \
  --webhook-url https://example.com/openkakao \
  --webhook-header 'Authorization: Bearer token' \
  --webhook-signing-secret 'super-secret' \
  --hook-chat-id 382416827148557 \
  --hook-type 1

Reconnect Behavior

ScenarioAction
Network errorExponential backoff with jitter (1s, 2s, 4s, ... max 32s)
Auth error (-950)Attempt token refresh, then reconnect
CHANGESVR receivedImmediate reconnect to new server
PING timeoutTreated as disconnect
Retryable error (typed)Uses OpenKakaoError::is_retryable() for classification
Max retries exceededExit
Ctrl-CSave watch state for --resume

Local Message Cache

As of v0.9.3, watch automatically persists incoming messages to a local SQLite cache at ~/.config/openkakao/messages.db. This cache is shared with the read command, meaning messages captured during a watch session become available in subsequent read calls even if the LOCO server only returns a limited history window.

The cache operates transparently — no flags required. If the database cannot be opened (e.g., disk full), watch prints a warning and continues without caching.

Watch State and Recovery

When watch is interrupted via Ctrl-C (SIGINT), it persists its position to:

~/.config/openkakao/watch_state.json

This file records the last processed logId per chat, allowing --resume to pick up where the previous session left off without replaying already-seen messages.

Use --resume for long-running or restarted watch processes:

openkakao-rs watch --resume

Recovery behavior:

  • If watch_state.json exists and --resume is set, the watcher skips messages with logIds at or below the saved watermark.
  • If the file does not exist or --resume is not set, the watcher starts fresh from the current position.
  • The state file is shared across process restarts. If you run multiple watch processes, they will overwrite each other's state. Run one watch process per machine.
  • The state file is written on graceful Ctrl-C (SIGINT) shutdown only. SIGTERM and SIGKILL (kill -9) bypass the shutdown handler, so state will not be saved.

Reconnect is transport resilience, not delivery guarantee

The socket reconnect ladder helps you stay attached to LOCO. It does not guarantee that every downstream hook or webhook side effect happened exactly once.

Output Format

Default (human-readable):

[Chat Title] Author: message text

Media messages show the attachment type:

[Chat Title] Author: [Photo] (attached)
[Chat Title] Author: [Video] video.mp4

NDJSON mode (--json)

When --json is set, each event is emitted as a single JSON line (NDJSON):

{"event_type":"message","received_at":"2026-03-10T12:00:00Z","method":"MSG","chat_id":382416827148557,"chat_name":"MemoChat","log_id":123456,"author_id":405979308,"author_nickname":"Me","message_type":1,"message":"hello","attachment":""}

SYNCMSG events include "event_type":"sync". This makes it easy to pipe into jq, log aggregators, or streaming processors.

Push event handlers

In addition to MSG (incoming messages), watch handles these server-pushed events:

Push MethodDescriptionSince
SYNCDLMSGA message was deleted by another user. Triggers hooks/webhooks.v0.9.0
SYNCACTIONA reaction was added/removed on a message. Shows in watch output.v0.9.1
SYNCREWRA message was edited (placeholder — not yet confirmed on macOS).v0.9.0
CHANGESVRServer migration request. Triggers automatic reconnect.v0.7.0

Capture mode (--capture)

When --capture is set, any LOCO push packet that doesn't have a dedicated handler is output as structured NDJSON:

{"event":"unknown_push","method":"BLSYNC","status":0,"body":{...},"timestamp":"2026-03-10T12:42:17Z"}

This is useful for protocol reverse engineering — discovering new LOCO push methods and their body structures without modifying the source code.

Hook Event Payload

When --hook-cmd is set, matched message events are delivered as JSON on stdin.

When --webhook-url is set, the same event JSON is sent as an HTTP POST with Content-Type: application/json.

When --webhook-signing-secret is set, the request also includes:

  • X-OpenKakao-Timestamp
  • X-OpenKakao-Signature

The signature format is sha256=<hex> and signs timestamp.payload.

This gives receivers an integrity check, not replay protection by itself. If you expose a webhook publicly, validate the timestamp window on the receiver side.

Environment variables are also provided:

  • OPENKAKAO_EVENT_TYPE
  • OPENKAKAO_CHAT_ID
  • OPENKAKAO_CHAT_NAME
  • OPENKAKAO_LOG_ID
  • OPENKAKAO_AUTHOR_ID
  • OPENKAKAO_AUTHOR_NICKNAME
  • OPENKAKAO_MESSAGE_TYPE
  • OPENKAKAO_MESSAGE_TYPE_LABEL

Example:

openkakao-rs watch \
  --unattended \
  --allow-watch-side-effects \
  --hook-cmd 'jq . > /tmp/openkakao-event.json' \
  --hook-chat-id 382416827148557 \
  --hook-type 1

Webhook example:

openkakao-rs watch \
  --unattended \
  --allow-watch-side-effects \
  --webhook-url https://hooks.example.com/openkakao \
  --webhook-header 'X-OpenKakao-Source: laptop' \
  --webhook-signing-secret 'super-secret' \
  --hook-keyword urgent

Signature verification example:

BODY_FILE=/tmp/openkakao-body.json
printf '%s.%s' "$HTTP_X_OPENKAKAO_TIMESTAMP" "$(cat "$BODY_FILE")" \
  | openssl dgst -sha256 -hmac 'super-secret'

Operational Boundaries

watch hooks are intentionally simple:

  • no delivery queue
  • no retry or backoff policy for webhook delivery
  • no built-in deduplication
  • no idempotency key management
  • no config file for multiple hook definitions

That is the right stopping point for now. If you need stronger guarantees, add them in your receiver or wrapper process instead of assuming the CLI is an event bus.

Where to Go Next

On this page