Usar plantillas y Flows de WhatsApp
Los mensajes pre-aprobados por Meta y los formularios que abre una conversación de WhatsApp.
Escribirle a un contacto fuera de la ventana de 24 horas pide una plantilla aprobada por Meta. Abrir un formulario dentro de la conversación pide un Flow. Los dos son catálogos de configuración del canal WhatsApp. Ninguno envía nada por sí solo: ambos se consumen desde el envío de mensajes de una conversación.
Plantillas
Una plantilla (whatsapp_template) es la forma pre-aprobada por Meta para escribirle a un contacto fuera de la ventana de 24 horas. Se crea aquí y se sincroniza con Meta. No hay webhook de aprobación, así que status (PENDING → APPROVED | REJECTED | …) se reconcilia con un sondeo periódico. POST /whatsapp-templates/sync fuerza un reconcile bajo demanda.
Leer y escribir piden campaigns:read / campaigns:write.
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-templates \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"name": "seguimiento_consulta",
"language": "es_CL",
"category": "MARKETING",
"body_text": "Hola {{1}}, {{2}} sigue disponible. ¿Seguimos adelante?",
"footer_text": "Equipo de atención"
}'name es solo minúsculas, dígitos y guión bajo (restricción de Meta). language se ve como es, es_CL o en_US. La respuesta es 201 con status: "PENDING": la aprobación es asíncrona.
param_meta guarda la etiqueta y el ejemplo de cada {{variable}}, para quien redacta. param_binding dice qué llena cada variable: un campo del contacto, un atributo personalizado, el interés del lead, un literal, o «lo escribe la persona». Los dos se editan aparte, con PATCH /whatsapp-templates/{id}/param-meta y PATCH /whatsapp-templates/{id}/param-binding. Ninguno toca Meta ni re-dispara aprobación: describen cómo Vitrina llena un cuerpo ya aprobado, no el cuerpo en sí.
{
"data": {
"id": "e2e2e2e2-0000-4000-8000-000000000001",
"name": "seguimiento_consulta",
"language": "es_CL",
"status": "APPROVED",
"param_binding": {
"1": { "source": "contact_field", "field": "name" },
"2": { "source": "lead_interest" }
}
}
}POST /whatsapp-templates/{id}/infer-bindings re-sugiere un binding para cada {{variable}}. La regla de fusión nunca sobrescribe un slot que una persona ya asignó a mano (origin: "manual"), así que volver a correrlo nunca pierde una decisión del workspace.
Flows
Un Flow (whatsapp_flow) es un formulario estructurado de varias pantallas que se abre dentro de la conversación: agendar, inscribirse, una encuesta corta.
Hay dos superficies. GET /whatsapp-flows es un espejo en vivo de lo que Meta reporta para un canal. Es lo que ve el selector del compositor, e incluye un Flow creado fuera de Vitrina. /whatsapp-flows/managed/* es el constructor: filas locales con builder_state como fuente de verdad, que son pantallas ordenadas de una paleta de componentes. Guardar regenera el Flow JSON y lo sube a Meta.
curl -X POST https://api.vitrinadev.com/api/v1/whatsapp-flows/managed \
-H "Authorization: Bearer $VITRINA_KEY" \
-H "Content-Type: application/json" \
-d '{
"messaging_account_id": "e4e4e4e4-0000-4000-8000-000000000001",
"name": "Agenda tu visita",
"categories": ["APPOINTMENT_BOOKING"],
"builder_state": {
"screens": [{
"title": "¿Cuándo te acomoda?",
"footer_label": "Continuar",
"components": [
{ "kind": "heading", "text": "Agenda tu visita" },
{ "kind": "date_picker", "name": "fecha", "label": "Fecha preferida", "required": true }
]
}]
}
}'{
"data": {
"id": "e3e3e3e3-0000-4000-8000-000000000001",
"meta_flow_id": "1200300400500600",
"name": "Agenda tu visita",
"status": "DRAFT",
"validation_errors": null
}
}meta_flow_id es el id que asigna Meta al subir el Flow. No lo generamos nosotros, así que no es un uuid. validation_errors es el veredicto de la última subida, y bloquea publish mientras no esté vacío.
Trampa
PATCH y DELETE sobre un Flow publicado responden 409
Meta lo vuelve inmutable al publicarlo. POST /whatsapp-flows/managed/{id}/duplicate es la salida: siempre crea una copia
nueva en DRAFT, lista para seguir editando. Una fila con builder_state: null fue creada fuera de Vitrina y aquí es de solo lectura.