Webhooks explicados: payloads firmados y reintentos
Acabas de integrar el webhook de un proveedor de pagos para recibir actualizaciones de transacciones. Todo funciona en las pruebas, pero en producción notas eventos faltantes durante picos de tráfico o cargos duplicados cuando el proveedor reintenta. Los webhooks son simples en concepto pero complicados en la práctica: cruzan límites de red, pueden retrasarse, duplicarse o falsificarse. Este artículo explica cómo construir consumidores y productores de webhooks robustos utilizando payloads firmados y estrategias de reintento inteligentes.
¿Qué son los webhooks?
Los webhooks son callbacks HTTP definidos por el usuario que se activan por eventos. Cuando algo sucede en un sistema de origen (por ejemplo, un nuevo pedido), envía una solicitud HTTP POST a una URL que proporcionas. A diferencia del polling, los webhooks envían datos casi en tiempo real, reduciendo la latencia y la carga del servidor.
Sin embargo, los webhooks introducen desafíos: asegurar que la solicitud realmente provenga del remitente esperado, manejar fallos con elegancia y evitar el procesamiento duplicado.
Asegurando webhooks con payloads firmados
Sin verificación, cualquiera puede enviar solicitudes falsas a tu endpoint de webhook. Los payloads firmados resuelven esto incluyendo una firma criptográfica que puedes validar.
Cómo funcionan las firmas HMAC
El remitente calcula un código de autenticación de mensaje basado en hash (HMAC) usando un secreto compartido y el cuerpo de la solicitud. La firma se envía en un encabezado (por ejemplo, X-Signature). Tú recalculas el HMAC de tu lado y comparas.
// Ejemplo en Node.js: verificando una firma de webhook
const crypto = require('crypto');
function verifySignature(payload, signature, secret) {
const expected = crypto
.createHmac('sha256', secret)
.update(payload)
.digest('hex');
return crypto.timingSafeEqual(
Buffer.from(signature),
Buffer.from(expected)
);
}
Siempre usa una función de comparación de tiempo constante como timingSafeEqual para prevenir ataques de temporización.
Incluye una marca de tiempo
Para prevenir ataques de repetición, incluye una marca de tiempo en el payload firmado y rechaza solicitudes con más de unos minutos de antigüedad (por ejemplo, 5 minutos). La marca de tiempo debe ser parte de los datos firmados.
Implementando reintentos confiables
Problemas de red o errores temporales del servidor pueden causar que la entrega del webhook falle. Un remitente robusto reintenta las entregas fallidas con retroceso exponencial.
Estrategia de reintentos
- Retroceso exponencial: Espera más tiempo entre cada reintento (por ejemplo, 1s, 2s, 4s, 8s...).
- Máximo de reintentos: Establece un límite (por ejemplo, 5 intentos) para evitar bucles infinitos.
- Cola de mensajes muertos: Después del máximo de reintentos, almacena el evento para inspección manual.
- Idempotencia: Incluye un ID de evento único para que el receptor pueda deduplicar.
Ejemplo: Reintento con retroceso en Python
import time
import requests
from requests.exceptions import RequestException
def send_webhook(url, payload, max_retries=5):
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload, timeout=5)
if response.status_code < 500:
return True # éxito o error del cliente (sin reintento)
except RequestException:
pass
time.sleep(2 ** attempt) # retroceso exponencial
return False
Mejores prácticas para consumidores de webhooks
- Responde rápidamente: Acusa recibo con un estado 2xx antes de procesar. Encola el trabajo de forma asíncrona.
- Valida las firmas: Siempre verifica la firma antes de confiar en el payload.
- Maneja duplicados: Usa el ID del evento para asegurar un procesamiento idempotente.
- Registra todo: Mantén registros de los webhooks recibidos para depuración.
Comparación: Webhooks vs Polling
| Aspecto | Webhooks | Polling |
|---|---|---|
| Latencia | Casi en tiempo real | Depende del intervalo |
| Carga del servidor | Baja (solo push) | Alta (solicitudes frecuentes) |
| Complejidad | Mayor (necesita endpoint, seguridad) | Menor |
| Confiabilidad | Requiere reintentos | Reintentos inherentes |
Preguntas frecuentes
¿Qué es un payload de webhook firmado?
Un payload firmado incluye una firma criptográfica (generalmente HMAC) que permite al receptor verificar la autenticidad e integridad de la solicitud.
¿Cuántas veces debo reintentar un webhook fallido?
Una práctica común es de 3 a 5 reintentos con retroceso exponencial, pero depende de tu SLA y la criticidad del evento.
¿Puedo usar webhooks sin HTTPS?
No, siempre usa HTTPS para proteger el payload y la firma de intercepciones. HTTP simple expone datos sensibles.
Al depurar payloads de webhooks, es posible que necesites formatear JSON rápidamente. Prueba nuestro Formateador JSON para imprimir y validar respuestas JSON de manera legible.