waiaconnect

Documentação / Completo

Modelos carrossel (cartões com imagem ou vídeo)

POST /v1/templates

Markdown

Programa com IA? Conecte-a à sua conta por MCP: ela revisa sua configuração, cria webhooks e modelos com sua confirmação e escreve o código. Veja como →

Uma mensagem com 2 a 10 cartões que deslizam, cada um com sua foto ou vídeo, seu texto e seus botões. Você monta pelo seu sistema e envia para aprovação, como qualquer modelo:

1. Uma amostra por cartão: POST /v1/templates/media com a imagem (JPG/PNG) ou o vídeo (MP4) → handle. É o que a Meta revisa; em cada envio vai a foto de verdade.
2. Criar: POST /v1/templates com category: "MARKETING", o body principal e carousel.cards[]. Cada cartão: header { format: IMAGE|VIDEO, handle }, body opcional e até 2 botões (QUICK_REPLY, URL, PHONE_NUMBER). Antes da Meta checamos as regras dela: a mesma quantidade de cartões ao enviar, o mesmo formato em todos, os mesmos botões na mesma ordem, o texto do cartão até 160 caracteres (em todos ou em nenhum) e exemplos para cada {{n}}. POST /v1/templates/validate testa sem criar nada; /preview?format=html desenha a faixa deslizante.
3. Esperar template.status_changed com APPROVED.
4. Enviar: POST /v1/messages com template.carousel.cards[], um por cartão e em ordem: o header (image/video com mediaId ou link), os valores do body e dos botões (quick_reply → seu payload, url → o final do link).
5. O toque: chega message.received com button.payload intacto e tap { cardIndex, buttonIndex }. A Meta não manda esses índices: nós os resolvemos com o seu payload.

O que a Meta define: o carrossel existe só como MARKETING e cada envio é cobrado como marketing; a Meta aprova ou rejeita como qualquer modelo; desde 1/4/2025 não entrega marketing a números dos EUA (131049), e aplica seus limites por pessoa a todo o marketing.

