Webhooks explicados: cargas firmadas y reintentos

Backend2026-11-03TryQuickToolBox

Acabas de integrar un proveedor de pagos externo. El usuario completa una compra, pero tu base de datos nunca se actualiza. ¿Por qué? Porque el webhook que debía notificar a tu aplicación falló silenciosamente. Sin una verificación de firma adecuada y una lógica de reintentos, los webhooks pueden convertirse en una fuente de errores misteriosos y vulnerabilidades de seguridad.

En este artículo, explicaremos cómo funcionan los webhooks, por qué debes verificar las cargas firmadas y cómo diseñar un mecanismo de reintentos robusto. También te mostraremos cómo probar y depurar webhooks de manera efectiva.

¿Qué son los webhooks?

Los webhooks son callbacks HTTP definidos por el usuario. Permiten que un sistema envíe datos en tiempo real a otro cuando ocurre un evento. Por ejemplo, cuando se procesa un pago, el proveedor de pagos envía una solicitud HTTP POST a una URL que especifiques, con detalles sobre el evento.

A diferencia del polling, donde tu aplicación pregunta repetidamente por actualizaciones, los webhooks te envían los datos en cuanto están disponibles. Esto los hace eficientes y casi en tiempo real.

El problema de seguridad: ¿por qué verificar firmas?

Los endpoints de webhooks son URLs accesibles públicamente. Cualquiera puede enviarles una solicitud POST. Sin verificación, un atacante podría falsificar una solicitud para, por ejemplo, marcar un pedido como pagado sin que se haya realizado el pago. Para evitarlo, los proveedores firman cada carga con una clave secreta.

Cómo funcionan las firmas HMAC

La mayoría de los proveedores utilizan HMAC (Código de Autenticación de Mensajes basado en Hash) con SHA-256. Calculan un hash del cuerpo de la solicitud usando un secreto compartido y lo incluyen en un encabezado, como X-Signature. Tu servidor recalcula el hash y lo compara con el encabezado. Si coinciden, la solicitud es auténtica.

Aquí tienes un ejemplo simple en Python:

import hmac
import hashlib

def verify_signature(payload, secret, signature):
    expected = hmac.new(secret.encode(), payload, hashlib.sha256).hexdigest()
    return hmac.compare_digest(expected, signature)

Utiliza siempre una función de comparación de tiempo constante como hmac.compare_digest para evitar ataques de temporización.

Errores comunes que debes evitar

Diseñando reintentos confiables

Los webhooks se envían por internet, por lo que los fallos son inevitables. Las redes fallan, los servidores se reinician y los endpoints se agotan. Un buen sistema de webhooks reintenta las entregas fallidas con retroceso exponencial.

Cómo funcionan típicamente los reintentos

Los proveedores suelen reintentar con retrasos crecientes: después de 1 minuto, 5 minutos, 30 minutos, etc., hasta un número máximo de intentos (por ejemplo, 5-10). Si todos los reintentos fallan, el evento se marca como fallido y puede estar disponible para reproducción manual.

Tu endpoint debe responder rápidamente con un código de estado 2xx para confirmar la recepción. Si devuelves un código que no sea 2xx o se agota el tiempo, el proveedor reintentará.

Implementando idempotencia

Dado que los reintentos pueden causar entregas duplicadas, tu endpoint debe ser idempotente. Utiliza el ID del evento (incluido en la carga o en los encabezados) para rastrear los eventos procesados. Antes de procesar, verifica si ya has manejado ese ID. Almacena los IDs procesados en una base de datos con un TTL.

Ejemplo de verificación de idempotencia:

def process_event(event_id, data):
    if db.exists(f"processed:{event_id}"):
        return  # ya procesado
    # procesar datos
    db.setex(f"processed:{event_id}", 86400, "1")  # expira después de 24h

Construyendo un receptor de webhooks: paso a paso

  1. Expón un endpoint HTTPS: Usa TLS para proteger los datos en tránsito.
  2. Verifica la firma: Rechaza solicitudes con firmas inválidas con un 401.
  3. Verifica la marca de tiempo: Si se proporciona, asegúrate de que esté dentro de una ventana aceptable (por ejemplo, 5 minutos).
  4. Analiza la carga: Valida la estructura JSON y los campos requeridos.
  5. Verifica la idempotencia: Usa el ID del evento para evitar duplicados.
  6. Procesa de forma asíncrona: Responde con 200 inmediatamente, luego procesa en un trabajo en segundo plano para evitar tiempos de espera.
  7. Registra y monitorea: Registra fallos y configura alertas.

Probando webhooks localmente

Durante el desarrollo, necesitas una forma de recibir webhooks en tu máquina local. Herramientas como ngrok o localtunnel crean una URL pública que redirige a tu localhost. Alternativamente, muchos proveedores ofrecen una CLI para activar eventos de prueba.

Prueba siempre la verificación de firma con firmas válidas e inválidas. Simula reintentos devolviendo códigos de error y observando el comportamiento de reintento del proveedor.

Comparación: Webhooks vs. Polling

AspectoWebhooksPolling
Tiempo realCasi en tiempo realRetrasado por el intervalo
EficienciaSolo envía datos cuando ocurre el eventoSolicitudes frecuentes incluso sin datos
ComplejidadRequiere endpoint, verificación de firma, reintentosMás simple de implementar
ConfiabilidadDepende del mecanismo de reintentosTú controlas el horario

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 del remitente esperado y no fue manipulada.

¿Cuántas veces se debe reintentar un webhook?

La mayoría de los proveedores reintentan de 5 a 10 veces con retroceso exponencial. El número exacto depende de la política del proveedor. Tu endpoint debe ser idempotente para manejar duplicados.

¿Qué código de estado HTTP debe devolver mi webhook?

Devuelve un código de estado 2xx (por ejemplo, 200 OK) para confirmar la recepción exitosa. Cualquier otro código (4xx o 5xx) indica fallo y activa reintentos.

¿Listo para probar las cargas de tus webhooks? Usa nuestro Formateador JSON para validar y formatear cargas JSON rápidamente.