Enviar mensajes de WhatsApp con Node.js

Código mínimo funcional contra la Cloud API, sin dependencias externas.

Necesitas Node.js 18 o superior (para fetch nativo), tu Phone Number ID y un token de acceso.

1. Enviar un mensaje de texto libre

Solo funciona si el usuario te escribió en las últimas 24 horas (ventana de servicio abierta):

const PHONE_NUMBER_ID = process.env.WHATSAPP_PHONE_NUMBER_ID;
const ACCESS_TOKEN = process.env.WHATSAPP_ACCESS_TOKEN;

async function enviarTexto(numeroDestino, mensaje) {
  const respuesta = await fetch(
    `https://graph.facebook.com/v26.0/${PHONE_NUMBER_ID}/messages`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${ACCESS_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        messaging_product: 'whatsapp',
        to: numeroDestino, // formato E.164 sin "+", ej. "34600000000"
        type: 'text',
        text: { body: mensaje }
      })
    }
  );

  const data = await respuesta.json();
  if (!respuesta.ok) throw new Error(JSON.stringify(data));
  return data;
}

enviarTexto('34600000000', 'Hola, ¿en qué puedo ayudarte?')
  .then(console.log)
  .catch(console.error);

2. Enviar una plantilla (fuera de la ventana de 24h)

Si no hay conversación abierta, tienes que usar una plantilla aprobada por Meta, referenciada por su nombre e idioma:

async function enviarPlantilla(numeroDestino, nombrePlantilla, idioma, parametros = []) {
  const respuesta = await fetch(
    `https://graph.facebook.com/v26.0/${PHONE_NUMBER_ID}/messages`,
    {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${ACCESS_TOKEN}`,
        'Content-Type': 'application/json'
      },
      body: JSON.stringify({
        messaging_product: 'whatsapp',
        to: numeroDestino,
        type: 'template',
        template: {
          name: nombrePlantilla,
          language: { code: idioma }, // ej. "es" o "es_ES"
          components: parametros.length ? [{
            type: 'body',
            parameters: parametros.map(texto => ({ type: 'text', text: texto }))
          }] : []
        }
      })
    }
  );

  const data = await respuesta.json();
  if (!respuesta.ok) throw new Error(JSON.stringify(data));
  return data;
}

// Ejemplo: plantilla "confirmacion_pedido" con 1 variable {{1}}
enviarPlantilla('34600000000', 'confirmacion_pedido', 'es', ['#48213']);

Comprueba antes qué categoría tendrá tu plantilla (afecta al coste) con el comprobador de categoría.

3. Recibir mensajes y estados (webhooks)

El envío es solo la mitad: para recibir mensajes entrantes y confirmaciones de entrega necesitas un endpoint propio que reciba los webhooks de Meta. Cómo montarlo y qué significa cada campo, en la guía de webhooks, y valida cualquier payload real con el validador de webhooks.

¿O prefieres no mantener este código tú mismo?

¡Contáctanos por WhatsApp!