Webhooks explicados: cargas firmadas y reintentos
Has construido una API y ahora necesitas notificar a otros servicios cuando ocurren eventos. Configuras un endpoint de webhook, pero pronto te das cuenta de que entregar solicitudes HTTP de forma fiable es más difícil de lo que parece. Fallos de red, actores maliciosos y eventos duplicados pueden causar caos. Esta guía explica cómo implementar webhooks con cargas firmadas y reintentos para que puedas construir una integración robusta.
¿Qué son los webhooks?
Los webhooks son callbacks HTTP definidos por el usuario. Cuando ocurre un evento en tu aplicación, envías una solicitud HTTP POST a una URL proporcionada por el suscriptor. Esto permite la comunicación en tiempo real entre servicios sin sondeo.
Por ejemplo, un procesador de pagos podría enviar un webhook a tu aplicación cuando un cargo se completa con éxito. Tu aplicación puede entonces actualizar el estado del pedido inmediatamente.
¿Por qué firmar las cargas de los webhooks?
Los endpoints de webhook son accesibles públicamente, por lo que cualquiera puede enviar solicitudes falsas. Sin verificación, tu aplicación podría procesar eventos fraudulentos. Firmar las cargas asegura que la solicitud se originó en tu servicio y no ha sido manipulada.
La firma normalmente utiliza un secreto compartido entre el emisor y el receptor. El emisor calcula un código de autenticación de mensaje basado en hash (HMAC) sobre la carga y lo incluye en una cabecera. El receptor recalcula el HMAC y lo compara.
Cómo generar y verificar firmas
La mayoría de las implementaciones usan HMAC-SHA256. Aquí tienes un ejemplo simple en Python:
import hmac
import hashlib
import json
def generate_signature(payload, secret):
body = json.dumps(payload, separators=(',', ':')).encode('utf-8')
signature = hmac.new(secret.encode('utf-8'), body, hashlib.sha256).hexdigest()
return signature
def verify_signature(payload, signature, secret):
expected = generate_signature(payload, secret)
return hmac.compare_digest(expected, signature)
Al enviar el webhook, incluye la firma en una cabecera como X-Signature. El receptor extrae el cuerpo sin procesar, recalcula el HMAC y lo compara. Utiliza siempre una función de comparación en tiempo constante para prevenir ataques de temporización.
Implementación de reintentos para una entrega fiable
Los webhooks se envían a través de Internet, por lo que los fallos son inevitables. Tu servidor podría estar caído, la red podría ser lenta o el suscriptor podría devolver un error. Los reintentos con retroceso exponencial son esenciales.
Estrategia de reintentos
Un enfoque común es reintentar con demoras crecientes: 1 minuto, 5 minutos, 30 minutos, 2 horas, etc. Detente después de un número máximo de intentos (por ejemplo, 10) o una ventana de tiempo total (por ejemplo, 24 horas).
Reintenta solo en condiciones específicas: errores de red, respuestas 5xx o 429 (Too Many Requests). No reintentes en errores 4xx (excepto 429) porque indican un error del cliente que no se solucionará reintentando.
Ejemplo de lógica de reintentos
Aquí tienes un mecanismo de reintento simplificado en Python usando la librería requests:
import requests
import time
from requests.exceptions import RequestException
def send_webhook(url, payload, secret, max_retries=5):
headers = {'Content-Type': 'application/json'}
signature = generate_signature(payload, secret)
headers['X-Signature'] = signature
for attempt in range(max_retries):
try:
response = requests.post(url, json=payload, headers=headers, timeout=10)
if response.status_code < 500 and response.status_code != 429:
return response.status_code
except RequestException:
pass
# Retroceso exponencial con jitter
delay = (2 ** attempt) + (random.random() * 0.1)
time.sleep(delay)
return None # Falló después de los reintentos
En producción, almacenarías los intentos fallidos en una cola y los procesarías de forma asíncrona. Herramientas como Celery o colas en la nube (AWS SQS, Google Cloud Tasks) pueden ayudar.
Mejores prácticas para la seguridad de webhooks
- Usa HTTPS: Envía siempre webhooks sobre TLS para prevenir escuchas.
- Incluye una marca de tiempo: Añade una marca de tiempo a la carga y rechaza solicitudes con más de unos minutos de antigüedad para prevenir ataques de repetición.
- Rota los secretos: Cambia periódicamente el secreto de firma y admite múltiples secretos activos durante la rotación.
- Valida la URL: Al registrar un webhook, asegúrate de que la URL sea accesible y devuelva un 2xx a una solicitud de prueba.
- Límite de tasa: Protege tu emisor de webhooks contra abusos limitando el número de solicitudes por suscriptor.
Manejo de duplicados e idempotencia
Los reintentos pueden causar entregas duplicadas. Incluye un ID de evento único en la carga y haz que el receptor rastree los IDs procesados. Si un ID de evento ya se ha visto antes, ignóralo.
Alternativamente, diseña tus manejadores de eventos para que sean idempotentes. Por ejemplo, actualizar un registro con los mismos datos varias veces debería tener el mismo efecto que hacerlo una vez.
Pruebas de webhooks
Usa herramientas como ngrok para exponer tu servidor local y probar la entrega de webhooks. Simula fallos devolviendo códigos de error y verifica que los reintentos ocurran como se espera.
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 alterada.
¿Cuántas veces debo reintentar un webhook fallido?
Depende de tus necesidades de fiabilidad. Una práctica común es de 5 a 10 reintentos con retroceso exponencial durante 24 horas. Siempre permite que los suscriptores activen manualmente un reintento.
¿Puedo usar webhooks sin firmar?
Puedes, pero es arriesgado. Sin firma, cualquiera puede enviar eventos falsos. Firma siempre los webhooks en producción para prevenir fraudes.
¿Listo para probar tus cargas de webhook? Usa nuestro Formateador JSON para validar y embellecer las cargas JSON antes de enviarlas.