¿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. Esperartemplate.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 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.