Контракт доставки
| Частина | Контракт |
|---|---|
| Події | booking.confirmed, booking.cancelled, booking.reschedule_requested, booking.rescheduled, booking.updated |
| Заголовки | X-Fenix-Event-Id, X-Fenix-Delivery-Id, X-Fenix-Timestamp, X-Fenix-Signature та Idempotency-Key |
| Назви сумісності | X-Fenix-*, data-fenix-* і FENIX_* є стабільними успадкованими назвами протоколу. Зберігайте їх без змін у наявних інтеграціях. |
| Підпис | v1=<HMAC-SHA256(timestamp + "." + raw_body)> з одноразовим секретом whsec_ |
| Актуальність | Приймайте лише п’ятихвилинне вікно timestamp і відхиляйте некоректні version/type/reference конверта. |
| Повторні спроби | П’ять обмежених спроб із затримками 1m, 5m, 30m і 2h. 2xx завершує доставку; 3xx відхиляється без переходу. |
Мінімальний потік перевірки
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 });Політика обробки отримувача
- Перевірте підпис за незміненим необробленим тілом до розбору JSON. Перевірте формат підпису v1, вікно timestamp та очікувану схему події.
- Атомарно збережіть X-Fenix-Event-Id до побічних ефектів. X-Fenix-Delivery-Id визначає спробу; Idempotency-Key повторює id події для усунення дублікатів.
- Повертайте 2xx лише після безпечної обробки події. Redirect відхиляється; невдала доставка отримує щонайбільше п’ять спроб із затримками 1m, 5m, 30m і 2h.
- Вважайте payload webhook операційними даними. Не передавайте їх у телеметрію браузера й приховуйте в логах, заявках та звітах.
Безпека endpoint
- Реєструйте лише публічний HTTPS hostname. URL з обліковими даними, fragment, query string або нестандартним портом відхиляються.
- Платформа визначає й перевіряє DNS перед налаштуванням і кожною доставкою; loopback, приватні, link-local, зарезервовані та змішані адреси блокуються.
- Одноразовий секрет підпису whsec_ показується власнику лише під час створення або ротації. Зберігайте його в серверному сховищі, ротуйте після витоку й не надсилайте підтримці.
- Призупиніть endpoint під час інциденту. Повторюйте лише невдалі або пропущені доставки після готовності отримувача усувати дублікати за початковим id події.
Feny