Plazbot
whatsapp apidevelopersnodejswebhookstutorial

WhatsApp Cloud API en Español: Envía Mensajes y Recibe Webhooks con Node.js (2026)

18 de octubre, 2026 · 4 min de lectura · Kristian Garcia

Los tutoriales de WhatsApp para desarrolladores en español suelen estar desactualizados o usan un sandbox de un proveedor. En esta guía usamos directamente la WhatsApp Cloud API de Meta con Node.js: enviar mensajes, enviar plantillas, recibir mensajes por webhook y verificar la firma.

Al final te mostramos lo mismo con el SDK de Plazbot, para cuando quieras ahorrarte la parte de infraestructura.

Requisitos

  1. Una cuenta de desarrollador de Meta en developers.facebook.com.
  2. Un portafolio de negocio de Meta (antes Business Manager).
  3. Una app de Meta de tipo negocio con el producto WhatsApp agregado.
  4. Un número de teléfono registrado en WhatsApp Business. Meta te da un número de prueba para empezar.
  5. Node.js 18 o superior, que ya incluye fetch.

De la sección WhatsApp → API Setup de tu app necesitas:

  • WA_PHONE_NUMBER_ID: el ID del número que envía.
  • WA_TOKEN: un token de acceso. El temporal sirve para probar; en producción usa un token de usuario del sistema.
  • META_APP_SECRET: el secreto de la app, para verificar los webhooks.

Guárdalos como variables de entorno, nunca en el código.

1. Enviar un mensaje de texto

javascript
// whatsapp.js
// Usa la versión vigente de la Graph API (revisa la documentación de Meta)
const GRAPH_URL = "https://graph.facebook.com/v24.0"
const { WA_TOKEN, WA_PHONE_NUMBER_ID } = process.env

