Webhooks explicados: firmas y reintentos

Backend2026-10-14TryQuickToolBox

Por qué los webhooks son complicados

Los webhooks son una idea simple: en lugar de hacer polling a una API para detectar cambios, el proveedor envía eventos a tu endpoint mediante HTTP POST. Pero en la práctica, los webhooks introducen una serie de desafíos que complican a muchos desarrolladores: verificar que la solicitud realmente provino del proveedor, manejar entregas duplicadas y lidiar con fallos de red. Si ignoras esto, te arriesgas a brechas de seguridad, corrupción de datos y usuarios enojados.

Este artículo recorre los dos aspectos más críticos de los webhooks de nivel producción: payloads firmados y reintentos. Veremos cómo verificar firmas, cómo implementar reintentos con retroceso exponencial y cómo hacer que tu procesamiento sea idempotente.

Payloads firmados: demostrar autenticidad

Cualquiera puede enviar un HTTP POST a tu endpoint. Para asegurar que la solicitud provino de tu proveedor, este firma el payload con una clave secreta y envía la firma en un header. Tu tarea es recalcular la firma y compararla.

La mayoría de los proveedores usan HMAC-SHA256. La firma normalmente se calcula sobre el cuerpo crudo de la solicitud (a veces con un timestamp) y se codifica en hexadecimal. Por ejemplo, Stripe usa un header como Stripe-Signature que contiene un timestamp y una firma.

Cómo funciona HMAC

HMAC (Hash-based Message Authentication Code) combina una clave secreta con una función hash. Dada la misma clave y el mismo mensaje, la salida es determinista. Sin la clave, un atacante no puede falsificar una firma válida.

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

const crypto = require('crypto');

function verifySignature(payload, signature, secret) {
  const expected = crypto
    .createHmac('sha256', secret)
    .update(payload)
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

En Python es igualmente sencillo:

import hmac
import hashlib

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

Buenas prácticas para la verificación de firmas

Manejo de timestamps y ataques de repetición

Muchos proveedores incluyen un timestamp en el header de firma. Por ejemplo, el header podría ser t=1614556800,v1=abc123.... Deberías:

  1. Extraer el timestamp y la firma.
  2. Verificar que el timestamp esté dentro de una ventana aceptable (por ejemplo, 5 minutos).
  3. Recalcular la firma usando el timestamp y el payload.
  4. Comparar las firmas de forma segura.

Esto evita que un atacante reenvíe una solicitud válida antigua.

Reintentos: manejar fallos con elegancia

Los webhooks se entregan a través de internet público, así que los fallos son inevitables. Tu endpoint podría estar caído, lento o devolver un error 500. Un proveedor robusto reintentará las entregas fallidas, pero también necesitas manejar reintentos de tu lado si eres el proveedor.

Retroceso exponencial con jitter

Al reintentar, evita golpear el endpoint con reintentos inmediatos. Usa retroceso exponencial: espera más después de cada fallo. Agrega jitter (aleatoriedad) para evitar problemas de estampida cuando muchos webhooks fallan simultáneamente.

Un calendario de reintentos típico podría verse así:

IntentoRetraso (aprox.)
1Inmediato
230 segundos
35 minutos
430 minutos
52 horas

Después de un número máximo de intentos, marca la entrega como fallida y alerta.

Idempotencia: la clave para reintentos seguros

Debido a que los reintentos pueden causar entregas duplicadas, tu manejador de webhooks debe ser idempotente. Esto significa que procesar el mismo evento varias veces debe tener el mismo efecto que procesarlo una vez.

Cómo lograr idempotencia:

Implementar reintentos en el lado del proveedor

Si estás construyendo un sistema de webhooks, necesitas un mecanismo de entrega fiable. Aquí hay un enfoque simplificado:

  1. Cuando ocurre un evento, encola un trabajo de entrega con el payload y la URL de destino.
  2. Un worker toma el trabajo y envía el HTTP POST con un timeout (por ejemplo, 10 segundos).
  3. Si el estado de la respuesta no es 2xx, programa un reintento con retroceso exponencial.
  4. Registra los intentos y abandona después de un máximo (por ejemplo, 5 intentos).
  5. Registra los fallos y ofrece una forma de que los usuarios puedan reenviar manualmente.

Usa una cola persistente (por ejemplo, Redis, RabbitMQ o una tabla de base de datos) para que los reintentos sobrevivan a los reinicios.

Poniéndolo todo junto: checklist para un receptor de webhooks

Al construir un receptor de webhooks, sigue esta checklist:

Preguntas frecuentes

¿Qué es un payload de webhook firmado?

Un payload firmado incluye una firma criptográfica (normalmente HMAC-SHA256) generada con un secreto compartido. El receptor recalcula la firma para verificar que la solicitud provino del remitente esperado y no fue manipulada.

¿Por qué los webhooks necesitan reintentos?

Problemas de red, caídas del servidor o errores temporales pueden hacer que una entrega de webhook falle. Los reintentos con retroceso exponencial aseguran que el evento se entregue eventualmente sin sobrecargar al receptor.

¿Cómo evito el procesamiento duplicado de webhooks?

Usa idempotencia: incluye un ID de evento único en el payload, almacena los IDs procesados y diseña tus operaciones para que sean idempotentes (por ejemplo, upserts). De esta forma, incluso si un webhook se entrega varias veces, el efecto es el mismo.

Si necesitas inspeccionar o depurar payloads de webhooks, el JSON Formatter puede ayudarte a formatear y validar datos JSON rápidamente.