Saltar al contenido
Chatbots de WhatsApp desde cero
Temario Ir al ejercicio →

Seguridad y privacidad

  • ⏱ 60 minutos
  • 📶 Nivel: avanzado
  • 🧩 Incluye ejercicio

💡 🎯 Al terminar esta lección podrás…

  • Guardar secretos y tokens fuera del repositorio y del chat, usando .dev.vars y secretos de Cloudflare.
  • Explicar cómo funciona la firma X-Hub-Signature-256 (HMAC) y verificarla con código.
  • Aplicar mínimo privilegio, validación de entradas, limitación de tasa e idempotencia.
  • Decidir qué datos guardar, por cuánto tiempo y cómo borrarlos a petición del cliente.
  • Redactar una política de privacidad básica y revisar el bot con una lista de comprobación de seguridad.

1.Por qué un bot de negocio es un blanco

Tu bot es un servidor público en internet: cualquiera que conozca la URL del webhook puede enviarle peticiones. Además guarda teléfonos, direcciones y pedidos de personas reales, y tiene un token que permite enviar mensajes en nombre del negocio. Si algo sale mal, los daños son concretos: mensajes falsos enviados con tu número, datos filtrados, una factura de Meta inflada o una cuenta suspendida.

La buena noticia es que la mayor parte de la seguridad de un bot pequeño se resume en unos pocos hábitos: no filtrar secretos, comprobar quién te escribe, no confiar en lo que llega, guardar poco y borrar a tiempo. Esta lección los recorre uno por uno.

⚠️ Importante · Esto no es asesoría legal

Hablamos de buenas prácticas técnicas y de principios de protección de datos. Las leyes cambian y dependen de cada país. Si vas a manejar datos de clientes de un negocio, consulta a un abogado local.

2.Secretos y tokens: nunca en el repo ni en el chat

Un secreto es cualquier valor que da poder: el token de acceso de WhatsApp, el secreto de la app de Meta, el token de verificación del webhook, las claves de otros servicios. Reglas simples:

  1. Nunca en el código. Ni «temporalmente», ni «solo para probar».
  2. Nunca en el repositorio. Un secreto subido a GitHub debe considerarse comprometido aunque borres el commit, porque queda en el historial y existen bots que rastrean repositorios en busca de claves.
  3. Nunca en un chat, correo o captura de pantalla (incluido pedirle ayuda a un compañero o a una IA con el token a la vista).
  4. Rotación inmediata si se filtra: genera uno nuevo y revoca el anterior.

En local: .dev.vars

Cloudflare lee los secretos de desarrollo desde un archivo .dev.vars junto a wrangler.toml. Ese archivo debe estar en .gitignore:

.dev.vars
WHATSAPP_TOKEN=pon-aqui-tu-token
APP_SECRET=pon-aqui-el-secreto-de-la-app
VERIFY_TOKEN=una-frase-larga-que-inventes
.gitignore
node_modules/
.dev.vars
.env

Comprueba que git lo ignora antes del primer commit:

Terminal
git check-ignore -v .dev.vars

En producción: secretos de Cloudflare

En el servidor desplegado se usan secretos de Worker, que se guardan cifrados y no aparecen en wrangler.toml:

Terminal
npx wrangler secret put WHATSAPP_TOKEN
npx wrangler secret put APP_SECRET

Wrangler te pedirá el valor por la terminal (no queda en tu historial de comandos como argumento). En el código, el secreto llega en el objeto env: env.WHATSAPP_TOKEN.

💡 Un secreto por entorno

Usa un token distinto para pruebas y para producción. Si se filtra el de pruebas, el negocio real no se toca.

3.Tokens de acceso: temporales, de usuario del sistema y mínimo privilegio

En la lección 12 viste que Meta entrega un token temporal para practicar: caduca pronto y sirve solo para pruebas. Para producción se crea un usuario del sistema en la configuración del negocio y se le genera un token con los permisos justos. La documentación oficial de la plataforma describe estos pasos en la guía de inicio.

Aplica el principio de mínimo privilegio: da a cada pieza solo lo que necesita para su trabajo.

Guarda la fecha de creación de cada token y revísalos cada cierto tiempo. Si trabajas para un cliente, la cuenta de Meta debe estar a nombre del cliente, y tú entras con un rol limitado (lección 26).

Verificación en dos pasos en la cuenta de Meta

