Contrato de entrega
| Parte | Contrato |
|---|---|
| Eventos | booking.confirmed, booking.cancelled, booking.reschedule_requested, booking.rescheduled, booking.updated |
| Cabeceras | X-Fenix-Event-Id, X-Fenix-Delivery-Id, X-Fenix-Timestamp, X-Fenix-Signature e Idempotency-Key |
| Nombres de compatibilidad | X-Fenix-*, data-fenix-* y FENIX_* son nombres de protocolo heredados y estables. Consérvalos exactamente en las integraciones existentes. |
| Firma | v1=<HMAC-SHA256(timestamp + "." + raw_body)> usando el secreto whsec_ de un solo uso |
| Vigencia | Acepta solo una ventana temporal de cinco minutos y rechaza versiones, tipos o referencias mal formados. |
| Reintentos | Cinco intentos limitados con esperas de 1m, 5m, 30m y 2h. 2xx completa la entrega; 3xx se rechaza y no se sigue. |
Flujo mínimo de verificación
js
import { createHmac, timingSafeEqual } from 'node:crypto';
const rawBody = await request.text();
const timestamp = request.headers.get('x-fenix-timestamp') || '';
const signature = request.headers.get('x-fenix-signature') || '';
const ageSeconds = Math.abs(Date.now() - Number(timestamp) * 1000) / 1000;
const expected = 'v1=' + createHmac('sha256', process.env.FENIX_WEBHOOK_SECRET)
.update(timestamp + '.' + rawBody)
.digest('hex');
const received = Buffer.from(signature);
const candidate = Buffer.from(expected);
if (!Number.isInteger(Number(timestamp)) || ageSeconds > 300 ||
received.length !== candidate.length || !timingSafeEqual(received, candidate)) {
return new Response(null, { status: 400 });
}
const event = JSON.parse(rawBody);
if (await events.has(event.id)) return new Response(null, { status: 204 });
await database.transaction(async () => {
await events.insert({ id: event.id });
await applyBookingEvent(event);
});
return new Response(null, { status: 204 });Política de procesamiento del receptor
- Verifica la firma con el cuerpo original sin modificar antes de analizar JSON. Valida el formato de firma v1, la ventana temporal y el esquema esperado.
- Guarda X-Fenix-Event-Id de forma atómica antes de los efectos. X-Fenix-Delivery-Id identifica un intento; Idempotency-Key repite el id del evento para eliminar duplicados.
- Devuelve 2xx solo cuando el evento esté procesado con seguridad. Los redirects se rechazan; los fallos reciben como máximo cinco intentos con esperas de 1m, 5m, 30m y 2h.
- Trata los payloads webhook como datos operativos. Exclúyelos de la telemetría del navegador y ocúltalos en logs, tickets e informes.
Seguridad del endpoint
- Registra solo un hostname HTTPS público. Se rechazan URLs con credenciales, fragmentos, query strings o puertos personalizados.
- La plataforma resuelve y valida DNS antes de configurar y en cada entrega; bloquea destinos loopback, privados, link-local, reservados y mixtos.
- El secreto whsec_ de un solo uso se muestra al propietario solo al crearlo o rotarlo. Guárdalo en un gestor del servidor, rótalo tras una exposición y nunca lo envíes a soporte.
- Pausa el endpoint durante un incidente. Repite solo entregas fallidas u omitidas cuando el receptor pueda eliminar duplicados por el id original.
Feny