Webhooks explicados: cargas firmadas y estrategias de reintento
Acabas de integrar un servicio de terceros que envía webhooks y todo funciona bien en las pruebas. Pero en producción, notas ocasionalmente eventos perdidos, entregas duplicadas o, peor aún, un actor malicioso enviando cargas falsas a tu endpoint. Los webhooks son simples en concepto pero complicados en la práctica. Este artículo explica cómo asegurar webhooks con cargas firmadas e implementar estrategias de reintento robustas para que puedas confiar en tus integraciones basadas en eventos.
¿Qué son los webhooks y por qué importan?
Los webhooks son callbacks HTTP definidos por el usuario que se activan por eventos específicos. Cuando ocurre un evento, como un pago exitoso o un nuevo registro de usuario, el servicio de origen envía una solicitud HTTP POST a una URL que proporcionas. Este modelo basado en push es más eficiente que el polling, reduciendo la latencia y la carga del servidor. Sin embargo, introduce desafíos: verificar la autenticidad y manejar fallos de entrega.
Asegurando webhooks con cargas firmadas
Sin verificación, cualquiera puede enviar solicitudes a tu endpoint de webhook. Las cargas firmadas resuelven esto al incluir una firma criptográfica que prueba que la solicitud se originó desde el remitente confiable.
Cómo funcionan las firmas HMAC
La mayoría de los proveedores usan HMAC (Hash-based Message Authentication Code) con un secreto compartido. El remitente calcula un hash de la carga usando el secreto y lo incluye en un encabezado (por ejemplo, X-Signature). Tu servidor recalcula el hash y lo compara. Si coinciden, la solicitud es auténtica.
// Ejemplo: Verificando una firma de webhook en Node.js
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const hmac = crypto.createHmac('sha256', secret);
hmac.update(payload);
const digest = hmac.digest('hex');
return crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(digest));
}
Importante: Siempre usa una comparación de tiempo seguro para prevenir ataques de temporización. Además, verifica la firma contra el cuerpo de la solicitud sin procesar, no contra un objeto JSON parseado, porque el parseo puede alterar la carga.
Mejores prácticas para la verificación de firmas
- Usa HTTPS: Siempre sirve webhooks sobre TLS para prevenir escuchas.
- Rota los secretos: Rota periódicamente tu secreto de firma y admite múltiples secretos activos durante la rotación.
- Valida la marca de tiempo: Incluye una marca de tiempo en la firma para prevenir ataques de repetición. Rechaza solicitudes con más de unos minutos de antigüedad.
- Registra fallos: Monitorea los fallos de verificación de firmas para detectar ataques o configuraciones incorrectas.
Implementando reintentos confiables
Incluso con cargas firmadas, problemas de red o interrupciones temporales pueden causar que la entrega del webhook falle. Una estrategia de reintento robusta asegura que los eventos se entreguen eventualmente sin abrumar tu sistema.
Backoff exponencial con jitter
Reintentar inmediatamente puede empeorar la congestión. En su lugar, usa backoff exponencial: espera más tiempo entre cada reintento. Agrega jitter (variación aleatoria) para evitar problemas de estampida cuando muchos webhooks fallan simultáneamente.
// Ejemplo: Programa de reintentos con backoff exponencial y jitter
function getRetryDelay(attempt) {
const base = 1000; // 1 segundo
const max = 30000; // 30 segundos
const delay = Math.min(base * Math.pow(2, attempt), max);
return delay + Math.random() * 1000; // agrega hasta 1s de jitter
}
Idempotencia: manejando duplicados
Los reintentos pueden causar entregas duplicadas. Tu manejador de webhooks debe ser idempotente: procesar el mismo evento varias veces debe tener el mismo efecto que procesarlo una vez. Usa el ID del evento de la carga para rastrear eventos procesados.
- Almacena los IDs de eventos procesados en una base de datos con un TTL.
- Antes de procesar, verifica si el ID del evento ya existe; si es así, devuelve una respuesta de éxito inmediatamente.
- Diseña operaciones para que sean idempotentes (por ejemplo, usa upserts en lugar de inserts).
Responde rápido y confirma
Tu endpoint de webhook debe responder con un código de estado 2xx lo más rápido posible, idealmente en unos pocos segundos. Si el procesamiento tarda más, encola el evento para manejo asíncrono y devuelve 202 Accepted. Esto previene timeouts y reintentos innecesarios.
Comparación: Webhook vs. Polling
| Aspecto | Webhooks | Polling |
|---|---|---|
| Latencia | Casi en tiempo real | Depende del intervalo |
| Carga del servidor | Baja (solo push) | Alta (solicitudes constantes) |
| Complejidad | Mayor (necesita reintentos, seguridad) | Menor |
| Confiabilidad | Requiere lógica de reintento | Inherentemente confiable |
Paso a paso: Construyendo un receptor de webhooks seguro
- Configura un endpoint HTTPS con un certificado válido.
- Extrae el encabezado de firma (por ejemplo,
X-Signature) y la marca de tiempo. - Verifica la firma usando el secreto compartido y el cuerpo sin procesar.
- Comprueba la marca de tiempo para rechazar solicitudes antiguas (por ejemplo, con más de 5 minutos).
- Parsea la carga y verifica si hay IDs de eventos duplicados.
- Procesa el evento de forma asíncrona si es necesario, y responde con 200 OK.
- Registra el evento para auditoría y depuración.
Preguntas frecuentes
¿Qué es una carga de webhook firmada?
Una carga firmada incluye una firma criptográfica (generalmente HMAC) que permite al receptor verificar que la solicitud provino de una fuente confiable y no fue manipulada.
¿Cuántas veces debo reintentar un webhook fallido?
La mayoría de los sistemas reintentan de 3 a 5 veces con backoff exponencial. Después de eso, el evento generalmente se marca como fallido y puede requerir intervención manual o manejo de cola de mensajes muertos.
¿Puedo usar webhooks sin HTTPS?
Está fuertemente desaconsejado. Sin HTTPS, las cargas y firmas pueden ser interceptadas o modificadas. Siempre usa TLS para endpoints de webhooks.
Al depurar cargas o registros de webhooks, formatear correctamente el JSON es esencial. Usa nuestro JSON Formatter para imprimir y validar cargas JSON rápidamente.