feat(messages): add carousel + fix interactive rendering on WhatsApp Web/Desktop#2526
Merged
DavidsonGomes merged 1 commit intoEvolutionAPI:developfrom Apr 29, 2026
Conversation
…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.
Contributor
There was a problem hiding this comment.
Sorry @pastoriniMatheus, you have reached your weekly rate limit of 500000 diff characters.
Please try again later or upgrade to continue using Sourcery
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /message/sendCarousel/{instance})<biz>node into the relayed stanza via Baileys' officialadditionalNodesoptionPOST /instance/logoutidempotent when the instance is already closed, fixing the manager UI delete flow that always shows an error toast even though the delete itself succeedsWhat 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)nativeFlowMessagedirect (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 sendsinteractiveMessagedirectly (noviewOnceMessage) and attaches the interactive biz node:listMessage(): switched from the moderninteractiveMessage+single_select(mobile-only) to the legacylistMessagewithSINGLE_SELECT(works on Web/Desktop/iOS/Android) and attaches the list biz node:sendMessage/sendMessageWithTyping: forward an optionaladditionalNodes: BinaryNode[]parameter and route top-levelinteractiveMessage/listMessagethroughclient.relayMessageso the biz node actually reaches the stanzaLogout idempotency
Before:
DELETE /instance/logout/{instance}returned400 Bad Requestwhen the instance was already disconnected. The manager UI callslogoutbeforedelete, so users always saw an error toast on the delete flow.After: returns
200 SUCCESSwith"Instance was already disconnected"— the delete flow is clean.Manager UI helper (test panel)
A small vanilla
manager/dist/assets/test-interactive.jsinjected viaindex.htmladds 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
maxRetriesreference in a verbose log inside themessages.updatehandler_is-defined-but-never-used violations inchatwoot.service.ts(auto-fixed byeslint --fix)Files
src/api/integrations/channel/whatsapp/helpers/interactiveMessage.helper.tsmanager/dist/assets/test-interactive.jsmanager/dist/index.html<script>tagsrc/api/integrations/channel/whatsapp/whatsapp.baileys.service.tssrc/api/dto/sendMessage.dto.tsCarouselCard,SendCarouselDtosrc/validate/message.schema.tscarouselMessageSchemasrc/api/controllers/sendMessage.controller.tssendCarouselsrc/api/routes/sendMessage.router.tsPOST /sendCarouselsrc/api/controllers/instance.controller.tssrc/api/integrations/chatbot/chatwoot/services/chatwoot.service.tsTest plan
npx tsc --noEmitcleannpm run lint:checkcleanurl+copy), 1 PIX (payment_info)