npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

whatsapp-monitor

v1.7.0

Published

Read-only WhatsApp monitoring service using Baileys, with a shell-command notify pipeline

Readme

whatsapp-monitor

A read-only WhatsApp monitoring service using Baileys. Runs as a persistent listener, filters by an allowlist, and hands batched notifications to a command of your choice — a webhook, an OpenClaw agent, a log, anything that reads JSON from stdin.

This is monitoring, not a chat channel. It surfaces messages from an external WhatsApp account into another tool. It cannot send messages.

Note for OpenClaw agents: @openclaw/whatsapp is a bidirectional channel — it's how an OpenClaw agent talks to users over WhatsApp. This tool is different: it observes messages on a separate (typically personal) WhatsApp account read-only, so an agent can react to them without being able to send from that account. The two can coexist.

Why a persistent service

Baileys is built around a live WebSocket session, not a pull API:

  • messaging-history.set / fetchMessageHistory are for initial/offline recovery, not reliable cron-style ingestion.
  • WhatsApp drops messages during reconnects, has limited server-side retention, and logs out linked devices after ~14 days of phone inactivity.
  • Baileys v7 explicitly says production apps must keep a persistent client.

So this tool is designed to run as a long-lived process under launchd / systemd / pm2. The one-shot messages and events commands are still there for debugging.

Install

npm install -g whatsapp-monitor

From source:

git clone https://github.com/amitm02/whatsapp-monitor
cd whatsapp-monitor
npm install
npm run build
npm link

Requires Node.js >= 18.

Quick start

  1. Link WhatsApp (one-time, interactive):

    whatsapp-monitor link

    Scan the QR with WhatsApp → Settings → Linked Devices → Link a Device. For headless/agent setups use the pairing code flow: whatsapp-monitor link --code --phone 12345678901.

  2. Pick what to monitor:

    whatsapp-monitor groups
    whatsapp-monitor config add [email protected]
  3. Configure a notifier in ~/.whatsapp-monitor/config.json. Pick one of the two modes:

    // Structured mode — first-class OpenClaw integration, no shell quoting
    {
      "allowedGroups": ["[email protected]"],
      "allowedContacts": [],
      "notify": {
        "kind": "openclaw-agent",
        "agent": "main",
        "sessionIdTemplate": "wa-monitor-{date}",
        "behaviorFile": "~/.whatsapp-monitor/behavior.md",
        "quietPeriodSec": 30,
        "timeoutSec": 120
      }
    }
    // Command mode — any shell command that reads JSON on stdin
    {
      "allowedGroups": ["[email protected]"],
      "allowedContacts": [],
      "notify": {
        "command": "tee -a ~/whatsapp-digest.jsonl",
        "quietPeriodSec": 30,
        "timeoutSec": 120
      }
    }

    kind and command are mutually exclusive.

  4. Verify the pipeline before connecting WhatsApp:

    whatsapp-monitor notify test
  5. Run the service:

    whatsapp-monitor run

    Leave it running. See running as a service for launchd/systemd.

How notifications work

run keeps a persistent WhatsApp connection, filters messages through the allowlist, and buffers them per chat. When a chat has been quiet for notify.quietPeriodSec seconds, one batched notification is emitted for that chat.

Each notification:

  1. Is appended to ~/.whatsapp-monitor/notifications.jsonl (durable record).
  2. Runs notify.command via sh -c, with the JSON payload written to the child's stdin.
  3. Exposes a few convenience env vars: WAM_CHAT_ID, WAM_CHAT_NAME, WAM_IS_GROUP, WAM_MESSAGE_COUNT, WAM_FIRST_TS, WAM_LAST_TS.

Non-zero exits are logged to stderr but don't crash the service. Invocations for the same chat are serialized (no overlap).

Payload shape

{
  "chatId": "[email protected]",
  "chatName": "Family Group",
  "isGroup": true,
  "firstTimestamp": 1713300000000,
  "lastTimestamp": 1713300180000,
  "messageCount": 3,
  "senderCount": 2,
  "messages": [
    { "id": "...", "sender": "...", "senderName": "Mom", "text": "...", "timestamp": 1713300000000, "type": "text", "upsertType": "notify", "chatId": "[email protected]", "isGroup": true },
    // ...
  ]
}

When quietPeriodSec: 0, the same shape is emitted with messageCount: 1 per message — consumers don't need to branch on two shapes.

notify config reference

| Field | Default | Description | |---|---|---| | kind | (none) | Structured mode. "openclaw-agent" is the only supported value today. Exclusive with command. | | agent | (required if kind=openclaw-agent) | OpenClaw agent id passed as --agent. | | sessionIdTemplate | "wa-monitor-{date}" | Template with {date}, {week}, {chatId}, {chatIdSlug} substitutions. | | behaviorFile | ~/.whatsapp-monitor/behavior.md | File prepended to each dispatch with a --- separator. Re-read every call. | | command | (none) | Command mode: shell command invoked via sh -c. Receives JSON on stdin. Exclusive with kind. | | quietPeriodSec | 30 | Per-chat quiet period before flushing a batch. 0 disables batching. | | timeoutSec | 120 | Hard cap on child process runtime. SIGTERM then 2s grace then SIGKILL. 0 disables. | | logFile | ~/.whatsapp-monitor/notifications.jsonl | Where each payload is appended regardless of command outcome. | | maxBufferedPerChat | 50 | Safety cap on buffered messages per chat before forced flush. |