Activa la autenticación en dos pasos (2FA) en tu cuenta personal de Facebook y en el portafolio comercial. Quien controla esa cuenta controla el número, los clientes y la facturación. Meta puede exigirla para administrar el portafolio o para acceder a ciertas funciones.

⚠️ Importante · Verifica este dato

Varios tutoriales dicen que la 2FA es obligatoria para acceder a la API, pero no pude confirmarlo en una página oficial de Meta. Los requisitos de seguridad del portafolio cambian: revisa lo que te pida tu cuenta en el Centro de ayuda para empresas de Meta y la documentación oficial de WhatsApp. Actívala aunque no te la pidan.

4.Verificar la firma: X-Hub-Signature-256

Cualquiera puede enviar un POST a tu webhook diciendo «soy Meta». Para distinguir lo auténtico de lo falso, Meta firma cada notificación y pone la firma en el encabezado X-Hub-Signature-256, con la forma sha256=<hex>.

La firma es un HMAC-SHA256: una huella calculada con el cuerpo exacto de la petición y el secreto de la app, que solo conocen Meta y tú. El proceso es:

Metafirma el cuerpo con el secreto
→POST + cabecera de firma
Tu webhookrecalcula la firma
→¿coinciden?
Procesar o rechazar200 / 401
  1. Recibes el cuerpo sin modificar (el texto crudo, antes de convertirlo a objeto).
  2. Calculas el HMAC-SHA256 de ese texto usando tu secreto de app.
  3. Lo comparas con lo que viene después de sha256=.
  4. Si coincide, el mensaje es auténtico y no fue alterado. Si no, respondes 401 y no haces nada más.

La documentación de Meta lo describe en Validating Payloads: genera una firma SHA256 con el payload y el secreto de la app y compárala con la del encabezado.

Ejemplo propio con el módulo crypto de Node (el código real del proyecto vive en codigo/src/whatsapp.js; aquí lo escribimos a mano para entender la idea). El secreto es de ejemplo:

firma.js
const crypto = require("node:crypto");

function firmar(cuerpo, secreto) {
  return "sha256=" + crypto.createHmac("sha256", secreto).update(cuerpo).digest("hex");
}

function firmaValida(cuerpo, cabecera, secreto) {
  if (!cabecera) return false;
  const esperada = Buffer.from(firmar(cuerpo, secreto));
  const recibida = Buffer.from(cabecera);
  return esperada.length === recibida.length && crypto.timingSafeEqual(esperada, recibida);
}

const secreto = "secreto-de-ejemplo-no-real";
const cuerpo = '{"entry":[{"id":"1"}]}';
const cab = firmar(cuerpo, secreto);
console.log(cab);
console.log("original:", firmaValida(cuerpo, cab, secreto));
console.log("alterado:", firmaValida('{"entry":[{"id":"2"}]}', cab, secreto));
console.log("sin cabecera:", firmaValida(cuerpo, null, secreto));
Salida esperada
sha256=35b1991096b0c6652e370ff67f1fb20c9595f6960f74ab42789869acca482b3c
original: true
alterado: false
sin cabecera: false

Detalles que importan:

5.Validar entradas y limpiar texto

Aunque la firma sea válida, el texto del cliente es entrada no confiable: puede ser larguísimo, tener caracteres de control o intentar engañar a tu código. Reglas:

seguridad.js
function limpiarEntrada(texto, max = 500) {
  if (typeof texto !== "string") return "";
  return texto
    .replace(/[​-‍]/g, "")       // ancho cero
    .replace(/[\u0000-\u001F\u007F]/g, " ")      // caracteres de control
    .replace(/\s+/g, " ").trim().slice(0, max);
}

console.log(JSON.stringify(limpiarEntrada("  hola\n\n\tquiero\u0000   pan​  ")));
Salida esperada
"hola quiero pan"

Y para registros y paneles, enmascara el teléfono: en los logs casi nunca necesitas el número completo.

seguridad.js
function enmascararTelefono(tel) {
  const d = String(tel ?? "").replace(/\D/g, "");
  if (d.length <= 5) return "*".repeat(d.length);
  return d.slice(0, 2) + "*".repeat(d.length - 5) + d.slice(-3);
}

console.log(enmascararTelefono("51999000111"));
Salida esperada
51******111

6.Limitar el abuso: tasa e idempotencia

Dos problemas distintos con soluciones simples.

Limitar la tasa (rate limiting)

