Pular para o conteúdo

Carrossel e Flows

Envie um carrossel de produtos e um formulário dentro do WhatsApp numa conexão oficial, e leia os toques e as respostas que voltam.

Dois formatos que só existem em conexão oficial, porque ambos são construídos sobre a Cloud API: um carrossel de cards de produto, e um Flow — um formulário que abre dentro do WhatsApp, sem mandar a pessoa para o navegador.

Tecnicamente não compartilham nada, mas chegam no mesmo lugar: os toques e o formulário preenchido voltam como webhook da conexão.

Nenhum dos dois funciona em conexão por QR Code. Enviado ali, um carrossel vira texto puro e um Flow é recusado.

Carrossel de produtos

Um carrossel é um template com um componente CAROUSEL: de 2 a 10 cards, cada um com header de mídia, corpo próprio e até dois botões. Todos os cards precisam ter o mesmo formato — mesmo tipo de header, mesma quantidade e tipo de botões. A Meta recusa um conjunto heterogêneo.

Dois uploads parecem a mesma coisa e não são intercambiáveis:

EndpointO que devolveValidade
DefiniçãoPOST /v3/connections/{id}/templates/header-mediahandlePermanente, descreve o template
EnvioPOST /v3/connections/{id}/templates/send-mediamediaId~30 dias, preso ao número

Nunca persista o mediaId. Suba de novo a cada envio.

Suba as imagens de amostra

Um handle por card, em templates/header-media. É o que a Meta analisa.

Crie o template

POST /v3/connections/{id}/templates com categoria MARKETING, um BODY e um CAROUSEL cujos cards[].components tragam HEADER (com example.header_handle), BODY e BUTTONS.

Aguarde a aprovação

A resposta volta PENDING. Consulte GET /templates/{templateId} até virar APPROVED; um rejected_reason diz o que corrigir.

Suba as imagens de novo, para envio

Agora em templates/send-media, que devolve o mediaId que cada header de card precisa.

Envie

POST /v3/connections/{id}/chats/messages/send-template com um único componente carousel.

{
  "to": "5511999998888",
  "name": "catalogo_demo",
  "language": "pt_BR",
  "components": [
    { "type": "carousel", "cards": [
      { "card_index": 0, "components": [
        { "type": "header", "parameters": [{ "type": "image", "image": { "id": "28801092679526768" } }] },
        { "type": "button", "sub_type": "quick_reply", "index": 0,
          "parameters": [{ "type": "payload", "payload": "add:SKU-FONE-ANC" }] },
        { "type": "button", "sub_type": "quick_reply", "index": 1,
          "parameters": [{ "type": "payload", "payload": "det:SKU-FONE-ANC" }] }
      ]}
    ]}
  ]
}
json

O casing muda entre definir e enviar

Definir um template usa HEADER, BUTTONS, QUICK_REPLY. Enviar usa header, button, quick_reply — e o index do botão é inteiro, não string. É assimetria da própria Meta; a nossa validação aponta todos os campos errados de uma vez.

Dê um payload próprio a cada botão. O webhook não traz índice de card, então add:SKU-FONE-ANC nomeando produto e ação é o único jeito de saber qual card foi tocado.

Flows

Um Flow é um formulário com telas próprias. Existem dois tipos, e a diferença decide todo o resto:

navigatedata_exchange
Telasfixas no Flow JSONseu backend responde cada passo
Opções de dropdownfixas no JSONvêm do seu banco, na hora
Validaçãosó formatosua (o cupom vale? o horário ainda está livre?)
Precisa da chave de criptografianãosim

Agendamento é o caso canônico de data_exchange: o cliente escolhe o dia, e os horários livres daquele dia precisam sair do seu sistema naquele instante.

Crie e publique

POST /v3/connections/{id}/flows com flowJson (objeto ou string) e publish: true. Confira validation_errors na resposta antes de considerar publicado.

Envie

POST /v3/connections/{id}/chats/messages/send-flow. Dentro da janela de 24h — fora dela, use template com botão FLOW.

{
  "to": "5511999998888",
  "header": "Fale com a gente",
  "body": "Preencha o formulário e a gente te responde hoje.",
  "cta": "Abrir formulário",
  "flowId": "1626797462403345",
  "flowAction": "navigate",
  "screen": "FORMULARIO",
  "flowToken": "lead-8812",
  "mode": "published"
}
json

flowToken é a sua correlação e o único fio de volta — o webhook que traz o formulário preenchido não inclui o id do Flow. Mande um id de pedido, de ticket, de lead. Se omitir, a gente gera pingo_<uuid> e devolve na resposta do envio.

Flows dinâmicos

Para data_exchange, a Meta criptografa cada requisição com RSA-2048 e AES-128-GCM. O Pingo é o endpoint registrado: ele decripta, repassa o JSON em claro para você, assinado, e encripta a sua resposta de volta.

Provisione a chave

PUT /v3/connections/{id}/flows-encryption. O Pingo gera o par RSA e registra a pública na Meta. Exige o app secret da Meta na conexão — é ele que autentica cada requisição que a Meta envia.

Inscreva um webhook em `flow.data_exchange`

Não existe URL separada para Flows: quem responde as telas é um webhook normal da conexão inscrito nesse evento. O HMAC precisa estar ligado.

Aponte o Flow para o Pingo

Coloque a dataExchangeUrl do GET /flows-encryption no endpointUri do Flow. O Flow JSON também precisa de data_api_version: "3.0" e routing_model.

O seu endpoint então recebe, no meio do preenchimento:

{ "action": "data_exchange", "screen": "ESCOLHER_DIA",
  "data": { "dia": "2026-10-05" }, "flow_token": "agendamento-8812" }
json

e responde com a próxima tela:

{ "screen": "ESCOLHER_HORA",
  "data": { "horarios": [{ "id": "09:00", "title": "09:00" }, { "id": "14:30", "title": "14:30" }] } }
json

Este evento é uma pergunta, não uma notificação

Todos os outros webhooks ignoram a sua resposta. Aqui o corpo que você devolve é a tela que o cliente vê, e o WhatsApp está esperando: responda em cerca de cinco segundos, com um objeto JSON. Um 200 vazio quebra o formulário. A chave é do número, não da conexão — ela sobrevive quando você apaga a conexão.

O que volta

Toques e respostas chegam como messages.upsert no webhook da conexão, nos nós documentados na referência:

O que a pessoa fezmessageTypeOnde está o id
Tocou um botão do carrosseltemplateButtonReplyMessageselectedId — o payload que você mandou
Enviou um FlowinteractiveResponseMessagenativeFlowResponseMessage.paramsJson, uma string — faça o parse

O formulário preenchido chega sempre aqui, nos dois tipos de Flow. O endpoint de data-exchange só cuida da conversa durante o preenchimento — muita gente configura ele e depois se pergunta por que as respostas nunca apareceram.

Um 201 no envio é a Meta aceitando a chamada, não entregando a mensagem. O desfecho chega depois, assíncrono, em messages.update — leia statuses[].errors. O erro 131047 significa janela de 24h fechada.

© 2026 Pingo Notify. Todos os direitos reservados.

pingonotify.com ·Feito com Nuxt e Scalar