Skip to content

feat(webhooks): lid<->pn addressing (fromAlt) and unified reaction delivery - #19

Merged
jobasfernandes merged 3 commits into
mainfrom
feat/webhooks-pn-and-reactions
Jun 4, 2026
Merged

jobasfernandes merged 3 commits into
mainfrom
feat/webhooks-pn-and-reactions

Conversation

@jlucaso1

@jlucaso1 jlucaso1 commented Jun 4, 2026 •

Copy link
Copy Markdown
Collaborator

Closes #17. Closes #18.

Two related additions to the normalized message/webhook contract, kept neutral and identical across the zapo and baileys engines. Split into two atomic commits.

1. fromAlt — alternate lid↔pn addressing (#17)

With the LID migration, from/participant can arrive as @lid. Both engines already resolve the alternate addressing on the message key (participantAlt/remoteJidAlt), so the message event now carries an optional fromAlt — the phone-number jid when from is a lid, or vice-versa, mirroring from = participant ?? chat. Omitted when the engine doesn't provide it.

  • types: optional fromAlt on messageEventSchema.
  • engine-baileys / engine-zapo: fromAlt = participantAlt ?? remoteJidAlt from the key.
  • openapi: documented on the WebhookDelivery message branch + description + a lid-addressed group example.

2. Unified reaction delivery (#18)

Reactions were only normalized when they arrived as a reactionMessage inside messages.upsert. Investigation showed this was wrong in different ways per engine:

  • zapo: addons.autoDecrypt is on by default, so reactions are delivered as decrypted message_addon events (kind: 'reaction'), not as message. The engine never subscribed to that event, so reactions were silently dropped. This is the real bug.
  • baileys: a plain reactionMessage already arrives via messages.upsert and fires messages.reaction — listening to both would duplicate. Encrypted reactions (encReactionMessage) would also slip through the upsert path as unknown.

Both engines now use a single dedicated reaction source that produces the exact same normalized payload the upsert produced:

  • engine-zapo: subscribe to message_addon, map kind: 'reaction' → content.type: 'reaction' (other addon kinds ignored for now — see feat(webhooks): evento de mensagem editada #8 for edits).
  • engine-baileys: map messages.reaction (envelope = who reacted, entry.key = target) and skip reactionMessage/encReactionMessage in the upsert path — exactly one source, no duplication, and encrypted reactions are covered as soon as the engine decrypts them.
  • both reuse fromAlt and the existing inboundReactionContentSchema.
  • openapi: reaction delivery example.

Tests

  • fromAlt present for lid group/direct, absent otherwise, on both engines.
  • baileys: mapBaileysReaction (group emoji, removal → null emoji, envelope-vs-target mapping, fromAlt); isBaileysReactionUpsert (plain + encrypted true, other content false).
  • zapo: mapZapoReaction (reaction addon → normalized event; non-reaction addon → null).
  • Full suite green; lint, typecheck, build, test:coverage (branches ≥ 70%) all pass.

Notes

  • Why migrate baileys to messages.reaction instead of leaving the upsert path? Long-term it gives one canonical reaction source per engine with explicit who-reacted/target semantics, keeps the two engines symmetric, and is future-proof: when baileys starts decrypting encReactionMessage, it already flows through messages.reaction (the upsert would only surface it as unknown). Risk is contained by reproducing the prior payload field-for-field and skipping reactions in the upsert so there is never a duplicate.
  • Reaction timestamp on baileys is derived from reaction.senderTimestampMs (ms → s); on zapo the addon carries no reliable timestamp, so it's omitted.
  • Empty/missing reaction text maps to emoji: null (a reaction removal), matching the existing reactionMessage mapping.
  • Scope is the message event; ack/presence lid↔pn resolution can follow separately if wanted.

Summary by CodeRabbit

  • New Features

    • Added support for alternative sender identity (fromAlt) in message events
    • Added dedicated webhook event handling for message reactions with proper normalization
  • Documentation

    • Updated webhook delivery documentation to include fromAlt behavior alongside existing fields
    • Enhanced webhook examples with group message reaction scenarios

jlucaso1 added 2 commits June 4, 2026 15:38
…malized message

With the LID migration, `from`/`participant` can arrive as `@lid`. Both
engines already resolve the alternate addressing on the message key, so
surface it as an optional `fromAlt` on the normalized message event — the
phone-number jid when `from` is a lid, or vice-versa.

- types: add optional `fromAlt` to messageEventSchema.
- engine-baileys/engine-zapo: map `fromAlt` from the key
  (`participantAlt ?? remoteJidAlt`), mirroring `from = participant ?? chat`.
  Omitted when the engine doesn't provide it.
- openapi: document `fromAlt` on the WebhookDelivery message branch, the
  webhook description, and a lid-addressed group example.
- tests: fromAlt present for lid group/direct, absent otherwise, on both engines.

Closes #17.
Reactions were only normalized when they arrived as a `reactionMessage`
inside `messages.upsert`. That left a real gap on zapo (where reactions are
delivered as decrypted `message_addon` events, not `message`) and made the
baileys path fragile (it would miss encrypted reactions while a future
decrypt would also duplicate plain ones via `messages.reaction`).

Unify both engines onto a single, dedicated reaction source, producing the
exact same normalized payload the upsert path produced:

- engine-zapo: subscribe to `message_addon` and map `kind: 'reaction'` to the
  normalized reaction message event (other addon kinds are ignored for now).
  This is the actual fix — reactions were dropped on zapo before.
- engine-baileys: map the `messages.reaction` event (envelope = who reacted,
  `entry.key` = target) and skip `reactionMessage`/`encReactionMessage` in the
  upsert path so there is exactly one source — no duplication, and encrypted
  reactions are covered as soon as the engine decrypts them.
- both reuse `fromAlt` (lid<->pn) and the existing `content.type: 'reaction'`.
- openapi: add a reaction delivery example.
- tests: reaction mappers (group emoji, removal, envelope/target, null for
  non-reaction addons) and the upsert reaction-skip guard.

Closes #18.
@coderabbitai

coderabbitai Bot commented Jun 4, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements message reaction support and introduces a fromAlt field for alternate sender addressing. The type schema is updated first, followed by parallel engine integrations for Baileys and Zapo, and concludes with OpenAPI documentation for webhook consumers.

Changes

Message Reactions and Alternate Sender Addressing

Layer / File(s) Summary
Message event type schema with fromAlt
packages/types/src/engine.ts
MessageEvent adds optional fromAlt: string field to support alternate sender identities.
Baileys message event mapping and fromAlt support
packages/engine-baileys/src/events.ts, packages/engine-baileys/src/events.test.ts
mapBaileysMessageEvent populates fromAlt from key.participantAlt or key.remoteJidAlt. Tests verify field derivation and presence/absence based on key structure.
Baileys reaction event handling
packages/engine-baileys/src/engine.ts, packages/engine-baileys/src/events.ts, packages/engine-baileys/src/events.test.ts
Baileys engine adds messages.reaction listener that routes reactions through mapBaileysReaction. isBaileysReactionUpsert detects reaction upserts for filtering in messages.upsert. Tests cover reaction detection, mapping including envelope/target structure, and null emoji for removals.
Zapo message event mapping and fromAlt support
packages/engine-zapo/src/events.ts, packages/engine-zapo/src/events.test.ts
mapZapoMessageEvent populates fromAlt from key.participantAlt or key.remoteJidAlt. Tests verify field derivation and presence/absence based on key structure.
Zapo reaction event handling
packages/engine-zapo/src/engine.ts, packages/engine-zapo/src/events.ts, packages/engine-zapo/src/events.test.ts
Zapo engine adds message_addon listener that routes reaction addons through mapZapoReaction. Function returns null for non-reaction addons and normalized MessageEvent for reactions. Import formatting updated to multi-line style. Tests verify addon mapping and type filtering.
OpenAPI schema and webhook examples
apps/api/src/http/openapi.ts
Schema extended with fromAlt field, new example added for group message reactions, existing examples updated to include fromAlt, and delivery description updated to document fromAlt behavior.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Possibly related PRs

  • w3nder/multi-wa-api#15: Both PRs update the message-event normalization contract via mapBaileysMessageEvent/mapZapoMessageEvent—PR#15 added mentions and quoted from contextInfo, while this PR adds fromAlt alongside existing fields at the same mapper output shape level.

Poem

🐰 Reactions bloom where messages flow,
With alternate addresses all aglow,
Both Baileys and Zapō now hear the chime,
And webhooks dance with fromAlt's time!
Through engines routed, events unfold— 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes both main features introduced: alternate addressing support (fromAlt) and unified reaction delivery via webhooks.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • ✅ Generated successfully - (🔄 Check to regenerate)
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/webhooks-pn-and-reactions

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/api/src/http/openapi.ts (1)

313-333: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix the inconsistent from field in the message reply example.

The example sets participant to a lid ('199999999999999@lid') but from to a phone-number JID ('5511888888888@s.whatsapp.net'). According to both engine implementations, from is derived as participant ?? chat, so when participant is present in a group message, from should match it.

📝 Proposed fix
     value: {
       type: 'message',
       id: '3EB0...',
       chat: '120363000000000000@g.us',
-      from: '5511888888888@s.whatsapp.net',
+      from: '199999999999999@lid',
       fromMe: false,
       isGroup: true,
       participant: '199999999999999@lid',
       fromAlt: '5511888888888@s.whatsapp.net',
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@apps/api/src/http/openapi.ts` around lines 313 - 333, The example object
message_reply_mention_group has an inconsistent from field: when participant is
present in a group message, from must equal participant (engine uses participant
?? chat). Update the value under message_reply_mention_group so from is
'199999999999999@lid' (matching participant) instead of the phone-number JID;
ensure any similar group examples follow the same rule (participant wins over
chat for from).
🧹 Nitpick comments (1)
packages/engine-zapo/src/events.test.ts (1)

243-265: ⚡ Quick win

Consider adding a test case for remoteJidAlt in direct messages.

The Baileys test suite includes a lidDirect test case that verifies fromAlt derivation from remoteJidAlt (lines 251-255 in packages/engine-baileys/src/events.test.ts). The Zapo tests only cover participantAlt for group messages but don't test remoteJidAlt for direct messages.

If Zapo's event structure supports remoteJidAlt, consider adding a similar test case for consistency and completeness.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/engine-zapo/src/events.test.ts` around lines 243 - 265, Add a test
that mirrors the Baileys "lidDirect" case: create a direct-message event using
mapZapoMessageEvent + base where key includes remoteJidAlt (e.g., remoteJid:
'123@s.whatsapp.net', remoteJidAlt: '55@lid', fromMe: false, isGroup: false) and
assert that the resulting object from mapZapoMessageEvent exposes fromAlt equal
to the remoteJidAlt; also include a plain message case (no remoteJidAlt)
asserting fromAlt is undefined. Reference mapZapoMessageEvent and base to locate
where to add the new assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@apps/api/src/http/openapi.ts`:
- Around line 313-333: The example object message_reply_mention_group has an
inconsistent from field: when participant is present in a group message, from
must equal participant (engine uses participant ?? chat). Update the value under
message_reply_mention_group so from is '199999999999999@lid' (matching
participant) instead of the phone-number JID; ensure any similar group examples
follow the same rule (participant wins over chat for from).

---

Nitpick comments:
In `@packages/engine-zapo/src/events.test.ts`:
- Around line 243-265: Add a test that mirrors the Baileys "lidDirect" case:
create a direct-message event using mapZapoMessageEvent + base where key
includes remoteJidAlt (e.g., remoteJid: '123@s.whatsapp.net', remoteJidAlt:
'55@lid', fromMe: false, isGroup: false) and assert that the resulting object
from mapZapoMessageEvent exposes fromAlt equal to the remoteJidAlt; also include
a plain message case (no remoteJidAlt) asserting fromAlt is undefined. Reference
mapZapoMessageEvent and base to locate where to add the new assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7a658c14-e0fc-4d2c-bba0-cc8d4288df13

📥 Commits

Reviewing files that changed from the base of the PR and between 0bc7aeb and 35617ed.

📒 Files selected for processing (8)
  • apps/api/src/http/openapi.ts
  • packages/engine-baileys/src/engine.ts
  • packages/engine-baileys/src/events.test.ts
  • packages/engine-baileys/src/events.ts
  • packages/engine-zapo/src/engine.ts
  • packages/engine-zapo/src/events.test.ts
  • packages/engine-zapo/src/events.ts
  • packages/types/src/engine.ts

@jlucaso1
jlucaso1 requested a review from w3nder June 4, 2026 18:57
Repository owner deleted a comment from coderabbitai Bot Jun 4, 2026
…o direct fromAlt

Review follow-up on #19:
- openapi: in the lid-addressed group example, `from` now equals `participant`
  (`@lid`), matching the engine rule `from = participant ?? chat`; the phone
  number stays in `fromAlt`.
- engine-zapo test: add the direct-chat case (remoteJidAlt, no participant) so
  `fromAlt` coverage mirrors the baileys lidDirect test.
coderabbitai Bot added a commit that referenced this pull request Jun 4, 2026
Docstrings generation was requested by @jobasfernandes.

* #19 (comment)

The following files were modified:

* `packages/engine-baileys/src/events.ts`
* `packages/engine-zapo/src/events.ts`
@coderabbitai

coderabbitai Bot commented Jun 4, 2026

Copy link
Copy Markdown

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #20

@jobasfernandes
jobasfernandes merged commit c2ccae4 into main Jun 4, 2026
2 checks passed
@jobasfernandes
jobasfernandes deleted the feat/webhooks-pn-and-reactions branch June 4, 2026 19:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(webhooks): capturar reações entregues fora do messages.upsert feat(webhooks): expor número (pn) resolvido junto ao jid lid nos eventos

2 participants