Webhooks explicados: payloads firmados y lógica de reintentos
Has creado una API y ahora necesitas notificar a otros servicios cuando ocurren eventos. El polling es ineficiente, así que recurres a los webhooks. Pero pronto descubres que los webhooks pueden fallar, ser suplantados o provocar procesamientos duplicados. Este artículo explica cómo proteger los webhooks con payloads firmados e implementar una lógica de reintentos robusta.
¿Qué son los webhooks?
Los webhooks son callbacks HTTP definidos por el usuario. Cuando ocurre un evento en tu sistema (por ejemplo, un pago exitoso), envías una solicitud HTTP POST a una URL proporcionada por el suscriptor. El payload suele contener los datos del evento en formato JSON o de formulario.
Los webhooks permiten la comunicación en tiempo real entre servicios sin polling constante. Son ampliamente utilizados por pasarelas de pago, herramientas de CI/CD y plataformas SaaS.
¿Por qué proteger los webhooks?
Sin seguridad, cualquiera que conozca la URL del webhook puede enviar solicitudes falsas, lo que provoca corrupción de datos o fraude. Además, los atacantes podrían reenviar solicitudes antiguas. Firmar los payloads con un secreto compartido garantiza la autenticidad y la integridad.
Firmar payloads con HMAC
HMAC (Hash-based Message Authentication Code) es un método común para firmar webhooks. El emisor y el receptor comparten una clave secreta. El emisor calcula un HMAC del payload usando el secreto y lo incluye en una cabecera (por ejemplo, X-Signature). El receptor recalcula el HMAC y lo compara.
Paso a paso: enviar un webhook firmado
- Crea el payload (JSON).
- Calcula el HMAC-SHA256 del payload usando el secreto.
- Incluye el digest hexadecimal en una cabecera, por ejemplo,
X-Signature: sha256=.... - Envía la solicitud POST.
// Ejemplo en Node.js usando crypto
const crypto = require('crypto');
const secret = 'your_shared_secret';
const payload = JSON.stringify({ event: 'payment.success', amount: 100 });
const signature = crypto.createHmac('sha256', secret).update(payload).digest('hex');
// Enviar POST con la cabecera X-Signature: sha256=signature
Verificar la firma
En el lado del receptor, extrae la cabecera de la firma, calcula el HMAC del cuerpo raw de la solicitud y compara usando una comparación de tiempo constante para prevenir ataques de temporización.
// Ejemplo en Express.js
app.post('/webhook', (req, res) => {
const signature = req.headers['x-signature'];
const computed = crypto.createHmac('sha256', secret).update(req.rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(signature), Buffer.from(computed))) {
return res.status(401).send('Invalid signature');
}
// Procesar el payload
res.status(200).send('OK');
});
Nota: Asegúrate de usar el cuerpo raw, no el JSON parseado, ya que el parseo puede alterar el payload.
Prevenir ataques de replay
Incluye una marca de tiempo en el payload firmado y rechaza las solicitudes con más de unos minutos de antigüedad. Además, incluye un ID de evento único y almacena los IDs procesados para evitar duplicados.
Lógica de reintentos para una entrega fiable
Los endpoints de webhook pueden estar caídos o lentos. Implementa reintentos con backoff exponencial y jitter. Establece un número máximo de reintentos (por ejemplo, 5) y un timeout para cada solicitud.
Estrategia de reintentos
- Backoff exponencial: Espera 1s, 2s, 4s, 8s, etc.
- Jitter: Añade aleatoriedad para evitar el efecto estampida.
- Idempotencia: Usa una clave de idempotencia para que las entregas duplicadas no provoquen acciones duplicadas.
Ejemplo de programación de reintentos: 1er reintento tras 5s, 2º tras 30s, 3º tras 2min, 4º tras 10min, 5º tras 1h.
Gestionar los fallos
Si todos los reintentos fallan, registra el fallo y notifica al suscriptor (por ejemplo, mediante email o panel de control). Proporciona una forma de reenviar webhooks manualmente.
Comparación: payloads firmados vs. Basic Auth
| Método | Seguridad | Complejidad | Protección contra replay |
|---|---|---|---|
| Payload firmado con HMAC | Alta | Moderada | Sí (con marca de tiempo) |
| Basic Auth | Baja | Baja | No |
| API Key en cabecera | Media | Baja | No |
Buenas prácticas
- Usa siempre HTTPS para los endpoints de webhook.
- Rota los secretos periódicamente.
- Valida el esquema del payload antes de procesarlo.
- Responde rápidamente (dentro de 2xx) y procesa de forma asíncrona.
- Monitoriza las métricas de entrega de webhooks (tasa de éxito, latencia).
Preguntas frecuentes
¿Cómo verifico la firma de un webhook?
Calcula el HMAC del cuerpo raw de la solicitud usando tu secreto y compáralo con la cabecera de la firma usando una comparación de tiempo constante.
¿Qué debo hacer si un webhook falla?
Implementa reintentos con backoff exponencial y jitter. Si todos los reintentos fallan, registra el fallo y proporciona una opción de reenvío manual.
¿Puedo usar webhooks sin firmar?
No se recomienda. Sin firma, no puedes verificar el emisor, lo que hace que tu sistema sea vulnerable a suplantación y ataques de replay.
Al probar payloads de webhooks, es posible que necesites inspeccionar respuestas JSON. Usa nuestro JSON Formatter para formatear y validar datos JSON rápidamente.