# 1) One sample image per card (Meta reviews them) → handles
H1=$(curl -s -X POST https://api.waiaconnect.com/v1/templates/media -H "Authorization: Bearer wc_live_YOUR_API_KEY" -F file=@remera.jpg | jq -r .handle)
H2=$(curl -s -X POST https://api.waiaconnect.com/v1/templates/media -H "Authorization: Bearer wc_live_YOUR_API_KEY" -F file=@buzo.jpg | jq -r .handle)

# 2) Create the carousel (MARKETING, 2–10 cards, same buttons in every card)
BODY='{"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","name":"novedades_semana","language":"es","category":"MARKETING","body":{"text":"Hola {{1}}, mirá lo nuevo de esta semana.","examples":["Martín"]},"carousel":{"cards":[{"header":{"format":"IMAGE","handle":"H1"},"body":{"text":"Remera {{1}} de algodón.","examples":["azul"]},"buttons":[{"type":"QUICK_REPLY","text":"Me interesa"},{"type":"URL","text":"Ver","url":"https://your-shop.example/p/{{1}}","example":"remera-azul"}]},{"header":{"format":"IMAGE","handle":"H2"},"body":{"text":"Buzo {{1}} de algodón.","examples":["gris"]},"buttons":[{"type":"QUICK_REPLY","text":"Me interesa"},{"type":"URL","text":"Ver","url":"https://your-shop.example/p/{{1}}","example":"buzo-gris"}]}]}}'
BODY=${BODY/H1/$H1}; BODY=${BODY/H2/$H2}
curl -s -X POST https://api.waiaconnect.com/v1/templates/validate -H "Authorization: Bearer wc_live_YOUR_API_KEY" -H "Content-Type: application/json" -d "$BODY"
curl -s -X POST https://api.waiaconnect.com/v1/templates -H "Authorization: Bearer wc_live_YOUR_API_KEY" -H "Content-Type: application/json" -d "$BODY"

# 3) Wait for the template.status_changed webhook with status APPROVED

# 4) Send it: one card per card of the template, in order
curl -s -X POST https://api.waiaconnect.com/v1/messages -H "Authorization: Bearer wc_live_YOUR_API_KEY" \
  -H "Content-Type: application/json" -H "Idempotency-Key: $(uuidgen)" \
  -d '{"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"template","template":{"name":"novedades_semana","language":"es","body":["Sofía"],"carousel":{"cards":[{"header":{"type":"image","link":"https://your-shop.example/img/remera-azul.jpg"},"body":["azul"],"buttons":[{"type":"quick_reply","payload":"remera-azul:me-interesa"},{"type":"url","text":"remera-azul"}]},{"header":{"type":"image","link":"https://your-shop.example/img/buzo-gris.jpg"},"body":["gris"],"buttons":[{"type":"quick_reply","payload":"buzo-gris:me-interesa"},{"type":"url","text":"buzo-gris"}]}]}}}'

# 5) The tap arrives as message.received:
#    data.message.button.payload = "buzo-gris:me-interesa"  (yours, intact)
#    data.tap = { "cardIndex": 1, "buttonIndex": 0 }
import { readFile } from "node:fs/promises";
import { randomUUID } from "node:crypto";
const H = { Authorization: "Bearer wc_live_YOUR_API_KEY" };
const J = { ...H, "Content-Type": "application/json" };

// 1) One sample image per card (Meta reviews them) → handles
const sample = async (file) => {
  const form = new FormData();
  form.append("file", new Blob([await readFile(file)]), file);
  return (await (await fetch("https://api.waiaconnect.com/v1/templates/media", { method: "POST", headers: H, body: form })).json()).handle;
};
const [h1, h2] = [await sample("remera.jpg"), await sample("buzo.jpg")];

// 2) Create the carousel (MARKETING, 2–10 cards, same buttons in every card)
const def = {
  "connectionId": "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
  "name": "novedades_semana",
  "language": "es",
  "category": "MARKETING",
  "body": {
    "text": "Hola {{1}}, mirá lo nuevo de esta semana.",
    "examples": [
      "Martín"
    ]
  },
  "carousel": {
    "cards": [
      {
        "header": {
          "format": "IMAGE",
          "handle": h1
        },
        "body": {
          "text": "Remera {{1}} de algodón.",
          "examples": [
            "azul"
          ]
        },
        "buttons": [
          {
            "type": "QUICK_REPLY",
            "text": "Me interesa"
          },
          {
            "type": "URL",
            "text": "Ver",
            "url": "https://your-shop.example/p/{{1}}",
            "example": "remera-azul"
          }
        ]
      },
      {
        "header": {
          "format": "IMAGE",
          "handle": h2
        },
        "body": {
          "text": "Buzo {{1}} de algodón.",
          "examples": [
            "gris"
          ]
        },
        "buttons": [
          {
            "type": "QUICK_REPLY",
            "text": "Me interesa"
          },
          {
            "type": "URL",
            "text": "Ver",
            "url": "https://your-shop.example/p/{{1}}",
            "example": "buzo-gris"
          }
        ]
      }
    ]
  }
};
const v = await (await fetch("https://api.waiaconnect.com/v1/templates/validate", { method: "POST", headers: J, body: JSON.stringify(def) })).json();
if (!v.ok) throw new Error(v.errors.map(e => `${e.field}: ${e.message}`).join("\n"));
await fetch("https://api.waiaconnect.com/v1/templates", { method: "POST", headers: J, body: JSON.stringify(def) });

// 3) Wait for template.status_changed (APPROVED) on your webhook, then:
// 4) Send it: one card per card of the template, in order
await fetch("https://api.waiaconnect.com/v1/messages", {
  method: "POST",
  headers: { ...J, "Idempotency-Key": randomUUID() },
  body: JSON.stringify({
    "connectionId": "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
    "to": "5493511234567",
    "type": "template",
    "template": {
      "name": "novedades_semana",
      "language": "es",
      "body": [
        "Sofía"
      ],
      "carousel": {
        "cards": [
          {
            "header": {
              "type": "image",
              "link": "https://your-shop.example/img/remera-azul.jpg"
            },
            "body": [
              "azul"
            ],
            "buttons": [
              {
                "type": "quick_reply",
                "payload": "remera-azul:me-interesa"
              },
              {
                "type": "url",
                "text": "remera-azul"
              }
            ]
          },
          {
            "header": {
              "type": "image",
              "link": "https://your-shop.example/img/buzo-gris.jpg"
            },
            "body": [
              "gris"
            ],
            "buttons": [
              {
                "type": "quick_reply",
                "payload": "buzo-gris:me-interesa"
              },
              {
                "type": "url",
                "text": "buzo-gris"
              }
            ]
          }
        ]
      }
    }
  })
});

// 5) In your webhook handler: which card and which button
export const onEvent = (evt) => {
  if (evt.type !== "message.received" || evt.data.message.type !== "button") return;
  const payload = evt.data.message.button.payload; // yours, intact
  const { cardIndex, buttonIndex } = evt.data.tap ?? {}; // e.g. 1 and 0
  console.log(payload, cardIndex, buttonIndex);
};
<?php
$key = "wc_live_YOUR_API_KEY";
function call($url, $key, $body, $json = true, $extra = []) {
  $ch = curl_init($url);
  curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => array_merge(["Authorization: Bearer $key"], $json ? ["Content-Type: application/json"] : [], $extra),
    CURLOPT_POSTFIELDS => $json ? json_encode($body) : $body,
  ]);
  return json_decode(curl_exec($ch), true);
}
// 1) One sample image per card → handles
$h1 = call("https://api.waiaconnect.com/v1/templates/media", $key, ["file" => new CURLFile("remera.jpg")], false)["handle"];
$h2 = call("https://api.waiaconnect.com/v1/templates/media", $key, ["file" => new CURLFile("buzo.jpg")], false)["handle"];

