Webhooks explicados: cargas firmadas y reintentos
Por qué los webhooks necesitan firmas y reintentos
Has creado un endpoint para recibir webhooks de un proveedor de pagos o una plataforma SaaS. Todo funciona en pruebas, pero en producción te enfrentas a dos problemas: ¿cómo sabes que la solicitud realmente proviene del proveedor? y ¿qué sucede cuando tu servidor está caído o lento? Sin cargas firmadas, cualquiera puede falsificar una solicitud. Sin reintentos, un solo fallo de red puede perder un evento para siempre.
Esta guía explica cómo implementar ambos correctamente. Cubriremos la firma HMAC, los pasos de verificación, las estrategias de reintentos y la idempotencia: los tres pilares del manejo confiable de webhooks.
Cómo funcionan las firmas de webhooks
Una firma de webhook es un hash criptográfico que demuestra que la carga fue creada por alguien que conoce un secreto compartido. La mayoría de los proveedores usan HMAC-SHA256. El emisor calcula la firma sobre el cuerpo crudo de la solicitud (a veces con una marca de tiempo) y la incluye en una cabecera. El receptor la recalcula y compara.
Este es un flujo típico:
- El proveedor y el consumidor comparten una clave secreta (configurada en el panel del proveedor).
- El proveedor crea la carga, calcula
HMAC-SHA256(secret, payload)y la envía en una cabecera comoX-Signature. - El consumidor recibe la solicitud, lee el cuerpo crudo, recalcula el HMAC y compara usando una función de tiempo constante.
- Si coinciden, procesa el evento; de lo contrario, rechaza con 401.
Importante: Siempre usa el cuerpo crudo de la solicitud tal como se recibió, no una versión parseada y re-serializada. La serialización JSON puede cambiar los espacios en blanco y el orden de las claves, rompiendo la firma.
Añadir marcas de tiempo para prevenir ataques de repetición
Una carga firmada por sí sola puede ser reenviada por un atacante que capture una solicitud válida. Para mitigarlo, incluye una marca de tiempo en los datos firmados y rechaza solicitudes más antiguas que unos minutos. Por ejemplo:
signed_payload = timestamp + "." + raw_body
signature = HMAC-SHA256(secret, signed_payload)
El consumidor verifica que la marca de tiempo esté dentro de una ventana aceptable (por ejemplo, 5 minutos) y que la firma coincida.
Implementar la verificación de firmas
Veamos un ejemplo concreto en Python usando Flask. Verificaremos una firma enviada en la cabecera X-Signature.
import hmac
import hashlib
from flask import Flask, request, abort
app = Flask(__name__)
SECRET = b'your_shared_secret'
@app.route('/webhook', methods=['POST'])
def webhook():
signature = request.headers.get('X-Signature')
if not signature:
abort(401)
raw_body = request.get_data()
expected = hmac.new(SECRET, raw_body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(expected, signature):
abort(401)
# Procesar el evento
event = request.json
# ... manejar evento ...
return '', 200
Puntos clave:
- Usa
hmac.compare_digest(o equivalente) para comparación en tiempo constante y evitar ataques de temporización. - Accede al cuerpo crudo mediante
request.get_data()antes de cualquier parseo. - Devuelve 200 rápidamente; realiza el procesamiento pesado de forma asíncrona.
Diseñar una estrategia de reintentos
Incluso con firmas, los webhooks pueden fallar: tu servidor podría estar caído, devolver un 500 o agotar el tiempo de espera. Los proveedores suelen reintentar con retroceso exponencial. Como consumidor, también debes manejar los reintentos con elegancia.
Esto es lo que debes considerar:
- Idempotencia: Cada evento debe tener un ID único. Almacena los IDs procesados para evitar el procesamiento duplicado cuando ocurran reintentos.
- Responde rápido: Confirma la recepción con un estado 2xx en pocos segundos. Encola el evento para procesamiento en segundo plano.
- Lógica de reintentos de tu lado: Si llamas a servicios downstream, implementa reintentos con retroceso y cortacircuitos.
La mayoría de los proveedores reintentan ante respuestas no 2xx o tiempos de espera agotados, a menudo hasta por 24 horas con intervalos crecientes. Consulta la documentación de tu proveedor para más detalles.
Ejemplo: Manejador de eventos idempotente
Usa una tabla de base de datos para rastrear los IDs de eventos procesados. Aquí tienes un patrón simplificado:
def handle_event(event_id, payload):
if db.exists('processed_events', event_id):
return # ya manejado
# procesar carga
db.insert('processed_events', event_id)
# ... trabajo real ...
Para alto rendimiento, usa una restricción única en el ID del evento y captura los errores de clave duplicada.
Comparación: Webhooks firmados vs no firmados
| Aspecto | Cargas firmadas | Cargas no firmadas |
|---|---|---|
| Seguridad | Verifica la autenticidad del emisor | Cualquiera puede falsificar solicitudes |
| Protección contra repetición | Posible con marcas de tiempo | Ninguna |
| Esfuerzo de implementación | Moderado (HMAC + verificación de marca de tiempo) | Bajo |
| Recomendado para | Todos los webhooks en producción | Solo pruebas internas |
Errores comunes y mejores prácticas
- No parsees antes de verificar: Siempre verifica la firma en el cuerpo crudo primero.
- Usa HTTPS: Las firmas protegen la integridad, pero HTTPS protege la confidencialidad.
- Rota los secretos: Admite múltiples secretos activos durante la rotación para evitar tiempo de inactividad.
- Registra los fallos: Guarda las discrepancias de firma y los reintentos para depuración.
- Prueba con cargas reales: Usa herramientas como webhook.site o la CLI de tu proveedor para simular eventos.
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 proviene de una fuente confiable y no fue manipulada.
¿Cómo manejo los reintentos de webhooks sin duplicar acciones?
Haz que tu manejador sea idempotente rastreando los IDs de eventos. Antes de procesar, verifica si el ID ya fue manejado; si es así, devuelve éxito sin repetir el trabajo.
¿Puedo usar webhooks sin firmas?
No se recomienda para producción. Sin firmas, no puedes distinguir solicitudes legítimas de falsificadas. Siempre verifica las firmas cuando estén disponibles.
Al depurar cargas de webhooks o analizar registros del servidor, el Formateador JSON puede ayudarte a inspeccionar y validar estructuras JSON rápidamente.