openclaw - 💡(How to fix) Fix [AI] WhatsApp: add reaction_received plugin hook (cousin #38714, complementary to #78963)

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…

Add a reaction_received (and optionally reaction_removed) plugin hook for the WhatsApp channel — analogous to the Discord request in #38714, and complementary to the inbound listen-only hooks discussed in #78963.

WhatsApp reactions (👍, ❤️, ⭐, 😲, 🧠, …) are received over the Baileys socket as messages.update events with a reactionMessage content type, but they are not currently exposed to plugins via a hook. This makes it impossible to build reaction-driven automations on the WhatsApp channel without patching runtime internals — which, as we show below, is empirically unsafe.

Root Cause

Add a reaction_received (and optionally reaction_removed) plugin hook for the WhatsApp channel — analogous to the Discord request in #38714, and complementary to the inbound listen-only hooks discussed in #78963.

WhatsApp reactions (👍, ❤️, ⭐, 😲, 🧠, …) are received over the Baileys socket as messages.update events with a reactionMessage content type, but they are not currently exposed to plugins via a hook. This makes it impossible to build reaction-driven automations on the WhatsApp channel without patching runtime internals — which, as we show below, is empirically unsafe.

Fix Action

Fix / Workaround

WhatsApp reactions (👍, ❤️, ⭐, 😲, 🧠, …) are received over the Baileys socket as messages.update events with a reactionMessage content type, but they are not currently exposed to plugins via a hook. This makes it impossible to build reaction-driven automations on the WhatsApp channel without patching runtime internals — which, as we show below, is empirically unsafe.

Code Example

{
  "channel": "whatsapp",
  "accountId": "default",
  "chatJid": "[email protected]",
  "messageId": "3EB0...",
  "messageFromMe": true,
  "userId": "[email protected]",
  "userPhone": "+33603017955",
  "emoji": "👍",
  "timestamp": 1715212345678
}

---

08:38:02  whatsapp/connection  Stream Errored (conflict)
08:38:02  whatsapp/connection  WhatsApp logged out (statusCode=401, loggedOut=true)
08:38:02  whatsapp/auth-store  clearing local creds for account default
RAW_BUFFERClick to expand / collapse

Summary

Add a reaction_received (and optionally reaction_removed) plugin hook for the WhatsApp channel — analogous to the Discord request in #38714, and complementary to the inbound listen-only hooks discussed in #78963.

WhatsApp reactions (👍, ❤️, ⭐, 😲, 🧠, …) are received over the Baileys socket as messages.update events with a reactionMessage content type, but they are not currently exposed to plugins via a hook. This makes it impossible to build reaction-driven automations on the WhatsApp channel without patching runtime internals — which, as we show below, is empirically unsafe.

Current Behavior

  • WhatsApp reactions are received internally by @openclaw/whatsapp (socket event) but are not surfaced through the Hooks system.
  • Available hook events on WhatsApp today are limited to inbound text messages.
  • There is no supported way for a plugin to observe reactions added/removed on agent-published messages.

Expected Behavior

Two new hook events on the WhatsApp channel:

  • reaction_received — fired when a user adds a reaction to a message in a chat the WhatsApp account participates in.
  • reaction_removed — fired when a user removes a reaction.

Payload (suggested, aligned on the existing Discord conventions and on #78963's message_received shape):

{
  "channel": "whatsapp",
  "accountId": "default",
  "chatJid": "[email protected]",
  "messageId": "3EB0...",
  "messageFromMe": true,
  "userId": "[email protected]",
  "userPhone": "+33603017955",
  "emoji": "👍",
  "timestamp": 1715212345678
}

Use case

Feedback loop on agent-published content. Concretely, an agent posts a curated news/finance brief to a WhatsApp group; participants react with emoji to flag impactful items, request follow-ups, or mark noise. A reaction hook lets a plugin record that signal back into the agent's memory store, without ever generating an outbound WhatsApp action or an agent run.

This is the WhatsApp equivalent of #38714 (Discord) and matches the listen-only spirit of #78963 (no LLM call, no outbound activity, hooks only).

Real behavior proof

We tried a local plugin that piggybacks on the active Baileys connection via the existing in-process registry symbol Symbol.for("openclaw.whatsapp.connectionControllerRegistry"), attaching a passive messages.update listener to the live socket without opening a second socket.

It does not hold in production. Empirical timeline, captured on 2026-05-09 against a real account:

  • 2026-05-08 ~21:00 — Plugin deployed, smoke test (~6 min) green: reactions captured correctly, no errors.
  • 2026-05-08 → 2026-05-09 — Normal traffic, 4 watchdog-driven reconnect cycles over ~13 hours.
  • 2026-05-09 08:38 — WhatsApp server issued Stream Errored (conflict) → 401 → loggedOut: true.
  • 2026-05-09 08:38 — Session treated as logged-out, credentials under ~/.openclaw/credentials/whatsapp/<account>/ wiped automatically.
  • 2026-05-09 08:38 → ~18:00 — Channel silently down for ~10 hours; the agent that uses WhatsApp stopped responding without alert (the channel reported connected: false but no failure-alert wiring caught it).
  • 2026-05-09 ~23:30 — Manual QR re-pair required to restore the channel.

Relevant log evidence (gateway log, abridged for legibility — full lines available in /tmp/openclaw/openclaw-2026-05-09.log):

08:38:02  whatsapp/connection  Stream Errored (conflict)
08:38:02  whatsapp/connection  WhatsApp logged out (statusCode=401, loggedOut=true)
08:38:02  whatsapp/auth-store  clearing local creds for account default

The conflict is consistent with WhatsApp's single-device session enforcement: even a "passive" runtime attachment, when combined with reconnection cycles, eventually presents to the server as a second device and gets evicted. Short tests do not surface this — only endurance under watchdog restarts does.

Practical consequence: any third-party effort to subscribe to WhatsApp reactions today either (a) opens a second Baileys socket (immediate conflict), or (b) reuses the live one (delayed conflict, as above). Neither is safe. Only an in-process hook emitted by the official channel plugin avoids the problem, since it doesn't add any new attachment to the socket.

Suggested implementation shape

In @openclaw/whatsapp's inbound monitor (monitor-*.js, around the existing messages.upsert / messages.update handlers):

  1. Detect reactionMessage content in messages.update events.
  2. Resolve the reacting user JID (group case: key.participant; DM case: key.remoteJid).
  3. Resolve messageFromMe and messageId from key.
  4. Emit the existing plugin hook bus with reaction_received / reaction_removed and the payload above.
  5. Gate behind the same listen-only / account-level configuration introduced in #78963 (so reactionLevel: "off" does not silence inbound reaction hooks).

No outbound activity, no agent run — same invariants as #78963 and #38714.

Acceptance criteria

  • Plugins receive a reaction_received event when any participant of a chat the account is in adds an emoji reaction (group or DM).
  • Plugins receive a reaction_removed event on emoji removal.
  • The payload includes at minimum: channel, accountId, chatJid, messageId, messageFromMe, userId, emoji, timestamp.
  • No agent session/run is created and no LLM call happens as a result of a reaction event.
  • No outbound WhatsApp activity (reply, ack reaction, read receipt, typing) is triggered by the hook itself.
  • The hook works under listen-only / hooks-only mode (#78963) and under regular conversational mode.
  • Hook is documented alongside Discord's reaction hook (#38714) for cross-channel parity.

Verification plan

A plugin author can validate the implementation with:

  1. Configure an account with the new hook enabled.
  2. From a second WhatsApp client, react with 👍 on a message in a group the account is in. The hook fires once with the correct payload.
  3. Remove the reaction. reaction_removed fires with the matching payload.
  4. Run a 24h+ soak test with at least 2 watchdog restarts. No Stream Errored (conflict) 401, no credential wipe, hook continues to fire.
  5. Run with processWithAgent: false (per #78963). No agent run is created, no LLM call recorded, no outbound activity logged.

Environment

  • OpenClaw: 2026.5.4
  • Channel: WhatsApp (Baileys-based, @openclaw/whatsapp)
  • Related issues: #38714 (Discord reaction hook), #78963 (WhatsApp listen-only mode), #69931 (closed — reaction-to-historical-message detection)

Additional context

This unblocks at least the following automation patterns on WhatsApp, all of which already work or are being requested on Discord:

  1. Reaction-based message saving / memory tagging.
  2. Feedback loops on agent-published content (impact scoring, follow-up flags).
  3. Lightweight voting / triage in shared groups.
  4. Cross-channel feedback consistency (same emoji semantics on Discord, WhatsApp, iMessage tapbacks).

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix [AI] WhatsApp: add reaction_received plugin hook (cousin #38714, complementary to #78963)