Contratto di consegna
| Parte | Contratto |
|---|---|
| Eventi | booking.confirmed, booking.cancelled, booking.reschedule_requested, booking.rescheduled, booking.updated |
| Header | X-Fenix-Event-Id, X-Fenix-Delivery-Id, X-Fenix-Timestamp, X-Fenix-Signature e Idempotency-Key |
| Nomi di compatibilità | X-Fenix-*, data-fenix-* e FENIX_* sono nomi di protocollo legacy stabili. Mantienili invariati nelle integrazioni esistenti. |
| Firma | v1=<HMAC-SHA256(timestamp + "." + raw_body)> usando il segreto whsec_ monouso |
| Validità temporale | Accetta solo una finestra timestamp di cinque minuti e rifiuta version/type/reference non validi. |
| Tentativi | Cinque tentativi limitati con attese di 1m, 5m, 30m e 2h. 2xx completa; 3xx viene rifiutato e non seguito. |
Flusso minimo di verifica
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 });Politica di elaborazione del destinatario
- Verifica la firma sul corpo grezzo non modificato prima di elaborare JSON. Convalida formato v1, finestra timestamp e schema evento atteso.
- Salva atomicamente X-Fenix-Event-Id prima degli effetti. X-Fenix-Delivery-Id identifica un tentativo; Idempotency-Key ripete l’id evento per la deduplicazione.
- Restituisci 2xx solo quando l’evento è gestito in sicurezza. I redirect sono rifiutati; gli errori ricevono al massimo cinque tentativi con attese di 1m, 5m, 30m e 2h.
- Tratta i payload webhook come dati operativi. Escludili dalla telemetria browser e oscurali in log, ticket e rapporti.
Sicurezza dell’endpoint
- Registra solo un hostname HTTPS pubblico. URL con credenziali, frammenti, query string o porte personalizzate vengono rifiutati.
- La piattaforma risolve e convalida il DNS prima della configurazione e a ogni consegna; blocca destinazioni loopback, private, link-local, riservate e miste.
- Il segreto di firma whsec_ monouso è mostrato al titolare solo alla creazione o rotazione. Conservalo in un secret manager server, ruotalo dopo un’esposizione e non inviarlo al supporto.
- Sospendi l’endpoint durante un incidente. Ripeti solo consegne fallite o saltate quando il destinatario può deduplicare l’id evento originale.
Feny