externalAdReply (Click-to-WhatsApp ads) no longer reaches the Chatwoot integration on 2.4
Summary
Since 2.4, messages coming from Instagram/Facebook click-to-WhatsApp ads arrive in Chatwoot as plain text. The ad card — thumbnail, title, body, source URL — is silently dropped. On 2.3.7 the same messages produced an attachment with the resized thumbnail plus the ad title and link.
There is no error in the logs. The message itself is delivered; only the ad context disappears.
Root cause
prepareMessage() in src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts
changed which field feeds messageRaw.contextInfo.
main (and 2.3.7):
const contentType = getContentType(message.message);
const contentMsg = message?.message[contentType] as any;
// ...
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo),develop (and 2.4.0-rc2):
contextInfo: this.deserializeMessageBuffers(message.message?.messageContextInfo),messageContextInfo is the WhatsApp device-metadata block (deviceListMetadata,
E2E key info). It never carries externalAdReply. The ad context lives in the
content message's own contextInfo, e.g.
message.message.extendedTextMessage.contextInfo.externalAdReply.
Why the Chatwoot side then fails
getAdsMessage() in
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts is called as
this.getAdsMessage(body), where body is the message wrapper:
title: msg.extendedTextMessage?.contextInfo?.externalAdReply?.title
|| msg.contextInfo?.externalAdReply?.title,The first branch never matches, because on the wrapper the content lives under
msg.message.extendedTextMessage, not msg.extendedTextMessage. So the whole
feature depended on the hoisted msg.contextInfo — precisely what the change
above repointed. getAdsMessage() now returns an object whose fields are all
undefined, the if (adsMessage && adsMessage.title || ...) branch is skipped,
and no attachment is created.
Side effect beyond ads
messageRaw?.contextInfo?.quotedMessage (same file) reads the same field, so
quote/reply context on media messages is lost as well.
Steps to reproduce
- Run 2.4.0-rc2 with the Chatwoot integration enabled.
- Publish an Instagram ad with a click-to-WhatsApp button.
- Click the ad and send any text.
- Chatwoot receives the text only. On 2.3.7 the same flow produced an attachment with the 320x180 thumbnail, the ad title and the source URL.
Suggested fix
Restore the previous source in prepareMessage():
const contentType = getContentType(message.message);
const contentMsg = message?.message[contentType] as any;
// ...
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo),Independently, getAdsMessage() would be more robust reading
msg.message?.extendedTextMessage?.contextInfo?.externalAdReply as its first
branch, so it no longer depends on the hoisted field at all.
Environment
- Evolution API 2.4.0-rc2 (Docker,
evoapicloud/evolution-api:2.4.0-rc2) - Chatwoot 4.17.1, self-hosted
- Baileys channel, four instances
- Regression confirmed by source diff between tags
2.3.7and2.4.0-rc2, and between branchesmainanddevelop.
Workaround in use
While this is open, we patch the compiled bundles
(dist/api/integrations/channel/whatsapp/whatsapp.baileys.service.{js,mjs}) in a
derived image, replacing the expression with one that does not depend on
minified symbol names:
contextInfo: this.deserializeMessageBuffers(
(e.message && Object.values(e.message).find(v => v && typeof v === "object" && v.contextInfo)?.contextInfo)
?? e.message?.messageContextInfo
)Checked against simulated payloads: ad leads recover title / body /
thumbnailUrl / sourceUrl, plain text messages are unaffected, and quoted
media regains its stanzaId. It is running in our production instance; we have
not yet observed a live ad lead through it.
Versão em português (pt-BR) — clique para expandir
Resumo
Desde a 2.4, mensagens vindas de anúncios click-to-WhatsApp do Instagram e do Facebook chegam ao Chatwoot como texto puro. O cartão do anúncio — miniatura, título, corpo e URL de origem — é descartado silenciosamente. Na 2.3.7 as mesmas mensagens produziam um anexo com a miniatura redimensionada, mais o título e o link do anúncio.
Não há erro no log. A mensagem em si é entregue; só o contexto do anúncio some.
Causa
O método prepareMessage(), em
src/api/integrations/channel/whatsapp/whatsapp.baileys.service.ts, passou a
preencher messageRaw.contextInfo a partir de outro campo.
main (e 2.3.7):
const contentType = getContentType(message.message);
const contentMsg = message?.message[contentType] as any;
// ...
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo),develop (e 2.4.0-rc2):
contextInfo: this.deserializeMessageBuffers(message.message?.messageContextInfo),messageContextInfo é o bloco de metadados de dispositivo do WhatsApp
(deviceListMetadata, informação de chaves E2E). Ele nunca carrega
externalAdReply. O contexto do anúncio vive na contextInfo da própria
mensagem de conteúdo, por exemplo
message.message.extendedTextMessage.contextInfo.externalAdReply.
Por que o lado do Chatwoot falha em seguida
getAdsMessage(), em
src/api/integrations/chatbot/chatwoot/services/chatwoot.service.ts, é chamado
como this.getAdsMessage(body), onde body é o invólucro da mensagem:
title: msg.extendedTextMessage?.contextInfo?.externalAdReply?.title
|| msg.contextInfo?.externalAdReply?.title,O primeiro ramo nunca casa, porque no invólucro o conteúdo fica em
msg.message.extendedTextMessage, e não em msg.extendedTextMessage. Ou seja,
o recurso inteiro dependia do msg.contextInfo hoisted — exatamente o que a
mudança acima repontou. getAdsMessage() agora devolve um objeto com todos os
campos undefined, o ramo if (adsMessage && adsMessage.title || ...) é pulado,
e nenhum anexo é criado.
Efeito colateral além dos anúncios
messageRaw?.contextInfo?.quotedMessage (mesmo arquivo) lê o mesmo campo, então
o contexto de citação/resposta em mensagens de mídia também se perde.
Como reproduzir
- Rodar a 2.4.0-rc2 com a integração do Chatwoot habilitada.
- Publicar um anúncio no Instagram com botão click-to-WhatsApp.
- Clicar no anúncio e enviar qualquer texto.
- O Chatwoot recebe apenas o texto. Na 2.3.7 o mesmo fluxo produzia um anexo com a miniatura 320x180, o título do anúncio e a URL de origem.
Correção sugerida
Restaurar a origem anterior em prepareMessage():
const contentType = getContentType(message.message);
const contentMsg = message?.message[contentType] as any;
// ...
contextInfo: this.deserializeMessageBuffers(contentMsg?.contextInfo),Independentemente disso, getAdsMessage() ficaria mais robusto lendo
msg.message?.extendedTextMessage?.contextInfo?.externalAdReply como primeiro
ramo, deixando de depender do campo hoisted.
Ambiente
- Evolution API 2.4.0-rc2 (Docker,
evoapicloud/evolution-api:2.4.0-rc2) - Chatwoot 4.17.1, self-hosted
- Canal Baileys, quatro instâncias
- Regressão confirmada por diff de fonte entre as tags
2.3.7e2.4.0-rc2, e entre os branchesmainedevelop.
Contorno em uso
Enquanto isso não é corrigido, aplicamos um patch nos bundles compilados
(dist/api/integrations/channel/whatsapp/whatsapp.baileys.service.{js,mjs}) numa
imagem derivada, trocando a expressão por uma que não depende de nomes de
símbolos minificados:
contextInfo: this.deserializeMessageBuffers(
(e.message && Object.values(e.message).find(v => v && typeof v === "object" && v.contextInfo)?.contextInfo)
?? e.message?.messageContextInfo
)Verificado com payloads simulados: leads de anúncio recuperam title / body /
thumbnailUrl / sourceUrl, mensagens de texto comum ficam inalteradas, e mídia
citando outra mensagem recupera o stanzaId. Está rodando na nossa instância de
produção; ainda não observamos um lead de anúncio real passando por ele.
Source: evolution-foundation/evolution-api