Skip to content

feat(messages): add carousel + fix interactive rendering on WhatsApp Web/Desktop#2526

Merged
DavidsonGomes merged 1 commit intoEvolutionAPI:developfrom
pastoriniMatheus:feat/interactive-messages
Apr 29, 2026
Merged

feat(messages): add carousel + fix interactive rendering on WhatsApp Web/Desktop#2526
DavidsonGomes merged 1 commit intoEvolutionAPI:developfrom
pastoriniMatheus:feat/interactive-messages

Conversation

@pastoriniMatheus
Copy link
Copy Markdown

Summary

  • Adds carousel message support (POST /message/sendCarousel/{instance})
  • Fixes buttons and list rendering on WhatsApp Web/Desktop (and iOS) by injecting the required <biz> node into the relayed stanza via Baileys' official additionalNodes option
  • Makes POST /instance/logout idempotent when the instance is already closed, fixing the manager UI delete flow that always shows an error toast even though the delete itself succeeds

What changes

Carousel (new)

  • POST /message/sendCarousel/{instance} — accepts up to 10 cards with image/title/body/footer and 1–3 native flow buttons each (reply, url, call, copy)
  • Single-card-without-image is automatically routed as nativeFlowMessage direct (iOS optimization, mirrors the upstream WhatsApp behaviour)

Buttons & Lists (rendering fix)

The previous implementation wrapped every interactive payload in a viewOnceMessage, which makes WhatsApp Web/Desktop treat it as a view-once media bubble and discard the buttons. Plus, the legacy and modern formats need different XML <biz> nodes to render across all clients.

  • buttonMessage(): now sends interactiveMessage directly (no viewOnceMessage) and attaches the interactive biz node:
    <biz>
      <interactive type="native_flow" v="1">
        <native_flow v="9" name="mixed"/>
      </interactive>
    </biz>
  • listMessage(): switched from the modern interactiveMessage + single_select (mobile-only) to the legacy listMessage with SINGLE_SELECT (works on Web/Desktop/iOS/Android) and attaches the list biz node:
    <biz><list type="product_list" v="2"/></biz>
  • sendMessage / sendMessageWithTyping: forward an optional additionalNodes: BinaryNode[] parameter and route top-level interactiveMessage / listMessage through client.relayMessage so the biz node actually reaches the stanza

Logout idempotency

Before: DELETE /instance/logout/{instance} returned 400 Bad Request when the instance was already disconnected. The manager UI calls logout before delete, so users always saw an error toast on the delete flow.

After: returns 200 SUCCESS with "Instance was already disconnected" — the delete flow is clean.

Manager UI helper (test panel)

A small vanilla manager/dist/assets/test-interactive.js injected via index.html adds a "🧪 Test Interactive" button to each instance card. Clicking opens a modal with 5 tabs (Reply / CTA / PIX / List / Carousel) — each tab pre-fills a valid editable JSON payload and a destination number field. Useful for QA without leaving the manager.

If the script can't detect a card (the upstream React layout changes), it falls back to a floating action button with an instance picker.

Drive-by fixes

  • Undefined maxRetries reference in a verbose log inside the messages.update handler
  • Two pre-existing _ is-defined-but-never-used violations in chatwoot.service.ts (auto-fixed by eslint --fix)

Files

Path Kind
src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.ts new
manager/dist/assets/test-interactive.js new
manager/dist/index.html adds <script> tag
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts core changes
src/api/dto/sendMessage.dto.ts CarouselCard, SendCarouselDto
src/validate/message.schema.ts carouselMessageSchema
src/api/controllers/sendMessage.controller.ts sendCarousel
src/api/routes/sendMessage.router.ts POST /sendCarousel
src/api/controllers/instance.controller.ts logout idempotent
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts drive-by lint fix

Test plan

  • npx tsc --noEmit clean
  • npm run lint:check clean
  • Manual smoke test on a real WhatsApp account, sent every kind to one mobile number:
    • Buttons: 3 reply, 2 CTA (url + copy), 1 PIX (payment_info)
    • List with multiple sections (renders on Web ✅)
    • Carousel with 3 image cards
    • Carousel with 1 card without image (rendered as buttons via the iOS optimization path)
  • All five kinds render with their buttons on Web, Desktop, iOS and Android
  • Manager UI delete flow: clean toast, no spurious errors when instance was already disconnected

…Web/Desktop

Adds carousel message support and fixes button/list rendering on WhatsApp
Web/Desktop and iOS by injecting the required <biz> node into the relayMessage
stanza via the official Baileys additionalNodes option.

Changes:
- New endpoint POST /message/sendCarousel/{instance} (interactiveMessage with
  carouselMessage; single-card-without-image is sent as nativeFlowMessage for
  iOS compatibility)
- buttonMessage: removed viewOnceMessage wrapper that prevented button
  rendering on Web/Desktop; added <biz><interactive type=native_flow v=1>
  <native_flow v=9 name=mixed/></interactive></biz> node
- listMessage: switched to legacy listMessage with SINGLE_SELECT listType
  (the modern interactiveMessage+single_select format does not render on
  Web/Desktop) and added the <biz><list type=product_list v=2/></biz> node
- sendMessage / sendMessageWithTyping: forward an optional additionalNodes
  parameter, and route top-level interactiveMessage / listMessage through
  client.relayMessage so the biz node reaches the stanza
- POST /instance/logout/{instance}: idempotent when the instance is already
  closed (returns SUCCESS instead of 400) so the manager UI delete flow
  (logout-then-delete) does not surface a misleading error
- DTO/schema/controller/router: SendCarouselDto, CarouselCard,
  carouselMessageSchema, sendCarousel handler and route
- Manager UI: small vanilla helper script (test-interactive.js) injected
  via index.html to add a "Test Interactive" button per instance card with
  an editable JSON modal for the 5 message kinds (Reply / CTA / PIX /
  List / Carousel)
- Drive-by fix: undefined `maxRetries` reference in a verbose log inside
  the messages.update handler

Tested manually on WhatsApp Web, Desktop, iOS and Android — all five
message kinds render correctly across clients.
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Sorry @pastoriniMatheus, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@DavidsonGomes DavidsonGomes merged commit 0980928 into EvolutionAPI:develop Apr 29, 2026
5 checks passed
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.

2 participants