waiaconnect

Documentación / Completo

Plantillas carrusel (tarjetas con imagen o video)

POST /v1/templates

Markdown

¿Programás con IA? Conectala a tu cuenta por MCP: revisa tu configuración, crea webhooks y plantillas con tu confirmación y te escribe el código. Ver cómo →

Un mensaje con 2 a 10 tarjetas que se deslizan, cada una con su foto o video, su texto y sus botones. Lo armás desde tu sistema y lo mandás a aprobación, igual que cualquier plantilla:

1. Una muestra por tarjeta: POST /v1/templates/media con la imagen (JPG/PNG) o el video (MP4) → handle. Es lo que Meta revisa; en cada envío mandás la foto de verdad.
2. Crear: POST /v1/templates con category: "MARKETING", el body principal y carousel.cards[]. Cada tarjeta: header { format: IMAGE|VIDEO, handle }, body opcional y hasta 2 botones (QUICK_REPLY, URL, PHONE_NUMBER). Antes de Meta chequeamos sus reglas: misma cantidad de tarjetas al enviar, el mismo formato en todas, los mismos botones en el mismo orden, el texto de la tarjeta hasta 160 caracteres (en todas o en ninguna) y ejemplos para cada {{n}}. POST /v1/templates/validate lo prueba sin crear nada; /preview?format=html dibuja la tira deslizable.
3. Esperar template.status_changed con APPROVED.
4. Enviar: POST /v1/messages con template.carousel.cards[], una por tarjeta y en orden: la header (image/video con mediaId o link), los valores del body y de los botones (quick_reply → tu payload, url → el final del enlace).
5. El clic: llega message.received con button.payload intacto y tap { cardIndex, buttonIndex }. Meta no manda esos índices: los resolvemos nosotros con tu payload.

Lo que pone Meta: el carrusel existe sólo como MARKETING y cada envío se cobra como marketing; Meta lo aprueba o lo rechaza como cualquier plantilla; desde el 1/4/2025 no entrega marketing a números de EE. UU. (131049), y aplica sus topes por persona a todo el 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)

Dale a cada respuesta rápida su propio payload (por ejemplo buzo-gris:me-interesa): con dos iguales en el mismo mensaje no se puede saber cuál tocó, y tap no sale. Un «Darme de baja» como respuesta rápida te llega igual: la baja la registra tu sistema.

Los botones de enlace y de llamada no generan aviso: Meta abre el navegador o el teléfono.

Un carrusel creado en WhatsApp Manager se sincroniza con sus tarjetas (GET /v1/templates, carousel.cards) y se envía igual.

El carrusel de productos del catálogo (botón «Ver», compra dentro de WhatsApp) todavía no.