← Blog

·

Cómo probar el webhook de WhatsApp localmente con ngrok

Actualizado el 25/10/2026

Para probar el webhook de WhatsApp localmente, ejecuta tu servidor en tu máquina, expone el puerto con un túnel HTTPS (ngrok o cloudflared) y registra la URL pública como Callback URL en el panel de la app de Meta. El servidor debe responder al GET de verificación devolviendo el hub.challenge y, en los POST, validar el encabezado X-Hub-Signature-256 con la clave secreta de la app. Así ves los eventos reales llegar a tu terminal, sin deploy.

Esta guía incluye un servidor Node.js completo, los comandos de los dos túneles, el botón de prueba del panel de Meta y los errores más comunes en esta etapa.

Por qué necesitas un túnel

Meta solo entrega webhooks a una URL pública con HTTPS. La documentación de webhooks exige un certificado TLS válido y aclara que no se admiten certificados autofirmados. http://localhost:3000 no cumple ninguno de los dos requisitos.

Un túnel lo resuelve: el agente corre en tu máquina, abre una conexión saliente con el servicio y obtiene una URL pública con HTTPS que reenvía las solicitudes a tu puerto local. No hace falta abrir puertos en el router ni tener IP fija.

HerramientaComandoURL generadaObservaciones
ngrokngrok http 3000HTTPS, se muestra en la consola del agenteRequiere cuenta y authtoken; tiene inspector de tráfico y reenvío de solicitudes
cloudflared (Quick Tunnel)cloudflared tunnel --url http://localhost:3000Subdominio temporal en trycloudflare.comNo requiere cuenta; la dirección cambia en cada ejecución; solo para pruebas

Cómo se comunica Meta con tu endpoint

Son dos solicitudes distintas en la misma URL:

  1. GET de verificación. Cuando guardas o editas la Callback URL o el token de verificación en el panel, Meta envía un GET con hub.mode=subscribe, hub.challenge (texto aleatorio) y hub.verify_token (el valor que escribiste en el panel). Si el token coincide, responde 200 con el valor de hub.challenge en el cuerpo. Si no coincide, responde 403 u otro código distinto de 200.
  2. POST de eventos. Cada mensaje recibido o cambio de estado llega como JSON, con el encabezado X-Hub-Signature-256: sha256=<hash>. El hash es un HMAC-SHA256 del cuerpo, con la clave secreta de la app como llave. Responde 200 para confirmar.

Si el POST falla, Meta reintenta de inmediato y luego varias veces más, con frecuencia decreciente, durante hasta 7 días. Las entregas no confirmadas en ese plazo se descartan. Por eso, el mismo evento puede llegar más de una vez y el servidor debe deduplicar. Meta también avisa que no hay API para recuperar webhooks antiguos: guarda lo que recibas.

¿Quieres probar mientras lees? Crea tu cuenta en Vectalk y envía el primer mensaje por la API oficial. Crear cuenta gratis →

Servidor Node.js de prueba

El ejemplo usa Express. El detalle clave es guardar el cuerpo crudo de la solicitud para calcular la firma. Si calculas el HMAC sobre un JSON vuelto a serializar, los bytes pueden cambiar y la comparación falla.

mkdir webhook-local && cd webhook-local
npm init -y
npm install express
// server.js
import express from 'express';
import crypto from 'node:crypto';

const { VERIFY_TOKEN, APP_SECRET, PORT = 3000 } = process.env;
const app = express();

// Guarda el cuerpo crudo para validar la firma
app.use(express.json({
  verify: (req, _res, buf) => { req.rawBody = buf; }
}));

// 1) Verificación (GET)
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 === VERIFY_TOKEN) {
    console.log('Webhook verificado');
    return res.status(200).send(challenge);
  }
  return res.sendStatus(403);
});

