Por Amy Baker
Publicado el 25 jul 2026
Cómo enviar un mensaje de plantilla de WhatsApp mediante la API
¿Estás creando un sistema de notificaciones por WhatsApp? Esta guía cubre las tres formas de enviar mensajes de plantilla mediante la API de Chakra, los dos patrones distintos para sustituir variables, cómo gestionar los errores 131049 y 131026, el seguimiento de entregas mediante webhooks y los envíos masivos con límites de frecuencia.
Si estás creando sistemas de notificaciones en WhatsApp —actualizaciones de pedidos, recordatorios de citas, OTP y flujos para volver a interactuar con clientes—, tarde o temprano necesitarás enviar un mensaje de plantilla mediante programación. Chakra te ofrece tres formas distintas de hacerlo, cada una adecuada para un patrón de integración diferente. Esta guía cubre las tres: la estructura de sus solicitudes, cuándo usar cada una, la sustitución de variables y cómo gestionar los dos errores que encontrarás con más frecuencia en producción: 131049 y 131026.
Se asume que ya sabes «qué es una plantilla de WhatsApp»: tienes una WABA aprobada, plantillas aprobadas y una clave de API. Si necesitas configurar estos elementos, consulta la sección de claves de API de Chakra , donde puedes generar tu token bearer.
Autenticación en pocas palabras
Cada solicitud necesita un token bearer generado a partir de una clave de API con el alcance de rol/privilegios adecuado:
curl --request GET \ --url https://api.chakrahq.com/v1/ext/config \ --header 'Authorization: Bearer <your_access_token>'Asigna a las claves solo los privilegios mínimos necesarios, sobre todo si se usan en código del lado del cliente. También conviene saber desde el principio que Chakra aplica un límite de frecuencia de 200 llamadas a la API/minuto : tenlo en cuenta en cualquier ciclo de envíos masivos en lugar de descubrirlo por los errores 429 en producción.
Las tres formas de enviar un mensaje de plantilla
| Método | Patrón del endpoint | Ideal para |
|---|---|---|
| Por proceso | .../process/{procedureShortId}/{primaryKeyOrId}/send-template-message | Envíos vinculados a un registro del CRM, como un prospecto, ticket o caso que ya está en Chakra |
| Por número de teléfono | .../phoneNumber/{phoneNumber}/send-template-message | Envíos sencillos e independientes cuando ya tienes el número del destinatario y no necesitas vincularlo a un registro |
| Formato de la API de Meta | .../api/{whatsappApiVersion}/{whatsappPhoneNumberId}/messages | Control total y el payload exacto de Meta Cloud API: útil si estás migrando código de una integración existente con Meta o necesitas tipos de mensaje que los endpoints simplificados de Chakra no abstraen |
Los tres métodos usan, en última instancia, la misma infraestructura de WhatsApp: la diferencia está en la estructura del payload y en el contexto que Chakra adjunta al envío.
Método 1: enviar por proceso
Usa este método cuando el mensaje esté vinculado a un registro que ya existe en la plataforma de Chakra: un prospecto, ticket, visita de campo o cualquier tipo de proceso personalizado. Es la opción adecuada si quieres que el mensaje saliente quede registrado automáticamente en ese registro, sin tener que asociarlos en un paso aparte.
Endpoint:
POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/process/{procedureShortId}/{primaryKeyOrId}/send-template-messagepluginId— el UUID de tu conexión de WhatsApp, que puedes copiar desde la página de detalles del plugin en la pantalla de configuraciónprocedureShortId— el tipo de registro (lead,ticket,caseo un shortId de proceso personalizado)primaryKeyOrId— el ID o la clave principal del registro específico
Ejemplo con curl:
curl --location --request POST \ 'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/process/lead/8842/send-template-message' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data-raw '{ "whatsappPhoneNumberId": "775966265503012", "templateName": "order_confirmation", "mapping": [ { "schemaPropertyName": "1", "schemaPropertyValue": "John" }, { "schemaPropertyName": "2", "schemaPropertyValue": "ORD-4521" } ] }'JavaScript (fetch):
async function sendTemplateForLead(leadId, templateName, variables) { const response = await fetch( `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/process/lead/${leadId}/send-template-message`, { method: "POST", headers: { "Authorization": `Bearer ${API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ whatsappPhoneNumberId: PHONE_NUMBER_ID, templateName, mapping: variables.map((value, i) => ({ schemaPropertyName: String(i + 1), schemaPropertyValue: value, })), }), } ); if (!response.ok) { const error = await response.json(); throw new Error(`Template send failed: ${JSON.stringify(error)}`); } return response.json();}Método 2: enviar por número de teléfono
El payload es funcionalmente idéntico al del Método 1, pero el envío se hace directamente a un número de teléfono en lugar de a un registro de proceso vinculado. Úsalo cuando la asociación con un registro no sea relevante, por ejemplo, para un envío activado por un webhook desde un sistema externo (Shopify, WooCommerce o tu propio backend) que no necesite un prospecto correspondiente en Chakra.
Endpoint:
POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/phoneNumber/{phoneNumber}/send-template-messageEjemplo con curl:
curl --location --request POST \ 'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/phoneNumber/919901258433/send-template-message' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data-raw '{ "whatsappPhoneNumberId": "775966265503012", "templateName": "shipping_update", "mapping": [ { "schemaPropertyName": "1", "schemaPropertyValue": "Priya" } ], "imageUrl": "https://your-cdn.com/shipping-banner.png" }'JavaScript (fetch):
async function sendTemplateByPhone(phoneNumber, templateName, mapping, imageUrl) { const body = { whatsappPhoneNumberId: PHONE_NUMBER_ID, templateName, mapping }; if (imageUrl) body.imageUrl = imageUrl; const response = await fetch( `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/phoneNumber/${phoneNumber}/send-template-message`, { method: "POST", headers: { "Authorization": `Bearer ${API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify(body), } ); return handleWhatsAppResponse(response); // shared error handler, see below}Ten en cuenta el campo imageUrl : si tu plantilla tiene un componente de encabezado con imagen, proporciona aquí una URL de acceso público en lugar de subir el contenido multimedia por separado, a menos que uses el endpoint en formato de Meta, que espera un ID de contenido multimedia.
Método 3: formato de la API de Meta (paso directo sin modificaciones)
Este endpoint es una capa delgada: Chakra envía tu payload directamente a la Cloud API de Meta, usando el esquema de Meta. Úsalo si ya tienes lógica de payload en formato de Meta (migrada de una integración directa anterior con Cloud API o creada a partir de la documentación de Meta), si necesitas tipos de mensaje o componentes que los endpoints simplificados de Chakra no admiten, o si quieres que las solicitudes y respuestas coincidan exactamente con las de Meta para tus propios registros.
Endpoint:
POST https://api.chakrahq.com/v1/ext/plugin/whatsapp/{pluginId}/api/{whatsappApiVersion}/{whatsappPhoneNumberId}/messagesEjemplo con curl:
curl --location --request POST \ 'https://api.chakrahq.com/v1/ext/plugin/whatsapp/d83e1d23-50b8-4d87-8f92-842a0ac516f6/api/v19.0/775966265503012/messages' \ --header 'Authorization: Bearer <token>' \ --header 'Content-Type: application/json' \ --data-raw '{ "messaging_product": "whatsapp", "recipient_type": "individual", "to": "919901258433", "type": "template", "template": { "name": "order_confirmation", "language": { "policy": "deterministic", "code": "en_US" }, "components": [ { "type": "body", "parameters": ["John", "ORD-4521"] } ] } }'JavaScript (fetch):
async function sendTemplateMetaFormat(to, templateName, langCode, bodyParams) { const response = await fetch( `https://api.chakrahq.com/v1/ext/plugin/whatsapp/${PLUGIN_ID}/api/v19.0/${PHONE_NUMBER_ID}/messages`, { method: "POST", headers: { "Authorization": `Bearer ${API_TOKEN}`, "Content-Type": "application/json", }, body: JSON.stringify({ messaging_product: "whatsapp", recipient_type: "individual", to, type: "template", template: { name: templateName, language: { policy: "deterministic", code: langCode }, components: [{ type: "body", parameters: bodyParams }], }, }), } ); return handleWhatsAppResponse(response);}Este mismo endpoint también acepta los payloads nativos de Meta para mensajes de sesión (de formato libre), como texto, imágenes y listas interactivas, ya que es un paso directo a la API /messages subyacente, no un endpoint exclusivo para plantillas.
Sustitución de variables: dos patrones distintos
Este punto suele generar confusión al cambiar de método, así que conviene dejarlo claro:
Los métodos 1 y 2 (la abstracción de Chakra) usan un array de mapping con pares { schemaPropertyName, schemaPropertyValue } . schemaPropertyName es la posición de la variable de la plantilla expresada como cadena ("1", "2", ...), y schemaPropertyValue es el valor que la sustituye. Este es el esquema simplificado propio de Chakra, independiente de la estructura sin procesar de los componentes de Meta.
El método 3 (formato de Meta) usa el array nativo de Meta components , donde cada componente (header, body, button) tiene su propio array de parameters , y las variables se completan según su posición y orden. Si tu plantilla tiene varias variables en el cuerpo, parameters: ["John", "ORD-4521"] completa {{1}} y {{2}} respectivamente, siguiendo el orden del array: no hay un campo de índice explícito, así que es importante respetar el orden.
Si estás creando un sistema que podría cambiar de endpoint más adelante (por ejemplo, empezar con algo sencillo y luego necesitar funciones sin procesar de Meta), vale la pena escribir una función adaptadora interna que convierta tu propia lista de variables al formato que espera el endpoint que estés usando, en lugar de duplicar la lógica de asignación de variables en todo el código.
Cómo gestionar los errores que realmente encontrarás
Error 131049 — Interacción con el ecosistema / mensaje no entregado. No se trata de un error de solicitud mal formada: el sistema de WhatsApp rechaza la entrega porque es probable que el destinatario no quiera recibir el mensaje (poca interacción reciente, muchos bloqueos o un límite de marketing por usuario alcanzado entre empresas, en general). No vuelvas a intentar enviar el mismo mensaje de inmediato : un ciclo de reintentos inmediato solo repetirá el error y podría empeorar la situación para ese contacto. Registra el error, excluye el contacto del siguiente ciclo de envío y vuelve a intentarlo después de una pausa (24 horas es un mínimo razonable) con una plantilla Utility, si el contexto lo permite.
Error 131026 — El destinatario no está en WhatsApp o no es posible contactarlo. El número no es válido en WhatsApp, no se aceptaron los términos vigentes o no se puede recibir este tipo de mensaje. Es un problema de calidad de datos, no algo transitorio: volver a intentarlo no lo solucionará. Marca el contacto para depurar la lista en lugar de volver a ponerlo en cola.
Un gestor de errores compartido para los tres métodos podría verse así:
async function handleWhatsAppResponse(response) { if (response.ok) return response.json(); const errorBody = await response.json(); const errorCode = errorBody?.error?.code || errorBody?._data?.error?.code; switch (errorCode) { case 131049: // Don't retry now. Queue for a delayed retry, exclude from current campaign. await flagForDelayedRetry(errorBody, { minDelayHours: 24 }); break; case 131026: // Bad number — don't retry, flag for cleanup. await flagInvalidNumber(errorBody); break; default: // Log and surface unexpected errors for manual review. console.error("Unhandled WhatsApp send error:", errorBody); } throw new Error(`WhatsApp send failed (${errorCode}): ${JSON.stringify(errorBody)}`);}Incorporar este tipo de clasificación a tu flujo de envíos desde el principio evita que un ciclo de reintentos insista silenciosamente en los mismos números que fallan, una de las formas más rápidas de perjudicar tu calificación de calidad.
Seguimiento del estado de entrega después del envío
Una respuesta 200 de cualquiera de los tres endpoints solo confirma que el mensaje se aceptó para su envío; no confirma su entrega ni su estado de lectura. Para eso, debes consumir el webhook de eventos entrantesde Chakra, que envía actualizaciones de estado (sent, delivered, read, failed) a medida que ocurren, asociadas al whatsappMessageId o al externalId devuelto en la respuesta original del envío.
Un controlador de webhook básico:
app.post("/webhooks/whatsapp", (req, res) => { const event = req.body; if (event.type === "message_status") { const { messageId, status, errorCode } = event.data; // status: sent | delivered | read | failed updateMessageRecord(messageId, { status, errorCode }); if (status === "failed" && errorCode) { handleWhatsAppResponse({ ok: false, json: async () => ({ error: { code: errorCode } }) }); } } res.sendStatus(200); // acknowledge quickly; don't block on downstream processing});Guardar el whatsappMessageId junto con tu propio registro (ID del pedido, ID del lead o lo que haya activado el envío) en el momento de enviar es lo que hace que este webhook sea realmente útil; de lo contrario, tendrás que relacionar los eventos de estado con el contexto después de que ocurran.
Envíos a gran escala: cómo respetar el límite de solicitudes
A 200 llamadas por minuto, un bucle básico para unos cuantos miles de destinatarios alcanzará rápidamente los errores 429. Un patrón sencillo de cola y limitación de velocidad lo resuelve sin complicar demasiado las cosas:
async function sendBatch(recipients, templateName, buildMapping) { const RATE_LIMIT = 200; // calls per minute const DELAY_MS = Math.ceil(60000 / RATE_LIMIT); for (const recipient of recipients) { try { await sendTemplateByPhone(recipient.phone, templateName, buildMapping(recipient)); } catch (err) { console.error(`Send failed for ${recipient.phone}:`, err.message); // classification already handled inside handleWhatsAppResponse } await new Promise((resolve) => setTimeout(resolve, DELAY_MS)); }}Para envíos que superen unos cuantos miles, vale la pena configurar una cola de tareas adecuada (BullMQ, SQS o similar) con reintentos progresivos, en lugar de usar un bucle dentro del proceso: puede sobrevivir al reinicio del proceso a mitad de un lote, algo que un simple bucle for no puede hacer.
Por qué crear esto con Chakra
Algunos aspectos que debes tener en cuenta al evaluar dónde crear esta integración:
- Documentación completa : fragmentos de código, ejemplos de solicitudes y respuestas, y tutoriales sobre webhooks para cada endpoint, para que no tengas que deducir cómo funciona todo mediante ensayo y error
- Infraestructura empresarial : 99.9% de disponibilidad, con lógica de reintentos y gestión de límites de solicitudes integradas en Chakra. Esto es importante cuando envías a gran escala y no puedes arriesgarte a perder mensajes sin darte cuenta
- Una capa de integración sencilla y transparente : el endpoint con formato Meta es realmente solo un pasamanos, así que todo lo que indica la documentación de Meta sigue siendo aplicable; no tienes que aprender una capa de abstracción propietaria para cada caso de uso
- Precios transparentes, sin recargos : pagas las tarifas estándar de Meta por los mensajes de plantilla, sin costos ocultos añadidos
- Webhooks en tiempo real : los eventos entrantes y los cambios en el estado de entrega están disponibles en cuanto ocurren, para que tu sistema pueda reaccionar ante los fallos (como los casos 131049/131026 anteriores) sin tener que consultar periódicamente
Si estás creando un sistema de notificaciones que debe escalar más allá de unos pocos envíos de prueba, vale la pena crearlo directamente con la API de Chakra, en lugar de conectar por tu cuenta llamadas de nivel inferior a Meta Cloud API. La lógica de reintentos, la gestión de límites de solicitudes y el envío vinculado al CRM ya están incluidos.
Obtén tu clave de API y empieza a enviar : regístrate, conecta tu WABA y podrás enviar tu primer mensaje de plantilla en minutos.