Un cliente (o un script) que te escribe 200 veces por minuto puede agotar tus límites gratuitos, generar costos y hundir la conversación. Una ventana deslizante guarda las horas de las últimas peticiones de cada cliente y rechaza cuando se supera el máximo:

seguridad.js
function limitarTasa(registro, clave, ahora, max, ventanaMs) {
  const vivos = (registro[clave] ?? []).filter(t => ahora - t < ventanaMs);
  if (vivos.length >= max) { registro[clave] = vivos; return false; }
  vivos.push(ahora);
  registro[clave] = vivos;
  return true;
}

const reg = {};
const r = [0, 1000, 2000, 3000, 61000].map(t => limitarTasa(reg, "51999000111", t, 3, 60000));
console.log(r);
Salida esperada
[ true, true, true, false, true ]

Con un máximo de 3 mensajes por minuto, el cuarto (en el segundo 3) se rechaza; pasado el minuto, vuelve a funcionar. Para el cliente que excede el límite, lo más amable es ignorar en silencio o responder una sola vez («Estás escribiendo muy rápido, espera un momento»).

📌 Nota · Dónde guardar el contador

En un Worker, cada petición puede ejecutarse en una instancia distinta, así que un Map en memoria no es fiable. Para un bot pequeño, guarda el contador en KV con caducidad corta, sabiendo que KV no es instantáneo entre regiones y que sus escrituras tienen cuota; es una defensa «suficiente», no perfecta. Para más rigor existen herramientas de Cloudflare diseñadas para esto.

Idempotencia

Meta puede reenviar una notificación si tu servidor tarda o falla: según la documentación, reintenta con frecuencia decreciente hasta 7 días y esos reintentos pueden producir notificaciones duplicadas. Si tu bot no se protege, un mismo mensaje «quiero 2 gaseosas» podría procesarse dos veces y crear un pedido doble.

Una operación es idempotente si repetirla no cambia el resultado. La técnica: guarda el identificador del mensaje (wamid...) que viene en el JSON y descarta los ya vistos.

seguridad.js
const vistos = new Set();
function procesarUnaVez(id, fn) {
  if (vistos.has(id)) return "duplicado ignorado";
  vistos.add(id);
  return fn();
}
console.log(procesarUnaVez("wamid.EJEMPLO1", () => "procesado"));
console.log(procesarUnaVez("wamid.EJEMPLO1", () => "procesado"));
Salida esperada
procesado
duplicado ignorado

En producción, el Set se reemplaza por KV con una caducidad de uno o dos días. Responde siempre 200 rápido a Meta y procesa sin demorar, para evitar reintentos innecesarios.

7.Qué datos guardar y por cuánto tiempo

El principio rector es la minimización: guarda solo lo necesario para el propósito, y solo mientras haga falta.

Dato ¿Hace falta? Retención sugerida (decide con el negocio)
Teléfono Sí, es el identificador de la conversación Mientras el cliente esté activo
Nombre Opcional, si lo da el cliente Igual que el teléfono
Dirección de entrega Solo si hay delivery Hasta entregar; después, borrar o pedirla de nuevo
Pedidos Sí (historial y reclamos) Según necesidad contable del negocio
Texto de las conversaciones Solo el estado necesario para continuar Corto: horas o pocos días
Registro de opt-in/baja Sí, como prueba del consentimiento Mientras dure la relación y el plazo legal aplicable
Logs técnicos Para depurar Pocos días, con teléfonos enmascarados

Lo que no debes guardar ni pedir por WhatsApp: números completos de tarjeta, claves, documentos de identidad, datos de salud. La política de Meta pide expresamente no compartir datos sensibles por el canal.

Las cifras de retención de la tabla son sugerencias, no obligaciones legales: algunos datos (como comprobantes) pueden tener plazos de conservación por ley contable o tributaria. Cada negocio debe fijarlos con su asesor.

Una función de retención es pura y fácil de probar: dice si un registro debe borrarse, ya sea por antigüedad o porque el cliente pidió el borrado:

seguridad.js
function debeBorrarse(registro, ahora, diasRetencion) {
  if (registro.solicitudBorrado) return true;
  return ahora - registro.creado >= diasRetencion * 24 * 60 * 60 * 1000;
}

