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.
| Herramienta | Comando | URL generada | Observaciones |
|---|---|---|---|
| ngrok | ngrok http 3000 | HTTPS, se muestra en la consola del agente | Requiere cuenta y authtoken; tiene inspector de tráfico y reenvío de solicitudes |
| cloudflared (Quick Tunnel) | cloudflared tunnel --url http://localhost:3000 | Subdominio temporal en trycloudflare.com | No 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:
- 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) yhub.verify_token(el valor que escribiste en el panel). Si el token coincide, responde200con el valor dehub.challengeen el cuerpo. Si no coincide, responde403u otro código distinto de 200. - 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. Responde200para 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
- Crea una cuenta en ngrok e instala el agente (en macOS,
brew install ngrok). - Conecta el agente a tu cuenta:
ngrok config add-authtoken TU_AUTHTOKEN
- Con el servidor corriendo en el puerto 3000, abre el túnel:
ngrok http 3000
- Copia la URL HTTPS de reenvío que aparece en la consola.
- En el panel de la app de Meta, ve a WhatsApp → Webhooks → Configuración (el nombre del menú puede variar), pega
https://TU-URL/webhooken Callback URL, escribe el mismoVERIFY_TOKENen Token de verificación y haz clic en Verificar y guardar. La terminal debería mostrar «Webhook verificado». - En la lista de campos, suscríbete al campo
messages. - 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 campomessagesfunciona con la app en modo desarrollo o publicada. - 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íntoma | Causa probable | Solución |
|---|---|---|
| «No se pudo validar la URL de devolución de llamada» en el panel | Token distinto, servidor caído, ruta incorrecta o respuesta en JSON en lugar del texto del challenge | Responde 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 POST | Campo messages sin suscribir, o la cuenta de WhatsApp no está vinculada a la app | Suscríbete al campo y revisa la app vinculada a la cuenta |
| Todas las firmas salen inválidas | HMAC 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 veces | Respuesta lenta o distinta de 200, que provoca reintentos | Responde 200 antes de procesar y deduplica por id |
| Dejó de funcionar al reiniciar el túnel | Cambió la URL pública | Actualiza 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.
