# El secreto del webhook viaja en la URL y tu registro de acceso lo guarda entero

> El token de verificación de un webhook de WhatsApp llega en la cadena de consulta, y el registro de acceso escribe la línea entera. Qué hacer con un secreto quemado.

- Publicado: 26 de septiembre de 2026
- Autor: Equipo de IntegraLaIA
- Categoría: Ingeniería
- Temas: WhatsApp, Seguridad, Agentes, Producción
- Original: https://integralaia.com/blog/secreto-del-webhook-en-la-url-log-de-acceso
- Cómo se hizo: Borrador asistido por IA (Claude); revisado, editado y verificado por Equipo de IntegraLaIA el 26 de septiembre de 2026. Ilustración generada con IA.

---

Dar de alta un webhook de WhatsApp empieza con un intercambio que casi nadie mira dos veces. Meta llama a la URL que le diste con tres parámetros en la cadena de consulta: `hub.mode`, `hub.challenge` y `hub.verify_token`. El último es el secreto compartido que tú mismo escribiste en el panel de la aplicación. Tu servicio lo compara, devuelve el desafío, el webhook queda suscrito.

El problema llega después de responder. El formato de registro `combined`, el que nginx usa cuando no se especifica otro, escribe la línea de petición completa, con su cadena de consulta. Cada servidor y cada balanceador que atendió la llamada dejó tu secreto en texto plano, en un archivo que nadie trata como bóveda de secretos y que suele copiarse a un agregador.

Lo encontramos leyendo el registro de acceso de nuestra propia API mientras nacía una línea de WhatsApp. OWASP lo tiene clasificado y es tajante en lo que más cuesta aceptar: **usar HTTPS no lo resuelve.** El cifrado protege el tramo entre las dos puntas; el valor se escapa en los extremos, donde se escribe el registro.

## La misma plataforma muestra las dos formas

Meta hace las dos cosas en el mismo canal. El saludo inicial lleva el secreto en la URL. Las notificaciones que llegan después no llevan secreto alguno: viajan firmadas en la cabecera `X-Hub-Signature-256`, un hash del cuerpo calculado con la clave de la aplicación. Quien recibe no necesita que el secreto viaje: lo usa de su lado para recalcular la firma y comparar.

Esa diferencia no es de gusto. Una cabecera no entra en el registro de acceso, no queda en el historial del navegador y no se reenvía en el Referer. Un parámetro de la URL hace las tres cosas.

## Cuando el diseño ajeno no se puede cambiar

Apagar el registro de acceso no sirve: es justo lo que te dice quién llamó y con qué respuesta cuando un alta se queda a medias. Lo que está hoy en producción tacha en el punto de escritura: un filtro reemplaza por asteriscos el valor de los parámetros que llevan secreto y deja la línea intacta, con su método, su ruta y su código de respuesta.

Queda la parte que se olvida. **Un secreto que ya viajó en una URL está quemado.** Tacharlo de ahora en adelante no borra los registros de ayer: hay que rotarlo y volver a apuntar la suscripción al valor nuevo.

## Qué revisar en tu propio sistema

Busca en tus registros los nombres `token`, `secret`, `apikey` y `password` con un valor al lado: cada uno que aparezca es un secreto que hay que rotar, no solo tachar. Y cuando el proveedor te deje elegir, firma el cuerpo con una cabecera.

Escribimos desde la operación de agentes de WhatsApp en producción ([casos](/casos)), y lo que aplicamos en ese canal está en [seguridad](/seguridad). Del mismo canal, otra regla que no se delega al modelo: [no toda línea de WhatsApp puede iniciar una conversación](/blog/no-toda-linea-de-whatsapp-puede-iniciar). Del mismo cuaderno de hallazgos, uno que no se ve hasta que llegan dos escrituras a la vez: [una clave foránea es un candado](/blog/deadlock-postgres-clave-foranea). Y otro secreto que viaja en la URL a propósito, la firma de un archivo, con un problema distinto: [la red del cliente bloquea el dominio y el archivo no llega](/blog/red-del-cliente-bloquea-s3-url-firmada).
---

## Sobre qué se escribió

- Meta, «Webhooks, primeros pasos»: la solicitud de verificación llega con hub.mode, hub.challenge y hub.verify_token en la cadena de consulta, y las notificaciones posteriores se validan con la cabecera X-Hub-Signature-256, un hash del cuerpo calculado con la clave de la aplicación. Consultado el 26 de septiembre de 2026: https://developers.facebook.com/docs/graph-api/webhooks/getting-started
- OWASP, «Information exposure through query strings in URL»: el valor de un parámetro de la URL queda expuesto en la cabecera Referer, los registros web, los sistemas compartidos, el historial y la caché del navegador, y usar HTTPS no resuelve la falla. Consultado el 26 de septiembre de 2026: https://owasp.org/www-community/vulnerabilities/Information_exposure_through_query_strings_in_url
- nginx, módulo ngx_http_log_module: el formato predefinido «combined», que se usa cuando no se especifica otro, registra «$request», la línea de petición completa. Consultado el 26 de septiembre de 2026: https://nginx.org/en/docs/http/ngx_http_log_module.html

IntegraLaIA (https://integralaia.com). Publicado bajo el enlace original; cítalo si lo reutilizas.