// 2) Eventos (POST)
app.post('/webhook', (req, res) => {
  const header = req.get('x-hub-signature-256') || '';
  const expected = 'sha256=' + crypto
    .createHmac('sha256', APP_SECRET)
    .update(req.rawBody || '')
    .digest('hex');

  const valid = header.length === expected.length &&
    crypto.timingSafeEqual(Buffer.from(header), Buffer.from(expected));

  if (!valid) {
    console.warn('Firma inválida');
    return res.sendStatus(401);
  }

  res.sendStatus(200); // confirma rápido

  for (const entry of req.body.entry || []) {
    for (const change of entry.changes || []) {
      const v = change.value || {};
      for (const m of v.messages || []) console.log('mensaje', m.from, m.type, m.id);
      for (const s of v.statuses || []) console.log('estado', s.id, s.status);
    }
  }
});

app.listen(PORT, () => console.log(`Escuchando en http://localhost:${PORT}`));

Para ejecutarlo, agrega "type": "module" al package.json e inícialo con las variables:

VERIFY_TOKEN=un-texto-que-elijas APP_SECRET=clave_secreta_de_la_app node server.js

El VERIFY_TOKEN es cualquier texto que defines y repites en el panel. El APP_SECRET es la clave secreta de la app, en Configuración de la app → Básica del panel de desarrolladores. Nunca la subas al repositorio.

Paso a paso con ngrok

  1. Crea una cuenta en ngrok e instala el agente (en macOS, brew install ngrok).
  2. Conecta el agente a tu cuenta:
ngrok config add-authtoken TU_AUTHTOKEN
  1. Con el servidor corriendo en el puerto 3000, abre el túnel:
ngrok http 3000
  1. Copia la URL HTTPS de reenvío que aparece en la consola.
  2. En el panel de la app de Meta, ve a WhatsApp → Webhooks → Configuración (el nombre del menú puede variar), pega https://TU-URL/webhook en Callback URL, escribe el mismo VERIFY_TOKEN en Token de verificación y haz clic en Verificar y guardar. La terminal debería mostrar «Webhook verificado».
  3. En la lista de campos, suscríbete al campo messages.
  4. Haz clic en Probar junto a messages. Meta envía un payload de ejemplo que debería aparecer en tu terminal. Según la guía de webhook de prueba de Meta, la prueba del campo messages funciona con la app en modo desarrollo o publicada.
  5. Envía un mensaje real desde tu celular al número de prueba o de producción conectado a la app y mira cómo llega el evento.

El agente de ngrok tiene un inspector de tráfico, normalmente en http://localhost:4040, que muestra encabezados, cuerpo y respuesta de cada solicitud. Con el modo de captura completa activado en la cuenta, el Traffic Inspector permite reenviar una solicitud a tu servidor sin esperar a Meta, lo que acelera mucho la depuración del parser.

Paso a paso con cloudflared

Si no quieres crear una cuenta, el Quick Tunnel de Cloudflare sirve para pruebas rápidas:

cloudflared tunnel --url http://localhost:3000

El comando imprime una URL en trycloudflare.com. Úsala en el panel de Meta de la misma forma. Según la documentación de Cloudflare, el Quick Tunnel es para pruebas y desarrollo, no tiene garantía de disponibilidad, admite hasta 200 solicitudes simultáneas y genera una dirección nueva en cada ejecución. Como la URL cambia, debes volver a verificar en el panel cada vez que reinicies el túnel.

Errores comunes y cómo resolverlos

SíntomaCausa probableSolución
«No se pudo validar la URL de devolución de llamada» en el panelToken distinto, servidor caído, ruta incorrecta o respuesta en JSON en lugar del texto del challengeResponde solo el valor de hub.challenge, con estado 200, en la ruta exacta registrada
El GET de verificación funciona, pero no llega ningún POSTCampo messages sin suscribir, o la cuenta de WhatsApp no está vinculada a la appSuscríbete al campo y revisa la app vinculada a la cuenta
Todas las firmas salen inválidasHMAC calculado sobre JSON reserializado, clave equivocada o encabezado comparado sin el prefijo sha256=Usa el cuerpo crudo y la clave secreta de la app (no el token de acceso)
El mismo evento llega varias vecesRespuesta lenta o distinta de 200, que provoca reintentosResponde 200 antes de procesar y deduplica por id
Dejó de funcionar al reiniciar el túnelCambió la URL públicaActualiza la Callback URL y vuelve a verificar

