Webhooks explicados: payloads firmados y reintentos

Backend2026-10-15TryQuickToolBox

Acabas de integrar una pasarela de pago. La documentación dice: "Enviaremos un webhook cuando un pago se complete". Genial. Pero luego te preguntas: ¿cómo sé que la petición realmente vino de ellos? ¿Y si mi servidor está caído cuando la envían? ¿Y si envían el mismo evento dos veces?

Los webhooks son potentes, pero tienen trampas. Esta guía repasa la mecánica, la seguridad y los patrones de fiabilidad que necesitas para gestionarlos correctamente.

¿Qué es un webhook?

Un webhook es una petición HTTP POST que un sistema envía a otro cuando ocurre un evento específico. En lugar de que tu app haga polling a una API cada pocos segundos ("¿Hay pedidos nuevos? ¿Hay pedidos nuevos?"), el proveedor te empuja los datos en cuanto ocurre algo.

Flujo típico:

  1. Registras una URL (p. ej., https://yourapp.com/webhooks/payments) con el proveedor.
  2. Ocurre un evento (pago completado, usuario registrado, archivo subido).
  3. El proveedor envía una petición POST a tu URL con un cuerpo JSON que describe el evento.
  4. Tu servidor procesa el evento y responde con 2xx para confirmar la recepción.

Si no respondes rápido (normalmente en 5–30 segundos), el proveedor considera que la entrega falló y puede reintentar.

Por qué debes verificar los payloads firmados

Tu endpoint de webhook es de acceso público. Cualquiera que descubra la URL puede enviar eventos falsos. Sin verificación, un atacante podría falsificar un evento de "pago completado" y obtener productos gratis.

La solución: payloads firmados. El proveedor calcula una firma criptográfica sobre el cuerpo de la petición usando un secreto compartido (conocido solo por ti y por él) y la incluye en una cabecera. Tú recalculas la firma en tu lado y comparas.

Cómo funciona la firma HMAC

La mayoría de los proveedores usan HMAC-SHA256. Los pasos:

  1. El proveedor concatena el cuerpo crudo de la petición con un timestamp (para prevenir ataques de replay).
  2. El proveedor calcula HMAC-SHA256(secret, timestamp + '.' + body).
  3. El proveedor envía la firma en una cabecera, p. ej., X-Signature: t=1234567890,v1=abc123....
  4. Tú extraes el timestamp y la firma, recalculas el HMAC usando tu copia del secreto y comparas usando una función de comparación en tiempo constante.

Aquí tienes un ejemplo mínimo en Node.js:

const crypto = require('crypto');

function verifySignature(req, secret) {
  const signatureHeader = req.headers['x-signature'];
  const [tPart, vPart] = signatureHeader.split(',');
  const timestamp = tPart.split('=')[1];
  const receivedSig = vPart.split('=')[1];

  const payload = timestamp + '.' + JSON.stringify(req.body);
  const expectedSig = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');

  // Comparación en tiempo constante para prevenir ataques de timing
  return crypto.timingSafeEqual(
    Buffer.from(receivedSig, 'hex'),
    Buffer.from(expectedSig, 'hex')
  );
}

Importante: usa siempre el cuerpo crudo de la petición para verificar la firma. Si tu framework parsea el JSON y lo vuelve a serializar, la secuencia de bytes puede diferir y la verificación fallará. Captura el cuerpo crudo antes de parsearlo.

Validación del timestamp

Comprueba que el timestamp sea reciente (p. ej., dentro de 5 minutos). Esto previene ataques de replay en los que un atacante captura una petición válida y la reenvía más tarde.

Lógica de reintentos: gestionar fallos con elegancia

Tu servidor podría estar caído, sobrecargado o sufrir un corte de red. Los proveedores gestionan esto reintentando las entregas fallidas. Pero debes diseñar tu endpoint para manejar los reintentos de forma segura.

Backoff exponencial con jitter

Los proveedores suelen reintentar con retardos crecientes: 1 minuto, 5 minutos, 30 minutos, 2 horas, etc. Esto se llama backoff exponencial. Algunos añaden jitter aleatorio para evitar el problema del thundering herd.

Desde tu lado, no controlas el calendario de reintentos del proveedor, pero deberías:

Idempotencia: la clave para reintentos seguros

Los reintentos implican que podrías recibir el mismo evento varias veces. Tu procesamiento debe ser idempotente: procesar el mismo evento dos veces debe tener el mismo efecto que procesarlo una vez.

Enfoque común: incluir un ID de evento único en el payload. Antes de procesar, comprueba si ya has gestionado ese ID. Si es así, devuelve 200 y omite el procesamiento.

async function handleWebhook(event) {
  const existing = await db.events.findOne({ id: event.id });
  if (existing) {
    return { status: 200, body: 'Already processed' };
  }

  await db.events.insert({ id: event.id, processedAt: new Date() });
  await processEvent(event);
  return { status: 200, body: 'OK' };
}

Este patrón asegura que, aunque el proveedor envíe el evento tres veces, tú solo actúas una vez.

Comparación: polling vs webhooks

Aspecto Polling Webhooks
Latencia Depende del intervalo (p. ej., 30 s) Casi en tiempo real
Carga del servidor Constante, incluso sin eventos Solo cuando ocurren eventos
Complejidad Fácil de implementar Requiere endpoint, verificación y reintentos
Fiabilidad Tú controlas los reintentos Depende de la política de reintentos del proveedor

Los webhooks ganan para actualizaciones en tiempo real, pero el polling puede ser un respaldo para eventos críticos si te preocupa perder entregas.

Checklist de buenas prácticas

Depurar problemas con webhooks

Cuando los webhooks fallan, el primer paso es inspeccionar la petición cruda. Problemas comunes:

Si estás depurando tráfico HTTP, herramientas como Nginx Log Analyzer pueden ayudarte a detectar patrones en fallos de peticiones y códigos de respuesta.

FAQ

¿Qué es un secreto de webhook y cómo lo almaceno?

El secreto es una clave compartida usada para firmar payloads. Guárdalo en variables de entorno o en un gestor de secretos; nunca en el código ni en el control de versiones. Rótalo periódicamente si el proveedor lo permite.

¿Cuántas veces reintentará un proveedor un webhook fallido?

Varía según el proveedor. Algunos reintentan durante 24 horas con backoff exponencial, otros durante 3 días. Consulta la documentación de tu proveedor para conocer el calendario exacto y el número máximo de intentos.

¿Puedo usar webhooks con funciones serverless?

Sí, pero ten en cuenta los límites de tiempo de ejecución. Confirma el webhook rápidamente (devuelve 200) y descarga el procesamiento pesado a una cola o función en segundo plano para evitar timeouts.

¿Listo para construir integraciones de webhook robustas? Empieza verificando las firmas e implementando idempotencia: estos dos pasos te ahorrarán horas de depuración y evitarán incidentes de seguridad costosos.