Repository navigation
feat(webhook): report WhatsApp event RSVPs as event.response (Baileys) - #1788
Conversation
Baileys already decrypts each RSVP to a WhatsApp event (encEventResponseMessage) from the stored creation message's messageSecret and re-emits it as a messages.update carrying update.eventResponses. The adapter only read update.status there, so every RSVP was lost. Map those entries to a new engine-neutral EventResponseEvent and publish it as the event.response webhook and WebSocket event: event message id, chat, responder, fromMe, response (going | not_going | maybe | unknown), extra guest count, event name when the creation message is still stored, the RSVP's own message id and its timestamp. The idempotency key is the RSVP id, which is unique per answer. whatsapp-web.js has no callback behind it, so it accepts the subscription and never fires, like presence.update and the call outcomes. Also lists the event in the dashboard (15 locales), the JavaScript, Python, Go and Java SDKs, the regenerated openapi.json, docs/06, docs/05, docs/22 and the changelog. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MKa6QBXzdC1LHFVs5nBgZd
Look the responder up through engine.getContactById, the lookup behind
GET /contacts/{contactId}, and attach `contact { name?, pushName? }` like
message.received does, so an attendance list can be built from the RSVPs
alone. The lookup is best-effort: an unknown contact or a failed read
publishes the RSVP without `contact`.
RSVPs are chained per engine so a slow lookup cannot reorder a guest's
"going" and "not going".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MKa6QBXzdC1LHFVs5nBgZd
rmyndharis
left a comment
There was a problem hiding this comment.
Thanks for this, @jelasus. The event wiring is thorough: the webhook and WebSocket catalogs, the idempotency key, the OpenAPI snapshot, the dashboard list with all 15 locales, the four SDK enums and the docs are all in step, and the branch still merges cleanly onto current main. A few things need to change before it can land.
Blocking
npx tsc --noEmit -p tsconfig.json, which the Lint job runs, fails on the new spec:src/modules/session/session-event-response-wiring.spec.ts:96,(call[2] as EventResponseEvent)is TS2352. ts-jest does not type-check that cast, so the unit tests pass.(call[2] as unknown as EventResponseEvent)fixes it.- 1:1 chats do not deliver RSVPs. Baileys rc14 hands
getMessagethe event creation key as the responder sees it (lib/Utils/process-message.js:424-433; unlike reaction and poll keys,cleanMessagedoes not rewriteeventCreationMessageKey). In a 1:1 chat that key'sremoteJidis our own account, so the chat cross-check in thegetMessagehook (baileys-lifecycle.ts, thedifferentWaIds(stored.key.remoteJid..., key.remoteJid)guard) rejects the lookup and Baileys emits nothing. Baileys also derives the creator fromparticipant || remoteJidof that same key, so relaxing the guard alone is not enough. The 1:1 test passes because it uses a receiver-view key ({ id: 'EVT2', remoteJid: chat }); a sender-view key reproduces the gap. Either normalise the creation key before the lookup (a smallscripts/patch-baileys-*style patch is fine here), or scope the feature and the docs to groups for now and say so. chatIdis taken from the creation key (baileys-events.ts,chatId: this.host.toNeutralJid(eventKey.remoteJid)), which is the responder's view. Use the RSVP envelope instead,envelope.remoteJid ?? eventKey.remoteJid, so chat-scoped sockets and chat-restricted keys receive it in the right chat.
Should fix
- The ordering chain in
session-engine-event-wiring.ts(eventResponses = eventResponses.then(...)) has no rejection handler: one throw rejects the chain and silently drops every later RSVP for that engine. Add a.catchthat logs inside the chained step. docs/06says answers given while offline are replayed on reconnect. Baileys buffers updates until the offline marker and merges them per message key (lib/Utils/event-buffer.js:425-444), so several buffered answers to one event collapse to the last. Please state that, and tell consumers to order bytimestamprather than delivery order.- Add a case for the new
rsvp_{sessionId}_{responseMessageId}key insrc/modules/webhook/utils/idempotency.util.spec.ts, like the sibling event keys there. - The
getContactByIdenrichment runs for every RSVP even when nobody subscribes, and on Baileys it can trigger a blocklist query;findContactalone is enough for the name. - Small docs gaps: the
available_eventslist indocs/12-troubleshooting-faq.md, the event families comment indocs/04-security-design.md, and the third (payload) cell of the new row in thedocs/06catalog table.
Fork CI has not run yet; I will approve the workflows once the branch is updated. When this merges I will tighten the CHANGELOG line to the house style and add your credit, so there is no need to touch that.
An RSVP to an event the account posted in a 1:1 chat names the event by the guest's key, whose remoteJid is the account itself. The getMessage chat check refused that lookup, so Baileys could not decrypt any such RSVP. It now answers for the account's own 1:1 message asked for under the account's own jid, and the chat is taken from the RSVP's own key for the same reason. The responder's name is read from the session's contact store in the adapter instead of through getContactById, which could query the blocklist on every RSVP. With no async lookup left in the wiring, the per-engine promise chain goes too.
Order answers by timestamp: Baileys merges replayed offline updates per event, so several answers given while offline arrive as the last one. Also fills the payload cell of the catalog row, states the 1:1 gap, and lists the event in the FAQ and the security design catalog comment.
|
@jelasus I merged current
Locally the Lint job's gates, |
Baileys already decrypts each RSVP to a WhatsApp event (encEventResponseMessage) from the stored creation message's messageSecret and re-emits it as a messages.update carrying update.eventResponses. The adapter only read update.status there, so every RSVP was lost.
Map those entries to a new engine-neutral EventResponseEvent and publish it as the event.response webhook and WebSocket event: event message id, chat, responder, fromMe, response (going | not_going | maybe | unknown), extra guest count, event name when the creation message is still stored, the RSVP's own message id and its timestamp. The idempotency key is the RSVP id, which is unique per answer.
whatsapp-web.js has no callback behind it, so it accepts the subscription and never fires, like presence.update and the call outcomes.
Also lists the event in the dashboard (15 locales), the JavaScript, Python, Go and Java SDKs, the regenerated openapi.json, docs/06, docs/05, docs/22 and the changelog.
Claude-Session: https://claude.ai/code/session_01MKa6QBXzdC1LHFVs5nBgZd
Description
Brief description of changes
Type of Change
Checklist
Screenshots (if applicable)
Related Issues
Closes #