Перейти к содержимому

Подписанная доставка

Сначала проверьте. Устраните дубликаты до побочных эффектов.

Webhooks являются уведомлениями в рамках приложения, а не источником доступа к tenant. Получатель должен аутентифицировать необработанное тело запроса, проверить актуальность и атомарно сохранить id события.

Контракт доставки

ЧастьКонтракт
События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 события.
Webhooks | Feny для разработчиков