Servidor webhook para la API oficial de WhatsApp Business (Meta Cloud API). Recibe mensajes de clientes, detecta palabras clave y responde automáticamente.
Cliente escribe en WhatsApp
↓
Meta recibe el mensaje
↓
Meta hace POST → /webhook (este servidor)
↓
verifySignature (HMAC-SHA256)
↓
messageHandler (detecta palabra clave)
↓
sendMessage → API de Meta → cliente recibe respuesta
CapiBot/
├── src/
│ ├── config.js # Variables de entorno centralizadas y validadas
│ ├── middleware/
│ │ ├── verifySignature.js # Verificación de firma Meta (HMAC-SHA256)
│ │ └── security.js # Helmet + Rate Limit + CORS
│ ├── handlers/
│ │ └── messageHandler.js # Lógica del bot y palabras clave
│ └── utils/
│ └── sendMessage.js # Funciones para enviar mensajes via API Meta
├── index.js # Servidor principal (Express)
├── .env # Variables locales (NO subir a GitHub)
├── .env.example # Plantilla de variables (sí va en GitHub)
├── .gitignore
└── package.json
Copia .env.example a .env y llena cada valor antes de arrancar.
| Variable | Descripción | Dónde encontrarla |
|---|---|---|
PORT |
Puerto del servidor (default: 3000) | Tú lo defines |
NODE_ENV |
development o production |
Tú lo defines |
VERIFY_TOKEN |
String inventado por ti para verificar el webhook | Tú lo defines — debe coincidir con lo que ingresas en Meta |
WHATSAPP_TOKEN |
Token permanente de acceso a la API | Meta Developers → Usuarios del sistema → Generar token |
PHONE_NUMBER_ID |
ID del número de WhatsApp registrado | Meta Developers → WhatsApp → Configuración de la API |
APP_SECRET |
Secreto de la app para verificar firmas | Meta Developers → Tu app → Configuración → Básica |
# Instalar dependencias
npm install
# Copiar plantilla de variables de entorno
cp .env.example .env
# Editar .env con tus valores reales
# (abre el archivo y llena cada campo)
# Arrancar en desarrollo (recarga automática)
npm run dev
# Arrancar en producción
npm startVerifica que el servidor está corriendo.
Respuesta: { "status": "ok", "env": "production" }
Meta lo usa una sola vez para verificar que el servidor es tuyo.
Compara el hub.verify_token con tu VERIFY_TOKEN del .env.
Parámetros (los envía Meta automáticamente):
hub.mode → "subscribe"
hub.verify_token → el token que configuraste en Meta
hub.challenge → string que debes devolver si el token es correcto
Recibe todos los mensajes de clientes en tiempo real. Protegido por verificación de firma HMAC-SHA256.
| El cliente escribe | El bot responde |
|---|---|
hola, buenas, buenos días |
Mensaje de bienvenida con menú |
catalogo, catálogo, productos |
Link al catálogo en Vercel |
cotizacion, cotización, precio, cuanto |
Link al formulario de cotización |
info, horario, envio, envío |
Información del negocio |
| cualquier otro texto | Menú de opciones |
Cada POST que Meta envía al webhook incluye el header X-Hub-Signature-256.
Es un HMAC-SHA256 del cuerpo del mensaje usando el APP_SECRET.
El servidor recalcula la firma y la compara. Si no coincide → 403 Forbidden.
Esto garantiza que solo Meta puede enviar mensajes al webhook.
Header recibido: X-Hub-Signature-256: sha256=abc123...
Firma calculada: sha256=HMAC(APP_SECRET, rawBody)
¿Coinciden? → procesa el mensaje
¿No coinciden? → 403
Agrega automáticamente 11 cabeceras HTTP de seguridad:
X-Content-Type-Options: nosniffX-Frame-Options: DENYStrict-Transport-Security(HSTS)Content-Security-Policy- entre otras
Máximo 30 peticiones por minuto por IP. Previene abuso y ataques de denegación de servicio básicos.
Meta hace peticiones server-to-server, no desde un navegador. CORS deshabilitado completamente en el webhook — cualquier petición desde un browser es rechazada.
Todos los tokens y secretos viven en .env (local) o en las variables de Railway (producción).
Nunca están escritos en el código.
El manejador de errores global devuelve solo "Error interno" cuando NODE_ENV=production.
Los detalles del error solo se muestran en desarrollo.
Railway provee HTTPS automáticamente. Todo el tráfico está encriptado en tránsito.
Ve a railway.app y crea una cuenta con GitHub.
- Click "New Project"
- Selecciona "Deploy from GitHub repo"
- Conecta el repositorio de CapiBot
En el panel de Railway → tu servicio → "Variables":
NODE_ENV = production
PORT = 3000
VERIFY_TOKEN = (el que definiste)
WHATSAPP_TOKEN = (token permanente de Meta)
PHONE_NUMBER_ID = (ID del número en Meta)
APP_SECRET = (secreto de la app en Meta)
Railway genera una URL HTTPS automáticamente:
https://capibot-production.up.railway.app
En Meta Developers → tu app → WhatsApp → Configuración → Webhooks:
URL de callback: https://capibot-production.up.railway.app/webhook
Token de verificación: (el mismo VERIFY_TOKEN que pusiste en Railway)
Campos suscritos: messages ✅
# 1. Verificar que el servidor arranca
npm run dev
# Debe mostrar: 🚀 CapiBot corriendo en puerto 3000 [development]
# 2. Health check
curl http://localhost:3000/health
# Respuesta: {"status":"ok","env":"development"}
# 3. Simular verificación de Meta
curl "http://localhost:3000/webhook?hub.mode=subscribe&hub.verify_token=TU_VERIFY_TOKEN&hub.challenge=test123"
# Respuesta: test123
# 4. Simular mensaje entrante (sin firma válida — debe responder 403)
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-d '{"entry":[{"changes":[{"value":{"messages":[{"from":"521234567890","type":"text","text":{"body":"hola"}}]}}]}]}'
# Respuesta: 403 (correcto — falta la firma Meta)1. Cliente escribe "cotización" al número de WhatsApp de Capibara's Crochet
2. Meta recibe el mensaje en sus servidores
3. Meta hace POST a https://capibot-production.up.railway.app/webhook
4. El servidor verifica la firma HMAC-SHA256 → válida ✅
5. messageHandler detecta la palabra "cotización"
6. sendMessage llama a la API de Meta con el link del formulario
7. Meta entrega el mensaje al cliente
8. Cliente recibe el link y llena el formulario de cotización
| Paquete | Versión | Uso |
|---|---|---|
express |
^5.2.1 | Servidor HTTP |
helmet |
^8.x | Cabeceras de seguridad |
express-rate-limit |
^8.x | Límite de peticiones |
cors |
^2.x | Control de origen |
dotenv |
^17.x | Variables de entorno en local |
crypto |
built-in Node.js | Verificación de firma HMAC-SHA256 |