Rotación de secreto de webhook sin downtime: firmas duales bien hechas
Aprende a rotar secretos de webhooks sin downtime usando verificación dual, logs claros, pasos seguros de corte, rollback y comprobaciones de limpieza.

Qué sale mal cuando rotas un secreto de webhook
Rotar un secreto de webhook suena simple: cambias el secreto en el emisor, lo cambias en tu receptor y listo. En la práctica, un desajuste de timing puede romper la verificación y convertir un día normal en una avalancha de entregas fallidas.
El fallo más común se ve así: el proveedor empieza a firmar solicitudes con el secreto nuevo antes de que tu servidor lo sepa (o tu servidor cambia primero mientras el proveedor aún usa el antiguo). Cada solicitud parece “manipulada”, así que tu receptor la rechaza.
Para la rotación de secretos sin downtime, la meta no es un conmutador perfecto de un segundo. Es una ventana breve de solapamiento donde se aceptan ambos secretos.
“El downtime” para webhooks suele manifestarse como:
- eventos perdidos que nunca se procesan (o llegan demasiado tarde)
- reintentos acumulándose y alcanzando límites de tasa
- duplicados cuando el proveedor reintenta y tu handler no es idempotente
- tickets de soporte porque pagos, correos o trabajos de sincronización se descuadran
La solución es aburrida pero fiable: acepta firmas creadas con el secreto antiguo o con el nuevo durante el corte, y vigila de cerca los fallos de firma. Cuando casi todo el tráfico valide con el secreto nuevo (y los reintentos se hayan vaciado), elimina el secreto antiguo.
Si tu handler de webhooks ya es frágil (parseo del body inconsistente, comprobaciones de firma inestables, preocupaciones mezcladas dentro de un handler gigante), la rotación tiende a exponerlo rápidamente.
Firmas de webhook en términos sencillos (y por qué la rotación se complica)
Un webhook es un sistema (el emisor) llamando a tu URL (el receptor) cuando pasa algo, como un pago o un registro. Porque cualquiera puede golpear tu endpoint, la mayoría de proveedores incluyen un secreto compartido y una cabecera de firma para que puedas verificar que la petición es real.
Con una firma HMAC, el emisor toma exactamente el body de la solicitud, lo mezcla con el secreto y produce una huella corta (la firma). Tu servidor hace el mismo cálculo con su copia del secreto. Si las huellas coinciden, el emisor demostró que conoce el secreto sin enviarlo por la red.
El problema: diferencias mínimas cambian la huella. Muchos fallos de firma durante la rotación no son “secretos malos”. Son desajustes en lo que se firma.
Errores comunes incluyen:
- firmar JSON parseado en lugar de los bytes crudos del body
- cambios de espacios o de orden de claves introducidos por middleware
- codificación equivocada (string vs bytes, UTF-8 vs otra cosa)
- diferencias en cabeceras (algunos proveedores usan nombres distintos o incluyen un timestamp)
- múltiples firmas en una cabecera (durante la rotación o por algoritmos distintos)
Entonces, ¿por qué “simplemente actualizar el secreto” rompe las cosas? Porque las actualizaciones no ocurren en todas partes a la vez. El proveedor puede desplegar cambios gradualmente, tu despliegue puede rodar por instancias en minutos y los reintentos pueden llegar después firmados con el secreto anterior. Si aceptas solo el secreto nuevo demasiado pronto, rechazarás eventos reales.
Por eso la rotación necesita una ventana de solapamiento donde verificas con ambos secretos, además de monitorización que te diga cuándo la firma antigua ha desaparecido efectivamente.
Planifica el corte: ventana de solapamiento y señales de éxito
Una rotación segura empieza con una decisión: cuánto tiempo aceptarás ambos secretos. Tu ventana de solapamiento debe ser más larga que el peor caso de tiempo en que un webhook puede llegar tarde. Eso incluye reintentos del proveedor (a veces horas o días), retrasos en tus colas y cualquier reenvío manual que tu equipo pueda disparar.
Antes de tocar código, confirma que puedes almacenar dos secretos a la vez y mantenerlos fuera de logs y mensajes de error. Trata uno como “actual” y otro como “anterior”. Haz posible voltear cuál es el actual sin redeploy (un cambio de configuración o actualización en el gestor de secretos).
Durante el solapamiento, normalmente verificas de una de dos maneras:
- probar el nuevo, y si falla, caer al antiguo
- verificar ambos y registrar cuál habría pasado
Define señales de éxito antes del cambio para no adivinar después. Controla:
- tasa de firma correcta (global y por endpoint si tienes varios)
- tasa de errores 4xx/5xx en el receptor
- latencia de entrega (timestamp del proveedor vs timestamp de procesado)
- volumen de reintentos (los picos suelen significar fallos de verificación)
Elige una regla de salida y cúmplela, por ejemplo: 99%+ de firmas pasando con el secreto nuevo durante 24 horas, sin aumento de reintentos y latencia estable. Luego programa la eliminación del secreto antiguo.
Paso a paso: implementar verificación dual en el receptor
Para rotar un secreto de webhook sin downtime, tu receptor necesita aceptar dos firmas válidas durante una ventana corta: el secreto nuevo y el antiguo.
Pon ambos secretos en la configuración (vars de entorno o gestor de secretos) y cárgalos como una lista ordenada. Mantén la función de verificación pequeña para poder unit testearla sin arrancar toda la app.
secrets = [NEW_SECRET, OLD_SECRET] // old is optional
def verify(raw_body, headers):
sig = headers["X-Signature"]
for secret in secrets:
if secret is empty: continue
expected = hmac(secret, raw_body)
if constant_time_equal(sig, expected):
return true
return false
Detalles que evitan dolores después:
- prueba el secreto nuevo primero, luego cae al antiguo
- usa comparación en tiempo constante (o un helper seguro de tu librería crypto)
- devuelve la misma respuesta de error para cualquier fallo de firma (no reveles cuál check falló)
- mantiene la función pura: entrada = body crudo + cabeceras, salida = true/false
- añade tests focalizados: válido con nuevo, válido con antiguo, inválido, cabecera ausente
Una regla práctica que evita muchos fallos misteriosos: calcula el HMAC sobre los bytes exactos del payload crudo que recibiste. Parsear JSON y re-serializarlo a menudo cambia espacios u orden de claves.
Si heredaste código de webhook generado por IA que mezcla parseo, verificación y lógica de negocio en un solo handler, separa la verificación en su propia función pequeña primero. Ese cambio hace que la verificación dual sea mucho más segura para desplegar.
Observabilidad durante la rotación: qué loguear y alertar
La rotación de secretos falla en silencio cuando no puedes ver qué secreto validó una petición, o por qué falló la validación. Trata la validación de firmas como un sistema de auth: logs claros, métricas simples y alertas que capten problemas reales sin ruido constante.
Registra fallos de firma usando un conjunto pequeño de categorías para que puedas agrupar y actuar:
- cabecera de firma ausente
- timestamp ausente o fuera de rango
- error al leer/parsear el body
- mismatch en la cadena canónica
- HMAC mismatch (new)
- HMAC mismatch (old)
También controla qué secreto validó las solicitudes exitosas. Un contador como webhook_validated_total{secret="new"} vs ...{secret="old"} te dice si los partners siguen usando el secreto antiguo y si la verificación dual funciona.
Una lista compacta que mantiene la seguridad:
- Log: request ID, provider event ID, categoría de motivo y qué secreto validó (nuevo/antiguo)
- Métrica: solicitudes totales, fallos totales, validadas-por-nuevo vs validadas-por-antiguo
- Alerta: incremento sostenido de fallos (tasa y recuento absoluto)
- Alerta: validaciones con el secreto antiguo altas después del solapamiento planeado
- Seguridad: nunca loguees secretos crudos; evita payloads completos si contienen PII, tokens o datos de pago
Los request IDs y event IDs importan porque los reintentos y duplicados parecen fallos aleatorios sin ellos. Si ves el mismo event ID fallando repetidamente, a menudo apunta a un bug de canonicalización más que a un atacante.
Playbook de corte: orden de despliegue, monitorización y rollback
Un corte limpio trata mayormente sobre el orden. Empieza haciendo el receptor más tolerante, luego cambia el emisor, luego endurece de nuevo.
Orden de despliegue (seguro por defecto)
- Stage 1: Despliega el receptor con verificación dual (acepta antiguo O nuevo). No cambies aún el emisor.
- Stage 2: Actualiza el emisor/proveedor para que firme con el secreto nuevo.
- Stage 3: Observa los resultados de validación hasta que la mayor parte del tráfico valide con el secreto nuevo.
Durante el Stage 1, la monitorización debería mostrar una línea base: casi todas las solicitudes validan con el secreto antiguo y las validaciones con el secreto nuevo son cercanas a cero. Tras el Stage 2, deberías ver un cambio sostenido del antiguo al nuevo.
Qué monitorizar y qué significa “bien”
Controla contadores, no solo logs: webhooks recibidos totales, válidos-nuevo, válidos-antiguo, inválidos. Alerta por un aumento de firmas inválidas y también si validaciones-antiguo se mantienen altas más tiempo del esperado (puede significar que el emisor no cambió realmente).
Para terminar el solapamiento, usa una condición clara para que la verificación dual no sea permanente:
- un tiempo mínimo de solapamiento (a menudo 24–72 horas, según comportamiento de reintentos)
- además: cero validaciones con el secreto antiguo durante una ventana completa (por ejemplo, 6–12 horas)
Plan de rollback
Si los fallos de firma suben después de cambiar el emisor, revierte el secreto del emisor primero. Mantén la verificación dual en el receptor durante el incidente. Eso mantiene el rollback en un solo cambio mientras investigas formateo del payload, drift de timestamps o despliegue del secreto equivocado.
Casos extremos que causan falsos fallos de firma
La mayoría de errores de “firma mala” durante la rotación no son realmente secretos malos. Son desajustes entre lo que el emisor firmó y lo que tu receptor verificó.
Primero, confirma que usas el secreto correcto para el entorno correcto. Los equipos suelen tener múltiples endpoints o entornos y los secretos se cruzan. Es común verificar un evento de producción con un secreto de staging porque un worker, cola o fichero de config apunta al sitio equivocado.
Si el proveedor usa firmas con timestamp, el skew de reloj puede parecer un fallo de firma. Acepta una ventana razonable (por ejemplo 5 minutos) y asegura que tus servidores tengan hora precisa. No aceptes una ventana enorme salvo que asumas el riesgo de replay.
Los reintentos y entregas fuera de orden también confunden el debug: un reintento antiguo puede llegar después de que hayas cambiado secretos. Durante el solapamiento, trata el evento como válido si cualquiera de las firmas verifica, y confía en la idempotencia para evitar el doble procesamiento.
Dos comprobaciones rápidas que detectan muchos “fallos misteriosos”:
- verifica contra los bytes crudos del body, no contra un objeto JSON re-serializado
- asegúrate de que el parseo del body no altere espacios, codificación o finales de línea antes de verificar
Finalmente, ten en cuenta que proxies y middleware pueden transformar el body (descompresión, cambios de charset, normalización de nuevas líneas). Aunque el payload parezca igual en logs, los bytes pueden no ser los mismos que el proveedor firmó.
Errores comunes (y las soluciones simples)
La mayoría de rotaciones fallidas no son por cripto. Son por detalles que cambian lo que se firma, o por fallos que permanecen ocultos hasta que los clientes se quejan.
Parsear JSON antes de verificar la firma es el error clásico. Muchos frameworks re-encodifican JSON (espacios, orden de claves, Unicode), así que los bytes que verificas no son los que el emisor firmó. Solución: captura primero el body crudo, verifica sobre esos bytes exactos y luego parsea JSON.
Otro bug común es leer el stream de la request dos veces. El middleware lee el body para logging y luego tu handler lo lee otra vez para verificar, pero la segunda lectura está vacía. Solución: bufferiza el body una vez y pasa ese buffer tanto al logging como a la verificación.
El manejo de la cabecera de firma también confunde. Algunos proveedores incluyen prefijos como sha256= o envían múltiples firmas. Solución: parsea la cabecera deliberadamente, selecciona el valor correcto y empata con el algoritmo del proveedor (sha1 vs sha256).
Un fallo de seguridad: tratar errores de verificación como “probablemente bien”. Timeouts, cabeceras malformadas, errores de decodificación y campos ausentes deben ser fallos duros, no pases suaves. Solución: falla cerrado, devuelve un 4xx claro y loguea una categoría de motivo.
Eliminar de forma segura el secreto antiguo y endurecer la seguridad
Una vez que tu ventana de solapamiento haya terminado y el secreto nuevo valide consistentemente, elimina el secreto antiguo de la configuración. Dejarlo “por si acaso” aumenta silenciosamente tu superficie de ataque y hace más difícil saber qué estás validando realmente.
Antes de borrar nada, confirma que tienes una señal de éxito clara: un ciclo de negocio completo sin fallos de firma inesperados y sin retrocesos inexplicables al secreto antiguo.
Una secuencia segura:
- deja de aceptar el secreto antiguo (elimínalo de la verificación dual o desactívalo con un feature flag)
- elimina el secreto antiguo de tu almacén de secretos y de la configuración en runtime
- revisa dónde pudo haberse filtrado el secreto (logs antiguos de CI, volcados de depuración, tokens de vault compartidos)
- restringe permisos para que solo un pequeño grupo de propietarios pueda leer o cambiar secretos de webhook
- documenta el runbook: propietario, pasos exactos, criterios de éxito, pasos de rollback y dónde mirar en los logs
Si sospechas que el secreto se expuso (historial de repo, capturas de pantalla, tickets de soporte del vendor), rota inmediatamente aunque estés en medio de un proyecto.
También documenta dónde vive la verificación de firmas en la base de código: módulo/función exacta, cómo se captura el body crudo (un punto común de fallo) y qué cabeceras se usan.
Checklist rápido para una rotación sin drama
Trata la rotación como una pequeña migración: solapa, mide y luego elimina.
Antes de cambiar nada
- Despliega código del receptor que acepte ambas firmas (antigua y nueva).
- Añade dashboards para tasa de éxito, tasa de fallo y validaciones por versión de secreto.
- Confirma que puedes cambiar rápido el secreto del emisor y que tienes un toggle de rollback.
Durante el corte
- Despliega verificación dual en el receptor primero, luego cambia el emisor.
- Observa firmas inválidas durante los primeros minutos y de nuevo después de tu ventana normal de reintentos.
- Mantén logs seguros: incluye tipo de evento, timestamp, sender ID y qué secreto validó. No loguees payloads crudos, firmas o secretos.
Después del cambio
- Espera el tiempo suficiente para que reintentos y entregas retrasadas terminen (a menudo al menos una ventana completa de reintentos, a veces 24 horas).
- Cuando los gráficos muestren cero validaciones con el secreto antiguo durante la ventana que elegiste, elimina el secreto antiguo.
- Escribe una nota de auditoría breve: cuándo rotaste, quién aprobó, qué monitorizaste y cuándo se eliminó el secreto antiguo.
Ejemplo realista: rotando el secreto de un webhook de pagos
Una pequeña SaaS acepta pagos con tarjeta y recibe eventos payment.succeeded de su proveedor de pagos. El equipo planea una ventana corta de solapamiento donde el receptor acepta firmas de ambos secretos.
El lunes por la mañana despliegan receptor v2 con verificación dual. Aún no cambian el proveedor. Durante la primera hora, casi todas las solicitudes validan con el secreto antiguo y el contador de secreto nuevo se mantiene cercano a cero (esperado).
Después del almuerzo actualizan el proveedor para que empiece a firmar con el secreto nuevo. En minutos los gráficos giran: valid_new sube, valid_old baja lentamente (por reintentos aún en vuelo) y invalid_both se mantiene plano. Esa es la señal clave de éxito.
Mantienen logs y contadores que responden una pregunta rápido: ¿qué pasó con este evento?
webhook_received event=payment.succeeded valid=old request_id=8f2...
webhook_received event=payment.succeeded valid=new request_id=912...
webhook_received event=payment.succeeded valid=none reason=signature_mismatch request_id=aa1...
metrics: valid_old=120 valid_new=118 invalid_both=0
Luego aparece un bug: invalid_both sube justo después de una actualización del framework. Que fallen ambos secretos al mismo tiempo es una pista fuerte de que la app está verificando los bytes equivocados (parseo del body o cambios de encoding). Corrigen el código para validar contra el payload crudo, redepliegan y el pico desaparece.
Al día siguiente, tras un periodo tranquilo, eliminan el secreto antiguo y siguen alertando sobre fallos de firma.
Próximos pasos si tu código de webhooks es poco fiable
Si intentas rotar un secreto sin downtime y el receptor sigue rechazando solicitudes reales, no lo trates como un problema de rotación. Trátalo como un problema de verificación.
Empieza por endurecer la ruta del body crudo. La mayor parte de bugs de firma ocurren porque el payload se cambia antes de calcular el HMAC (parseo y re-serialización JSON, cambios de espacio, codificación). Verifica la firma contra los bytes exactos que llegaron y luego parsea solo si pasa.
Añade un pequeño conjunto de tests automatizados que reproduzcan fallos reales de producción:
- firma válida con el body crudo exacto (debe pasar)
- un byte cambiado en el body (debe fallar)
- secreto equivocado (debe fallar)
- cabecera de firma ausente (debe fallar con un log claro)
- múltiples valores de firma (debe escoger el correcto o fallar de forma predecible)
Antes de producción, haz un ensayo en staging usando los mismos pasos: activa verificación dual, envía webhooks firmados con el secreto antiguo y con el nuevo, y confirma que logs y alertas se comportan como esperas.
Si tu handler de webhooks fue generado por herramientas como Lovable, Bolt, v0, Cursor o Replit y se comporta raramente bajo reintentos o rotaciones, una revisión focalizada puede ahorrarte un incidente largo. FixMyMess (fixmymess.ai) hace diagnóstico y reparación de bases de código para apps generadas por IA, incluyendo validación de firmas, manejo seguro del body crudo y preparación para despliegue.
Preguntas Frecuentes
How do I rotate a webhook secret without breaking deliveries?
Usa una ventana de solapamiento donde tu receptor acepte firmas creadas con cualquiera de los secretos, el antiguo o el nuevo. Despliega primero la verificación dual, cambia el proveedor después y solo elimina el secreto antiguo cuando los reintentos se hayan vaciado y veas que casi todas las validaciones usan el secreto nuevo.
Why does “just changing the secret” cause webhook failures?
Porque el emisor y el receptor rara vez cambian exactamente al mismo tiempo. Los proveedores pueden aplicar cambios de forma gradual, tus servidores pueden desplegarse en varios minutos y los reintentos retrasados pueden llegar firmados con el secreto anterior, así que un “cambio” de un solo secreto hace que eventos reales fallen la verificación.
What does webhook “downtime” look like during secret rotation?
La verificación falla, y tu handler lo trata como manipulación. El proveedor normalmente reintentará, pero puedes sufrir procesamiento retrasado, tormentas de reintentos, límites de tasa y duplicados si tu handler no es idempotente, por eso puede parecer downtime aunque el servidor esté arriba.
Should I verify the signature on parsed JSON or the raw request body?
Verifica el HMAC contra los exactos bytes del body crudo que recibiste, antes de cualquier parseo o re-serialización JSON. Parsear primero suele cambiar espacios, orden de claves o codificación, lo que altera el resultado de la firma aunque el secreto sea correcto.
During the overlap, should I try the new secret first or the old one?
Por defecto, nuevo primero, luego antiguo, y registra cuál de los dos tuvo éxito. Eso te mantiene alineado con la dirección prevista de la migración y aun así aceptas reintentos tardíos firmados con el secreto antiguo.
How long should I accept both secrets during rotation?
Mantenlo más tiempo que tu peor retraso esperado, incluyendo reintentos del proveedor, retrasos en colas internas y cualquier reenvío manual. Un referente común son 24–72 horas, pero la regla práctica es: no elimines el secreto antiguo hasta que las validaciones con el secreto antiguo caigan a cero durante la ventana completa que confíes.
What should I monitor while rotating webhook secrets?
Controla el total de webhooks recibidos, fallos de firma y la división de validaciones exitosas por secreto (nuevo vs antiguo). También vigila tasas 4xx/5xx del receptor, latencia de entrega y volumen de reintentos para detectar fallos de verificación antes de que afecten a los clientes.
What’s safe and useful to log for signature verification issues?
Registra un identificador de petición o evento, una categoría pequeña de motivo (cabecera ausente, timestamp fuera de rango, error al leer el body, fallo HMAC), y si la validación fue por nuevo o por antiguo. Evita registrar secretos en texto claro, firmas crudas o payloads completos si pueden contener datos sensibles.
What’s the safest rollback plan if signature failures spike?
Pon la verificación dual en el receptor primero y déjala activa. Si los fallos suben después de cambiar el proveedor, revierte el secreto del proveedor/emisor primero, porque suele ser el cambio único más rápido, mientras investigas parseo del body, parseo de cabeceras, timestamps o que se haya desplegado el secreto equivocado.
What are the most common bugs that cause “invalid signature” during rotation?
Los errores clásicos son leer el body dos veces (la segunda lectura queda vacía), verificar después de que middleware haya alterado el body, manejar mal cabeceras con prefijos o múltiples valores, usar el secreto del entorno equivocado y no usar comparación en tiempo constante. Solución: bufferiza el body crudo una vez, parsea las cabeceras deliberadamente, verifica antes de parsear y falla cerrado con respuestas 4xx coherentes.