Ir al contenido

Entrega firmada

Verifica primero. Elimina duplicados antes de los efectos.

Los webhooks son notificaciones de la aplicación, no una fuente de acceso al tenant. El receptor debe autenticar el cuerpo original, comprobar su vigencia y guardar el id del evento de forma atómica.

Contrato de entrega

ParteContrato
Eventosbooking.confirmed, booking.cancelled, booking.reschedule_requested, booking.rescheduled, booking.updated
CabecerasX-Fenix-Event-Id, X-Fenix-Delivery-Id, X-Fenix-Timestamp, X-Fenix-Signature e Idempotency-Key
Nombres de compatibilidadX-Fenix-*, data-fenix-* y FENIX_* son nombres de protocolo heredados y estables. Consérvalos exactamente en las integraciones existentes.
Firmav1=<HMAC-SHA256(timestamp + "." + raw_body)> usando el secreto whsec_ de un solo uso
VigenciaAcepta solo una ventana temporal de cinco minutos y rechaza versiones, tipos o referencias mal formados.
ReintentosCinco 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.
Webhooks | Desarrolladores Feny