// 2) Create the carousel (MARKETING, 2–10 cards, same buttons in every card)
$def = ["connectionId" => "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","name" => "novedades_semana","language" => "es","category" => "MARKETING","body" => ["text" => "Hola {{1}}, mirá lo nuevo de esta semana.","examples" => ["Martín"]],"carousel" => ["cards" => [["header" => ["format" => "IMAGE","handle" => $h1],"body" => ["text" => "Remera {{1}} de algodón.","examples" => ["azul"]],"buttons" => [["type" => "QUICK_REPLY","text" => "Me interesa"],["type" => "URL","text" => "Ver","url" => "https://your-shop.example/p/{{1}}","example" => "remera-azul"]]],["header" => ["format" => "IMAGE","handle" => $h2],"body" => ["text" => "Buzo {{1}} de algodón.","examples" => ["gris"]],"buttons" => [["type" => "QUICK_REPLY","text" => "Me interesa"],["type" => "URL","text" => "Ver","url" => "https://your-shop.example/p/{{1}}","example" => "buzo-gris"]]]]]];
call("https://api.waiaconnect.com/v1/templates", $key, $def);

// 3) Wait for template.status_changed (APPROVED) · 4) send it, one card per card, in order
$send = ["connectionId" => "conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to" => "5493511234567","type" => "template","template" => ["name" => "novedades_semana","language" => "es","body" => ["Sofía"],"carousel" => ["cards" => [["header" => ["type" => "image","link" => "https://your-shop.example/img/remera-azul.jpg"],"body" => ["azul"],"buttons" => [["type" => "quick_reply","payload" => "remera-azul:me-interesa"],["type" => "url","text" => "remera-azul"]]],["header" => ["type" => "image","link" => "https://your-shop.example/img/buzo-gris.jpg"],"body" => ["gris"],"buttons" => [["type" => "quick_reply","payload" => "buzo-gris:me-interesa"],["type" => "url","text" => "buzo-gris"]]]]]]];
call("https://api.waiaconnect.com/v1/messages", $key, $send, true, ["Idempotency-Key: " . bin2hex(random_bytes(16))]);