Un cuidado más: el webhook se configura por app. Si apuntas la Callback URL de la app de producción a tu máquina, producción deja de recibir eventos. Usa una app de desarrollo separada para las pruebas locales.

De la prueba a producción

El túnel es una herramienta de desarrollo. En producción, el endpoint necesita dominio propio, certificado válido, una cola para procesar los eventos fuera de la solicitud y almacenamiento de los payloads. La guía de estados de mensaje muestra cómo manejar sent, delivered, read y failed. Cuando el webhook traiga errores, la tabla de errores de la API de WhatsApp ayuda a decidir qué hacer. Si la app todavía usa un token temporal, cámbialo por un token permanente antes de publicar.

Cómo funciona con Vectalk

En Vectalk, los eventos son los mismos de Meta, con el mismo formato entry[].changes[].value, así que el parser de arriba funciona sin cambios. Lo que cambia es el registro: en lugar del panel de Meta, registras la URL por API con instance_id, url (HTTPS) y name, y puedes tener varias URL por instancia, todas recibiendo el mismo evento.

curl -X POST https://apis.vectalk.com.br/api/webhooks \
  -H "Authorization: Bearer TU_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "instance_id": "TU_INSTANCIA",
    "url": "https://TU-URL-DEL-TUNEL/webhook",
    "name": "Dev local"
  }'

Esto permite registrar la URL del túnel como un webhook adicional, sin quitar el de producción, y desactivarlo después con active: false en el endpoint de actualizar webhook. Vectalk espera un 200 en menos de 20 segundos y reenvía el evento si no lo recibe, según la documentación de webhooks. Antes de exigir validación de firma en este flujo, revisa en la documentación y en el inspector del túnel qué encabezados llegan.

Crea tu cuenta en apps.vectalk.com.br/register.

Preguntas frecuentes

¿Puedo registrar http://localhost como webhook de WhatsApp?

No. Meta exige una URL pública con HTTPS y certificado válido. Para probar en tu máquina, usa un túnel como ngrok o cloudflared.

¿Qué debo responder en el GET de verificación?

Estado 200 con el valor de hub.challenge en el cuerpo, sin JSON y sin comillas, siempre que hub.verify_token sea igual al token que registraste.

¿Qué clave uso para validar X-Hub-Signature-256?

La clave secreta de la app (App Secret), no el token de acceso ni el token de verificación. El hash es un HMAC-SHA256 del cuerpo de la solicitud, comparado con el valor después de sha256=.

¿Por qué recibo el mismo webhook dos veces?

Porque Meta reintenta cuando no recibe un 200, durante hasta 7 días. Responde rápido y deduplica por el id del mensaje o del estado.

¿Puedo usar ngrok en producción?

Está pensado para exponer servicios locales durante el desarrollo. Para producción, aloja el endpoint en un servidor con dominio y certificado propios.


Fuentes oficiales consultadas el 25/10/2026: Crear un endpoint de webhook (WhatsApp), Webhook de prueba con echo bot, Webhooks de la Graph API: primeros pasos, ngrok: Share Localhost Quickstart, ngrok: Test Webhooks Locally, ngrok: Traffic Inspector, Cloudflare Quick Tunnels, Documentación de Vectalk: webhooks, Documentación de Vectalk: actualizar webhook.

Prueba hoy la API oficial de WhatsApp

Crea tu cuenta, genera el token y envía el primer mensaje con la guía de la documentación. Los mismos payloads y webhooks de la Cloud API de Meta.