const DIA = 86400000;
console.log(
  debeBorrarse({ creado: 0 }, 91 * DIA, 90),
  debeBorrarse({ creado: 0 }, 10 * DIA, 90),
  debeBorrarse({ creado: 0, solicitudBorrado: true }, DIA, 90)
);
Salida esperada
true false true

Una tarea programada (la misma técnica de los recordatorios) puede ejecutar la limpieza cada noche.

8.Consentimiento, política de privacidad y borrado a petición

Quien te da su teléfono y su dirección tiene derechos sobre esos datos. En Perú, por ejemplo, la Ley 29733, Ley de Protección de Datos Personales y su reglamento regulan el consentimiento, los derechos del titular (acceder, rectificar, cancelar y oponerse) y las obligaciones de quien trata datos; la autoridad es la Autoridad Nacional de Protección de Datos Personales. Cada país tiene su propia norma (por ejemplo, México, Colombia, Chile o Argentina tienen leyes distintas) y el reglamento peruano ha tenido modificaciones recientes, así que verifica la versión vigente.

⚠️ Importante · Verifica este dato

No des por hecho que una ley antigua sigue igual: busca la versión vigente en el portal oficial del Estado o consúltala con un abogado antes de redactar documentos legales para un cliente.

Una política de privacidad para el bot del minimarket debe responder, en lenguaje claro:

  1. Quién es el responsable (nombre y contacto del negocio).
  2. Qué datos se recogen (teléfono, nombre, dirección, pedidos).
  3. Para qué se usan (atender pedidos, avisos de entrega y, solo si aceptó, ofertas).
  4. Con quién se comparten (Meta como proveedor del canal, el proveedor de alojamiento, repartidores si aplica).
  5. Cuánto tiempo se conservan.
  6. Cómo ejercer derechos: pedir copia, corregir, borrar o darse de baja, y a qué contacto.
  7. Cómo se pide el consentimiento y cómo se retira.

Publica la política en una página web (aunque sea una sola página) y enlázala en el cartel del QR y en el primer mensaje del bot.

Mensaje de ejemplo para la primera interacción:

Texto
Hola, soy el asistente de Minimarket La Esquina. Uso tu número para atender
tu pedido. Más información en nuestra política de privacidad:
https://ejemplo.com/privacidad. Para borrar tus datos, escribe BORRAR.

Borrar datos a petición

Si el cliente escribe BORRAR (o lo pide por otra vía), el bot debe:

  1. Confirmar la identidad: el número que escribe es el titular de los datos.
  2. Borrar o anonimizar pedidos no obligatorios de conservar, sesiones y registros.
  3. Mantener lo que la ley obliga (por ejemplo, comprobantes) y explicarlo.
  4. Confirmar con un mensaje breve y registrar que se atendió la solicitud, sin guardar de nuevo los datos que acabas de borrar.

9.Lista de seguridad antes de entregar el bot

10.Errores frecuentes

11.Apuntes para llevar

12.Glosario

Término Significado
HMAC Huella criptográfica calculada con un mensaje y un secreto compartido; prueba autenticidad.
Secreto de la app Valor privado de tu app de Meta usado para firmar y verificar notificaciones.
Usuario del sistema Identidad técnica del portafolio de Meta para generar tokens de uso en servidores.
Mínimo privilegio Dar a cada persona o pieza solo los permisos que necesita.
Limitación de tasa Restringir cuántas peticiones puede hacer un cliente en un período.
Idempotencia Propiedad por la cual repetir una operación no cambia el resultado.
Minimización Guardar solo los datos estrictamente necesarios.
Retención Tiempo que se conservan los datos antes de borrarlos.

🧠 Comprueba lo aprendido

Elige una respuesta en cada pregunta. Verás al instante si acertaste y por qué.

¿Dónde se guardan los secretos de un Worker en producción?

¿Qué es la firma X-Hub-Signature-256?

¿Por qué hay que calcular la firma sobre el cuerpo crudo y no sobre el JSON reparseado?

Meta reenvía una notificación y tu bot crea dos pedidos. ¿Qué técnica lo evita?

¿Qué principio guía qué datos del cliente conviene guardar?

📚 Para profundizar (fuentes oficiales)

Este material lo hace un estudiante con ayuda de IA: ante cualquier duda, manda la documentación oficial.

Tu progreso se guarda en este navegador y se refleja en el índice del curso.

💡 🚀 ¡Ahora te toca!

La práctica fija lo aprendido. Pasa al ejercicio de esta lección.

Ir al ejercicio