// 5) In your webhook: $evt["data"]["message"]["button"]["payload"] (yours, intact)
//    and $evt["data"]["tap"]["cardIndex"], $evt["data"]["tap"]["buttonIndex"]
import uuid, requests
H = {"Authorization": "Bearer wc_live_YOUR_API_KEY"}

# 1) One sample image per card (Meta reviews them) -> handles
h1 = requests.post("https://api.waiaconnect.com/v1/templates/media", headers=H, files={"file": open("remera.jpg", "rb")}).json()["handle"]
h2 = requests.post("https://api.waiaconnect.com/v1/templates/media", headers=H, files={"file": open("buzo.jpg", "rb")}).json()["handle"]

# 2) Create the carousel (MARKETING, 2-10 cards, same buttons in every card)
definition = {"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","name":"novedades_semana","language":"es","category":"MARKETING","body":{"text":"Hola {{1}}, mirá lo nuevo de esta semana.","examples":["Martín"]},"carousel":{"cards":[{"header":{"format":"IMAGE","handle":h1},"body":{"text":"Remera {{1}} de algodón.","examples":["azul"]},"buttons":[{"type":"QUICK_REPLY","text":"Me interesa"},{"type":"URL","text":"Ver","url":"https://your-shop.example/p/{{1}}","example":"remera-azul"}]},{"header":{"format":"IMAGE","handle":h2},"body":{"text":"Buzo {{1}} de algodón.","examples":["gris"]},"buttons":[{"type":"QUICK_REPLY","text":"Me interesa"},{"type":"URL","text":"Ver","url":"https://your-shop.example/p/{{1}}","example":"buzo-gris"}]}]}}
requests.post("https://api.waiaconnect.com/v1/templates", headers=H, json=definition).raise_for_status()

# 3) Wait for template.status_changed (APPROVED) on your webhook, then
# 4) send it: one card per card of the template, in order
requests.post("https://api.waiaconnect.com/v1/messages", headers={**H, "Idempotency-Key": str(uuid.uuid4())}, json={"connectionId":"conn_XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX","to":"5493511234567","type":"template","template":{"name":"novedades_semana","language":"es","body":["Sofía"],"carousel":{"cards":[{"header":{"type":"image","link":"https://your-shop.example/img/remera-azul.jpg"},"body":["azul"],"buttons":[{"type":"quick_reply","payload":"remera-azul:me-interesa"},{"type":"url","text":"remera-azul"}]},{"header":{"type":"image","link":"https://your-shop.example/img/buzo-gris.jpg"},"body":["gris"],"buttons":[{"type":"quick_reply","payload":"buzo-gris:me-interesa"},{"type":"url","text":"buzo-gris"}]}]}}})

# 5) In your webhook handler: which card and which button
def on_event(evt):
    if evt["type"] == "message.received" and evt["data"]["message"]["type"] == "button":
        payload = evt["data"]["message"]["button"]["payload"]  # yours, intact
        tap = evt["data"].get("tap")  # {"cardIndex": 1, "buttonIndex": 0}
        print(payload, tap)

Dê a cada resposta rápida o seu próprio payload (por exemplo moletom-cinza:quero): com dois iguais na mesma mensagem não dá para saber qual foi tocado, e tap não vem. Um «Sair da lista» como resposta rápida chega igual: o descadastro fica com o seu sistema.

Os botões de link e de ligação não geram aviso: a Meta abre o navegador ou o telefone.

Um carrossel criado no WhatsApp Manager é sincronizado com seus cartões (GET /v1/templates, carousel.cards) e é enviado igual.

O carrossel de produtos do catálogo (botão «Ver», compra dentro do WhatsApp) ainda não.