Recipes

# Plain log
"command": "tee -a ~/whatsapp-digest.jsonl"

# Slack webhook
"command": "jq -r '\"\\(.chatName): \\(.messageCount) new messages\"' | curl -s -X POST -H 'Content-Type: application/json' --data-binary @- \"$SLACK_WEBHOOK_URL\""

# OpenClaw agent turn — daily-rolling session, behavior brief prefixed
# (see skills/whatsapp-monitor/SKILL.md for the full onboarding flow)
"command": "openclaw agent --agent main --session-id \"wa-monitor-$(date +%F)\" --message \"$(printf '%s\\n\\n---\\n\\n' \"$(cat ~/.whatsapp-monitor/behavior.md)\")$(cat)\""

# Only ping on group messages
"command": "[ \"$WAM_IS_GROUP\" = \"true\" ] && openclaw agent --agent main --session-id \"wa-monitor-$(date +%F)\" --message \"$(cat)\" || cat >/dev/null"

Security: notify.command runs as the user owning the service process, with full shell access. The config file is user-owned — treat it accordingly.

Running as a service

macOS (launchd)

Write ~/Library/LaunchAgents/com.whatsapp-monitor.run.plist:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
  <key>Label</key><string>com.whatsapp-monitor.run</string>
  <key>ProgramArguments</key>
  <array>
    <string>/usr/local/bin/whatsapp-monitor</string>
    <string>run</string>
  </array>
  <key>RunAtLoad</key><true/>
  <key>KeepAlive</key><true/>
  <key>StandardOutPath</key><string>/tmp/whatsapp-monitor.out.log</string>
  <key>StandardErrorPath</key><string>/tmp/whatsapp-monitor.err.log</string>
</dict>
</plist>

Then:

launchctl load ~/Library/LaunchAgents/com.whatsapp-monitor.run.plist
launchctl start com.whatsapp-monitor.run

Adjust /usr/local/bin/whatsapp-monitor to match which whatsapp-monitor.

Linux (systemd user service)

~/.config/systemd/user/whatsapp-monitor.service:

[Unit]
Description=WhatsApp Monitor
After=network-online.target

[Service]
ExecStart=/usr/local/bin/whatsapp-monitor run
Restart=on-failure
RestartSec=5

[Install]
WantedBy=default.target
systemctl --user daemon-reload
systemctl --user enable --now whatsapp-monitor

pm2

pm2 start whatsapp-monitor --name wa-monitor -- run
pm2 save

Commands

| Command | Purpose | |---|---| | whatsapp-monitor run | Persistent listener. The primary mode. | | whatsapp-monitor notify test | Run notify.command once with a synthetic payload. | | whatsapp-monitor link | Link WhatsApp account (QR or pairing code). | | whatsapp-monitor groups | List available groups with their IDs. | | whatsapp-monitor config add/remove/list | Manage the allowlist. | | whatsapp-monitor reset | Reset auth state (requires re-linking). |

Debugging / inspection commands

These are for ad-hoc debugging. Prefer run for ongoing monitoring.

| Command | Purpose | |---|---| | whatsapp-monitor messages | Connect, fetch queued messages, exit. -f keeps running but is no substitute for the service. | | whatsapp-monitor events | Stream raw Baileys events. |

See whatsapp-monitor <command> --help for flags.

Configuration

Stored at ~/.whatsapp-monitor/config.json:

{
  "allowedGroups": ["[email protected]"],
  "allowedContacts": ["[email protected]"],
  "authDir": "/Users/you/.whatsapp-monitor/auth",
  "notify": {
    "kind": "openclaw-agent",
    "agent": "main",
    "sessionIdTemplate": "wa-monitor-{date}",
    "behaviorFile": "~/.whatsapp-monitor/behavior.md",
    "quietPeriodSec": 30,
    "timeoutSec": 120
  }
}

Security

  • Allowlist-based: only messages from explicitly allowed chats are surfaced.
  • Secure default: if the allowlist is empty, run refuses to start.
  • Read-only: the client does not expose any methods to send messages.

Library usage

import { WhatsAppMonitor, loadConfig } from 'whatsapp-monitor'

const config = await loadConfig()
const client = new WhatsAppMonitor(config)

client.onMessage((msg) => {
  console.log('New message:', msg.text)
})

await client.connect()

Available methods

  • connect() / disconnect()
  • listGroups(), getGroupMetadata(groupId)
  • onMessage, onMessageUpdate, onMessageDelete
  • onConnection, onQR, onReady, onActivity

Baileys limits (why the service design matters)

  • No guarantee that offline messages will all sync; prioritizes recent.
  • Connection-timing drops during reconnect are real.
  • Server-side retention is limited.
  • 14+ days of phone inactivity logs out linked devices.

A persistent listener closes all of these gaps as much as WhatsApp allows. Run it under a process manager; don't rely on one-shot messages for anything important.

Message shape

interface MonitorMessage {
  id: string
  chatId: string
  chatName?: string
  sender: string
  senderName?: string
  timestamp: number
  text?: string
  type: 'text' | 'image' | 'video' | 'audio' | 'document' | 'sticker' | 'reaction' | 'poll' | 'location' | 'contact' | 'unknown'
  upsertType: 'notify' | 'append' | 'unknown'
  isGroup: boolean
  quotedMessage?: { id: string; sender: string; text?: string }
}

License

MIT