async function callWhatsApp(payload) {
  const res = await fetch(`${GRAPH_URL}/${WA_PHONE_NUMBER_ID}/messages`, {
    method: "POST",
    headers: {
      Authorization: `Bearer ${WA_TOKEN}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ messaging_product: "whatsapp", ...payload }),
  })
  if (!res.ok) throw new Error(`WhatsApp ${res.status}: ${await res.text()}`)
  return res.json() // { messages: [{ id: "wamid.…" }] }
}

export function sendText(to, body) {
  return callWhatsApp({ to, type: "text", text: { body } })
}

to es el número con código de país y sin +, por ejemplo 51912345678 para un celular de Perú.

Importante: un mensaje de texto libre solo se entrega si el cliente te escribió en las últimas 24 horas. Para iniciar una conversación necesitas una plantilla.

2. Enviar una plantilla

Las plantillas se crean y aprueban en el administrador de WhatsApp de Meta. Para enviar una con variables:

javascript
export function sendTemplate(to, name, variables) {
  return callWhatsApp({
    to,
    type: "template",
    template: {
      name, // por ejemplo "recordatorio_cita"
      language: { code: "es" },
      components: [
        {
          type: "body",
          parameters: variables.map((text) => ({ type: "text", text })),
        },
      ],
    },
  })
}

// await sendTemplate("51912345678", "recordatorio_cita", ["Ana", "jueves 10:30"])

Cada plantilla tiene una categoría (marketing, utilidad o autenticación) y un precio por país. Te explicamos las tarifas en precios de WhatsApp Business API 2026 por país.

3. Recibir mensajes con un webhook

Meta envía los mensajes entrantes y los estados de entrega (enviado, entregado, leído) a una URL tuya. Este ejemplo usa Express:

javascript
// server.js
import express from "express"
import crypto from "node:crypto"
import { sendText } from "./whatsapp.js"

const app = express()

// Guardamos el cuerpo original para verificar la firma
app.use(express.json({ verify: (req, _res, buf) => { req.rawBody = buf } }))

// Verificación inicial: Meta llama con un GET al registrar el webhook
app.get("/webhook", (req, res) => {
  const mode = req.query["hub.mode"]
  const token = req.query["hub.verify_token"]
  const challenge = req.query["hub.challenge"]
  if (mode === "subscribe" && token === process.env.WA_VERIFY_TOKEN) {
    return res.status(200).send(challenge)
  }
  res.sendStatus(403)
})

function hasValidSignature(req) {
  const received = req.get("x-hub-signature-256") ?? ""
  const expected =
    "sha256=" +
    crypto.createHmac("sha256", process.env.META_APP_SECRET).update(req.rawBody).digest("hex")
  return (
    received.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(received), Buffer.from(expected))
  )
}

app.post("/webhook", async (req, res) => {
  if (!hasValidSignature(req)) return res.sendStatus(401)
  res.sendStatus(200) // responde rápido y procesa después

  for (const entry of req.body.entry ?? []) {
    for (const change of entry.changes ?? []) {
      const value = change.value

      for (const msg of value.messages ?? []) {
        if (msg.type === "text") {
          await sendText(msg.from, `Recibimos tu mensaje: ${msg.text.body}`)
        }
      }

      for (const status of value.statuses ?? []) {
        console.log("Estado", status.id, status.status) // sent, delivered, read, failed
      }
    }
  }
})

app.listen(3000, () => console.log("Webhook en http://localhost:3000/webhook"))

Luego, en WhatsApp → Configuración de tu app:

  1. Pon la URL pública de tu webhook (en desarrollo puedes usar un túnel).
  2. Pon el mismo WA_VERIFY_TOKEN que usas en tu servidor.
  3. Suscríbete al campo messages.

4. Cosas que conviene saber en producción

  • Responde el webhook rápido. Devuelve 200 de inmediato y procesa en segundo plano; si tardas, Meta reintenta y puedes recibir duplicados.
  • Evita duplicados guardando el id de cada mensaje procesado.
  • Un webhook por app. Meta registra una sola URL de webhook por app de WhatsApp. Si dos sistemas comparten la misma app, uno deja de recibir eventos.
  • Usuarios con nombre de usuario. En 2026 Meta empezó a habilitar nombres de usuario en WhatsApp y un identificador de usuario por negocio (BSUID). Cuando un usuario usa nombre de usuario, el teléfono puede no venir en el webhook: guarda también ese identificador y revisa la documentación de Meta sobre usernames.
  • Mensajes seguidos. Los clientes escriben en varios mensajes cortos. Si respondes con IA, agrúpalos unos segundos antes de contestar.
  • Costos. Desde octubre de 2026 cada respuesta se cobra después de las primeras 1,000 del mes por número; responde en pocos mensajes.

Lo mismo con Plazbot

Si prefieres no administrar la app de Meta, los tokens ni los webhooks, Plazbot WhatsApp for Developers te da la API oficial con un SDK, un CLI y webhooks listos. Plazbot es Meta Business Partner.

bash
npm i -g plazbot-cli
plazbot login
plazbot whatsapp setup   # conecta tu número, prueba un mensaje y registra tu webhook
ts
import { Plazbot } from "plazbot"

const bot = new Plazbot({
  apiKey:      process.env.PLAZBOT_KEY!,
  workspaceId: process.env.PLAZBOT_WORKSPACE_ID!,
  zone:        "LA", // "LA" | "EU"
})

await bot.message.onWhatsappMessage({ to: "51912345678", message: "Hola 👋" })

await bot.message.onConversation({
  to:       "51912345678",
  template: "recordatorio_cita",
  variablesBody: [
    { variable: "{{1}}", value: "Ana" },
    { variable: "{{2}}", value: "jueves 10:30" },
  ],
})

Además:

  • Webhooks firmados con HMAC-SHA256, con reintentos automáticos (1 minuto, 5 minutos, 30 minutos y 2 horas) e historial de entregas.
  • Agentes de IA que puedes asignar a tu número para que respondan solos.
  • Bandeja para tu equipo cuando una conversación necesita a una persona.
  • Link para que tus clientes conecten su número, si eres una agencia o un SaaS.

Comparamos este enfoque con Twilio en Twilio para WhatsApp: precio real y la alternativa para developers.

Preguntas frecuentes

¿Qué es la WhatsApp Cloud API?

Es la API oficial de Meta para enviar y recibir mensajes de WhatsApp desde tu propio software, alojada por Meta. Envías mensajes con llamadas HTTP a la Graph API y recibes los mensajes de tus clientes por webhook.

¿La WhatsApp Cloud API es gratis?

El acceso a la API no tiene costo, pero Meta cobra por mensaje entregado según el país y el tipo de mensaje. Desde el 1 de octubre de 2026 también cobra las respuestas de servicio, después de las primeras 1,000 al mes por número.

¿Puedo enviar un mensaje de texto a cualquier número?

Solo si ese cliente te escribió en las últimas 24 horas. Fuera de esa ventana, tienes que usar una plantilla aprobada por Meta.

¿Cómo verifico que un webhook viene de Meta?

Meta firma cada webhook con el encabezado X-Hub-Signature-256, un HMAC-SHA256 del cuerpo de la petición usando el secreto de tu app. Calcula el mismo HMAC con el cuerpo original y compáralo antes de procesar el mensaje.

Artículos relacionados