Skip to main content
El flujo completo de una campaña de WhatsApp en v1, de la API key al reporte de entrega. Cada paso trae ejemplos copiables en cURL, Node y Python. Los conceptos (plantillas, ventana de 24 h, opt-out, estados) están en la guía de Outbound.
Trabajaremos con una key en la variable KEY y el canal de WhatsApp con channelId/integrationId = 9. Sustituye por los tuyos. Todas las llamadas usan la URL base https://api-v2.sofiachat.com/api/v1.

1 · Crea la key y verifica el acceso

Crea una API key con scopes read y write desde el panel (Configuración → API) y confirma que responde y que tu plan incluye campañas con GET /v1/me:
En la respuesta, revisa plan.limits.conversations y plan.usage.conversations (margen disponible) y que apiKey.scopes incluya write. Si Outbound no está en tu plan, las llamadas siguientes responden 403 PLAN_FEATURE_REQUIRED.

2 · Consigue una plantilla aprobada

Una campaña envía una plantilla aprobada. Lista las del canal con GET /v1/templates:
Si el arreglo data viene vacío, créala con POST /v1/templates y envíala a revisión de Meta. Usa placeholders con nombre en el cuerpo:
Ojo con el nombrado. Este endpoint y POST /v1/campaigns reciben integrationId (no channelId), y category / buttons[].type van en MAYÚSCULAS.
La aprobación de Meta suele tardar minutos, pero puede tomar hasta 24 h. Sincroniza con POST /v1/templates/sync para traer el estado actualizado y repite el GET hasta ver status: "approved":
Guarda el id de la plantilla aprobada (lo usaremos como templateId).

3 · Define los campos personalizados

Las variables de la plantilla que no son datos estándar del contacto viven en custom_fields. Declara su clave antes de importar contactos con POST /v1/contact-fields:

4 · Carga los contactos

Crea contactos uno a uno con POST /v1/contacts y sus custom_fields…
…o impórtalos en lote desde un XLSX/CSV con POST /v1/contacts/import (mismas columnas que la plantilla de importación del panel):
Guarda los id de los contactos creados.

5 · Crea el grupo y añade los contactos

El grupo es la audiencia reutilizable de la campaña. Créalo con POST /v1/contact-groups y llénalo con POST /v1/contact-groups/:id/contacts:

6 · Crea la campaña y mapea cada variable

Crea la campaña con POST /v1/campaigns. Sin scheduledAt, queda en draft (buen momento para revisar antes de enviar):
El cuerpo de la plantilla era "Hola {{nombre}}, tu {{producto}} vence el {{fecha}}.". El arreglo parameterMapping.body se lee en orden, un elemento por variable: Cada variable admite tres orígenes: contact_field con field (first_name, last_name, email, phone, full_name), custom_field con key, o static con value (mismo texto para todos). Guarda el id de la campaña que devuelve la respuesta.

7 · Dispara el envío

Inícialo con POST /v1/campaigns/:id/send:
El envío es asíncrono: la campaña pasa a sending y los mensajes salen de forma progresiva. (Si programaste scheduledAt, saldrá sola a esa hora; send la fuerza de inmediato.) Para detener una campaña en draft o scheduled, usa POST /v1/campaigns/:id/cancel.

8 · Sigue el avance y revisa los fallos

Consulta los contadores en el detalle de la campaña con GET /v1/campaigns/:id:
recipientCounts resume el estado: { total, pending, sending, sent, delivered, read, failed, skipped }. Cuando pending y sending llegan a 0, terminó. Lista los destinatarios que fallaron con GET /v1/campaigns/:id/recipients para depurar tus listas:
Cada destinatario incluye el contacto, su status, el wa_message_id y el error si lo hubo.

9 · Interpreta los fallos más comunes

Cuando un destinatario responde, se abre la ventana de 24 h y su conversación entra al panel bajo el conversationTagId que definiste, para que la atienda la IA o un agente. Para automatizar este ciclo (renovaciones diarias, reintentos), mira las recetas de integración.