Consenty
V-28 Webhooks

Webhooks salientes

Consenty envia webhooks firmados cuando ocurren eventos en tus canales. Cada entrega incluye un payload minimo, una firma HMAC SHA-256 y los headers necesarios para que tu receptor verifique autenticidad antes de actuar.

Eventos que disparan webhooks

Preferencias concedidas

preference.granted

Preferencias rechazadas

preference.denied

Preferencias retiradas

preference.withdrawn

Nueva version de aviso publicada

notice.published

Exportacion disponible

export.completed

Entrega webhook agotada (dead-letter)

webhook.delivery_failed

Configuracion del destino

Crea un destino desde /integrations/webhooks (vista V-25 / V-26). El panel valida la URL contra SSRF, exige HTTPS, y te entrega un secreto (whsec_...) que solo se muestra una vez. Guardalo en el vault de tu receptor.

Validacion automatica
Antes de activar el destino, el panel envia un evento sintetico firmado y mide la latencia. Solo lo marca como activo si la respuesta es 2xx.

Verificacion de firma

La firma sigue el patron sha256=<hex> donde hex = HMAC_SHA256(secret, "<timestamp>.<rawBody>"). Valida siempre:

  1. 1. Que el prefijo sea sha256=.
  2. 2. Que el timestamp caiga en una ventana razonable (recomendado: 5 minutos).
  3. 3. Que crypto.timingSafeEqual compare los buffers en tiempo constante.
verify.ts
ts
import { createHmac, timingSafeEqual } from 'node:crypto'

function verifyWebhook({
  secret,
  signature,
  timestamp,
  rawBody,
  windowSec = 300
}) {
  const expected = createHmac('sha256', secret)
    .update(`${timestamp}.${rawBody}`)
    .digest('hex')
  const providedHex = signature.startsWith('sha256=')
    ? signature.slice(7)
    : signature
  const a = Buffer.from(expected, 'hex')
  const b = Buffer.from(providedHex, 'hex')
  if (a.length !== b.length) return false
  const ts = Number.parseInt(timestamp, 10)
  if (Math.abs(Math.floor(Date.now() / 1000) - ts) > windowSec) return false
  return timingSafeEqual(a, b)
}

Headers de cada entrega

HeaderUso
X-Consenty-Timestamp Unix epoch en segundos. Combinado con el cuerpo firma la entrega.
X-Consenty-Signature Firma HMAC SHA-256 con prefijo sha256=.
X-Consenty-Event-Id Identificador UNICO del evento. Usalo como clave de deduplicacion.
X-Consenty-Event-Type Tipo de evento, p.ej. preference.granted.
X-Consenty-Delivery-Id Identificador de la entrega (puede repetirse entre reintentos).
X-Consenty-Request-IdTrazabilidad de la operacion original en el API.

Reintentos y dead-letter

  • 8 intentos en total, con backoff exponencial 30s -> 64m (cap).
  • 2xx marca la entrega como delivered. 4xx (excepto 408/429) NO se reintenta — la peticion es invalida.
  • Al agotar los 8 intentos, el estado pasa a dead_letter. Un Owner puede reenviarla manualmente desde la vista de entregas.
  • El panel conserva hasta 32 intentos por entrega con su responseStatus, responseExcerpt y errorMessage.

Idempotencia

X-Consenty-Event-Id es UNICO por receptor. Si Consenty reintenta una entrega (por timeout, 5xx, etc.), el eventId se mantiene. Tu receptor debe deduplicar por ese identificador — tratar dos entregas con el mismo eventId como una sola transaccion.

Consumidor de referencia

En examples/webhook-consumer/ dejamos un receptor Node.js minimo (@consenty/webhook-consumer-example) con /webhook, /health, /ready y deduplicacion en memoria. Sirve como base para tus adaptaciones.

terminal
bash
# Opcion 1: filtro de pnpm (recomendado)
pnpm --filter @consenty/webhook-consumer-example dev

# Opcion 2: entrar al paquete directamente
cd examples/webhook-consumer
pnpm dev
Lee el README del consumidor
El README del paquete cubre el smoke test E2E, las variables WEBHOOK_SECRET y WEBHOOK_TS_WINDOW_SEC, y los limites conocidos de la deduplicacion in-memory.