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.grantedPreferencias rechazadas
preference.deniedPreferencias retiradas
preference.withdrawnNueva version de aviso publicada
notice.publishedExportacion disponible
export.completedEntrega webhook agotada (dead-letter)
webhook.delivery_failedConfiguracion 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.
Verificacion de firma
La firma sigue el patron sha256=<hex> donde hex = HMAC_SHA256(secret, "<timestamp>.<rawBody>"). Valida siempre:
- 1. Que el prefijo sea
sha256=. - 2. Que el timestamp caiga en una ventana razonable (recomendado: 5 minutos).
- 3. Que
crypto.timingSafeEqualcompare los buffers en tiempo constante.
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
| Header | Uso |
|---|---|
| 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-Id | Trazabilidad de la operacion original en el API. |
Reintentos y dead-letter
- 8 intentos en total, con backoff exponencial 30s -> 64m (cap).
2xxmarca la entrega comodelivered.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,responseExcerptyerrorMessage.
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.
# 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