Webhooks explicados: cargas firmadas y estrategias de reintento

Backend2026-10-30TryQuickToolBox

Has construido una API y ahora necesitas notificar a otros servicios cuando ocurren eventos. El polling es ineficiente, así que implementas webhooks. Pero pronto te enfrentas a dos problemas comunes: cómo asegurar que la carga no ha sido manipulada y qué hacer cuando el endpoint receptor está caído. Este artículo explica cómo usar cargas firmadas y estrategias de reintento para hacer tus webhooks seguros y confiables.

Por qué los webhooks necesitan firmas y reintentos

Los webhooks son callbacks HTTP activados por eventos. Son simples de implementar pero introducen desafíos de seguridad y confiabilidad. Sin firmas, un atacante podría falsificar solicitudes a tu endpoint, llevando a corrupción de datos o acciones no autorizadas. Sin reintentos, fallos transitorios (como cortes de red o errores temporales del servidor) causan eventos perdidos.

Las cargas firmadas resuelven la autenticidad: el emisor firma la carga con un secreto y el receptor la verifica. Los reintentos resuelven la confiabilidad: el emisor reintenta entregas fallidas con retroceso exponencial.

Cómo funcionan las cargas firmadas

Una carga firmada incluye una firma criptográfica generada a partir del cuerpo de la solicitud y un secreto compartido. El método más común es HMAC (Hash-based Message Authentication Code) con SHA-256.

Generando la firma

El emisor calcula el HMAC del cuerpo crudo de la solicitud usando la clave secreta. La firma se envía en un encabezado, a menudo junto con una marca de tiempo para prevenir ataques de repetición.

// Ejemplo en Node.js: generando firma HMAC
const crypto = require('crypto');

function signPayload(payload, secret) {
  const timestamp = Math.floor(Date.now() / 1000);
  const signedPayload = `${timestamp}.${JSON.stringify(payload)}`;
  const signature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  return { timestamp, signature };
}

Incluye la marca de tiempo y la firma en los encabezados, por ejemplo, X-Webhook-Timestamp y X-Webhook-Signature.

Verificando la firma

El receptor debe reconstruir la carga firmada usando la marca de tiempo recibida y el cuerpo crudo, luego calcular el HMAC y compararlo de forma segura.

// Ejemplo en Node.js: verificando firma
function verifySignature(rawBody, timestamp, receivedSignature, secret) {
  const signedPayload = `${timestamp}.${rawBody}`;
  const expectedSignature = crypto
    .createHmac('sha256', secret)
    .update(signedPayload)
    .digest('hex');
  // Usar comparación timing-safe
  return crypto.timingSafeEqual(
    Buffer.from(expectedSignature, 'hex'),
    Buffer.from(receivedSignature, 'hex')
  );
}

Siempre usa una comparación timing-safe para prevenir ataques de temporización. Además, verifica que la marca de tiempo sea reciente (por ejemplo, dentro de 5 minutos) para mitigar ataques de repetición.

Diseñando una estrategia de reintento

Los reintentos son esenciales para una entrega confiable de webhooks. Pero los reintentos ingenuos pueden abrumar al receptor o causar procesamiento duplicado. Una buena estrategia de reintento incluye:

Aquí hay un bucle de reintento simple en pseudocódigo:

function deliverWithRetry(url, payload, maxRetries = 5) {
  let attempt = 0;
  while (attempt < maxRetries) {
    try {
      const response = await fetch(url, { method: 'POST', body: payload });
      if (response.ok) return;
    } catch (error) {
      // Registrar error
    }
    attempt++;
    const delay = Math.pow(2, attempt) * 1000; // retroceso exponencial
    await new Promise(resolve => setTimeout(resolve, delay));
  }
  // Mover a cola de mensajes muertos
}

Considera añadir jitter al retroceso para evitar problemas de estampida.

Comparación: Polling vs Webhooks con reintentos

Aspecto Polling Webhooks con reintentos
Latencia Alta (depende del intervalo) Baja (casi en tiempo real)
Carga del servidor Constante (incluso sin eventos) Basada en eventos
Confiabilidad Alta (controlada por el receptor) Depende de la estrategia de reintento
Seguridad Autenticación de API estándar Requiere cargas firmadas

Paso a paso: Implementando webhooks con firmas y reintentos

  1. Genera una clave secreta para cada endpoint de webhook y compártela de forma segura.
  2. Al ocurrir un evento, construye la carga (JSON) y calcula la firma usando HMAC-SHA256 con el secreto y una marca de tiempo.
  3. Envía la solicitud POST al endpoint con los encabezados: Content-Type: application/json, X-Webhook-Timestamp y X-Webhook-Signature.
  4. En el lado del receptor, verifica la firma usando el cuerpo crudo y la marca de tiempo. Rechaza si es inválida o la marca de tiempo es demasiado antigua.
  5. Si la entrega falla (respuesta no 2xx o error de red), reintenta con retroceso exponencial hasta un número máximo de intentos.
  6. Después del máximo de reintentos, registra el fallo y opcionalmente alerta; almacena el evento en una cola de mensajes muertos para reproducción manual.
  7. Asegura la idempotencia en el receptor usando un ID de evento para ignorar duplicados.

Errores comunes y mejores prácticas

Preguntas frecuentes

¿Qué es una carga de webhook firmada?

Una carga de webhook firmada incluye una firma criptográfica (por ejemplo, HMAC) que permite al receptor verificar que la solicitud se originó del emisor esperado y no fue manipulada.

¿Cuántas veces debo reintentar un webhook fallido?

Una práctica común es de 3 a 5 reintentos con retroceso exponencial. El número exacto depende de tu SLA y la criticidad del evento. Siempre usa una cola de mensajes muertos para eventos que excedan el límite de reintentos.

¿Puedo usar webhooks sin firmas?

No se recomienda. Sin firmas, tu endpoint es vulnerable a solicitudes falsificadas. Siempre usa firmas (como HMAC) y valida las marcas de tiempo para prevenir ataques de repetición.

Al depurar entregas de webhooks, inspeccionar los registros del servidor es crucial. Si estás usando Nginx, el Analizador de registros de Nginx puede ayudarte a analizar los registros de acceso para rastrear solicitudes de webhooks e identificar fallos.