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

📤 Исходящие вебхуки

Что это. Fixorix сам отправляет POST-запрос на ваш HTTPS-адрес, как только появляется событие подходящего типа. Запрос подписан, неудачные доставки повторяются, каждая попытка попадает в журнал.

Когда нужно. Когда ваша система должна реагировать на события платформы сразу, а не выяснять изменения регулярными запросами к REST API.

Завести подписку пока нельзя

Подписки на вебхуки будут настраиваться в дашборде, но раздела «Вебхуки» там пока нет — как он будет устроен, описано на странице Вебхуки на дашборде. Кроме того, продукт пока не публикует события для вебхуков, и каталога типов событий нет.

Ниже описано, как устроена доставка. По этому описанию можно заранее подготовить сервер, который будет принимать вебхуки.

Подписка на события​

Подписка — это связка «проект + адрес получателя + список типов событий».

ПолеОбязательноОписание
Адрес получателядаHTTPS-адрес, на который уходит POST. Максимум 2048 символов. Схема http отклоняется
Типы событийнетСписок типов, на которые подписан вебхук. Подписка без типов создаётся, но не получит ни одной доставки, пока типы не добавят. Формат каждого типа — [A-Za-z0-9][A-Za-z0-9._:@-]{0,127}

При создании подписки выдаётся секрет подписи — строка из 64 символов URL-safe base64. Им подписывается каждая доставка.

Правила​

  • Один адрес — одна подписка. Повторно добавить тот же адрес в проект нельзя: вернётся уже существующая подписка, дубль не появится, а секрет повторно не выдаётся.
  • Лимит — 10 подписок на проект.
  • Адрес после создания не меняется. Менять можно только набор типов: добавлять и снимать. Чтобы сменить адрес, подписку удаляют и заводят заново.
  • У каждой подписки свой набор типов. Одно событие может породить несколько доставок — по одной на каждую подписку проекта, подписанную на этот тип.

Адрес получателя не будет принят, если:

  • он начинается не с https://;
  • его не удаётся разобрать или в нём нет хоста;
  • имя хоста не резолвится;
  • хотя бы один IP-адрес хоста попадает в запрещённые диапазоны;
  • он длиннее 2048 символов.
Секрет показывается один раз

Секрет подписи выдаётся ровно один раз — при создании подписки. Посмотреть его позже нельзя. Потерянный секрет восстанавливается только обновлением — оно выдаёт новый.

Формат доставки​

ПараметрЗначениеЧто делает
МетодPOSTДругой метод не используется
Схематолько httpsДоставка по http невозможна
Content-Typeapplication/jsonТело всегда JSON
Теловесь конверт события целикомОтправляется весь конверт, а не только полезная нагрузка. JSON при этом нормализуется: порядок ключей и пробелы могут отличаться от исходного события. Подпись считается по фактически отправленному телу
Таймаут ответа3 секундыНе успели ответить — попытка считается неудачной
Таймаут соединения1 секундаСтолько же отводится на получение соединения из пула
РедиректыотключеныОтвет 3xx — терминальный отказ, переход по Location не выполняется
Cookieне хранятсяСессию на стороне получателя держать не получится
Максимальный размер262 144 байта (256 КиБ)Событие крупнее не отправляется вовсе
User-AgentFixorix-Webhook/1.0По нему доставку удобно отличать в ваших логах
Успехлюбой ответ 2xxВсё остальное — неудача

Конверт события​

В теле приходит конверт целиком:

{
"id": "evt-20260824-000123",
"projectId": "1f2e3d4c-5b6a-7890-abcd-ef1234567890",
"type": "ticket.created",
"createdAt": "2026-08-24T10:15:30Z"
}

Поля id, projectId и type в конверте обязательны, createdAt — нет. Подробнее — на странице Типы событий.

Заголовки доставки​

ЗаголовокЧто содержит
X-Webhook-IdИдентификатор события. По нему получатель дедуплицирует повторные доставки
X-Webhook-TypeТип события
X-Webhook-Project-IdUUID проекта
X-Webhook-TimestampМетка времени в секундах epoch. Именно она вошла в подпись. Формируется заново при каждой попытке, поэтому повторная доставка приходит со свежей меткой. Время возникновения события — в поле createdAt конверта, если отправитель его задал
X-SignatureHMAC-SHA256 текущим действующим секретом, hex в нижнем регистре
X-Signature-SecondaryВторая подпись прежним секретом. Приходит только в окне обновления секрета
User-AgentFixorix-Webhook/1.0

Проверка подписи​

Подписывается строка из метки времени, точки и сырого тела запроса:

подпись = HMAC_SHA256(секрет, X-Webhook-Timestamp + "." + сырое тело)

Правила:

  • алгоритм — HMAC-SHA256, ключ — секрет подписи в UTF-8;
  • результат — hex в нижнем регистре, без префикса алгоритма;
  • тело берётся сырым, до разбора JSON: любое переформатирование ломает подпись;
  • заголовки в подпись не входят — ни идентификатор события, ни тип, ни идентификатор проекта. Подписываются только метка времени и тело.

Пример проверки​

const crypto = require("crypto");

function safeEqual(a, b) {
const bufA = Buffer.from(a, "utf8");
const bufB = Buffer.from(b, "utf8");
if (bufA.length !== bufB.length) return false;
return crypto.timingSafeEqual(bufA, bufB);
}

// rawBody — тело запроса строкой или Buffer, ДО разбора JSON.
// Подпись = hex(HMAC-SHA256(секрет, X-Webhook-Timestamp + "." + сырое тело)), без префикса.
function verify(rawBody, headers, secret) {
const timestamp = headers["x-webhook-timestamp"];
if (!timestamp) return false;

// допуск по времени выбираете вы сами, здесь — 5 минут
const age = Math.abs(Math.floor(Date.now() / 1000) - Number(timestamp));
if (!Number.isFinite(age) || age > 300) return false;

const expected = crypto
.createHmac("sha256", secret)
.update(timestamp + "." + rawBody.toString("utf8"), "utf8")
.digest("hex");

// в окне ротации секрета подходящей может оказаться любая из двух подписей
return safeEqual(expected, headers["x-signature"] || "")
|| safeEqual(expected, headers["x-signature-secondary"] || "");
}

Что должен делать получатель​

ПравилоЗачем
Сравнивать подписи константным временемОбычное сравнение строк выдаёт длину совпадающего префикса
Отклонять слишком старую X-Webhook-TimestampЗащита от повторной отправки перехваченного запроса
Дедуплицировать по X-Webhook-IdДоставка гарантируется «хотя бы один раз»: один и тот же идентификатор может прийти дважды
В окне обновления секрета принимать любую из двух подписейПозволяет обновить секрет без потери валидных доставок
Отвечать быстро, работу делать асинхронноНа ответ отводится 3 секунды
Коротко

Возьмите сырое тело, приклейте к нему спереди метку времени и точку, посчитайте HMAC-SHA256 своим секретом и сравните с X-Signature — или с X-Signature-Secondary, если она пришла.

Обновление секрета​

Обновление выдаёт новый секрет подписи. Порядок такой:

  1. Прежний запасной секрет, если он был, удаляется.
  2. Действующий секрет становится запасным и получает срок жизни — 86 400 секунд, то есть сутки.
  3. Создаётся и выдаётся новый действующий секрет.

Сутки после обновления каждая доставка подписывается дважды: X-Signature — новым секретом, X-Signature-Secondary — прежним. За это окно получателю нужно подставить новый секрет у себя. По истечении окна прежний секрет перестаёт использоваться, и приходит только одна подпись.

Одновременно действующих секретов не бывает больше двух.

Статусы доставки​

СтатусЧто означает
PENDING — в очередиЖдёт отправки или следующей попытки. В этом же статусе копятся доставки, пока подписка на паузе
IN_PROGRESS — отправляетсяПопытка выполняется прямо сейчас
DELIVERED — доставленоПолучен ответ 2xx
FAILED — отказТерминальный отказ, повторов не будет
EXHAUSTED — исчерпанаЗакончились все попытки

Доставка, зависшая в статусе IN_PROGRESS дольше минуты, автоматически возвращается в очередь.

Повторные попытки​

Максимум — 10 попыток на одну доставку.

ПопыткаЧерез сколько после предыдущей
1сразу
210 секунд
330 секунд
41 минута
52 минуты
65 минут
710 минут
830 минут
91 час
102 часа

Если получатель недоступен всё это время, последняя попытка приходится примерно на 3 часа 49 минут после первой.

Когда повтор будет, а когда нет​

Ответ получателяЧто происходит
Любой 2xxДоставлено, повторов нет
408, 409, 425, 429, 500, 502, 503, 504Повтор по расписанию
Сетевой сбой: ответа нет вовсеПовтор по расписанию
400, 401, 403, 404, 422Терминальный отказ сразу
Любой другой код, которого нет в списке повторяемых, — включая 3xxТерминальный отказ сразу
Автоматического отключения подписки нет

Серия провалов подписку не выключает. После 10 неудачных попыток конкретная доставка получает статус EXHAUSTED («Исчерпана»), а сама подписка остаётся активной и продолжает получать новые доставки. Ни счётчика подряд идущих ошибок, ни порога, ни автопаузы нет — приостановить подписку можно только вручную.

Три случая дают терминальный отказ немедленно, без единого повтора: адрес получателя не HTTPS, событие крупнее 256 КиБ, у подписки нет действующего секрета.

Причины сбоя​

Причина записывается в журнал попыток:

ПричинаЧто означает
okУспешный ответ
4xxОтвет из диапазона 4xx; сюда же попадает превышение размера события
5xxОтвет из диапазона 5xx
3xxРедирект — не выполняется, считается отказом
timeoutПолучатель не ответил за отведённое время
dnsИмя хоста не разрешилось
blockedВсе адреса хоста оказались в запрещённых диапазонах или схема не HTTPS
connectНе удалось установить соединение
tlsОшибка TLS-рукопожатия или сертификата
ioОбрыв или ошибка чтения-записи

Пауза и возобновление​

Подписку можно поставить на паузу и потом возобновить.

На паузе доставки копятся, а не пропадают

Приостановленная подписка продолжает набирать доставки: события по-прежнему становятся строками в очереди со статусом PENDING, просто отправка не выполняется. После возобновления накопленная очередь начнёт доставляться. Если получатель лежит долго, к моменту возобновления очередь может оказаться большой.

Тестовая отправка​

Тестовое событие проверяет связку целиком, не дожидаясь настоящих событий.

  • Тип события по умолчанию — webhook.test.
  • Нагрузка кладётся в поле body конверта; по умолчанию — {"test": true}. Пример тела: {"id":"test-…","projectId":"…","type":"webhook.test","createdAt":"…","body":{"test":true}}.
  • Идентификатор события формируется как test- и случайный UUID.
  • Тестовая отправка работает и для подписки на паузе.
  • Отправка идёт тем же путём, что и настоящие доставки: те же заголовки, та же подпись, те же таймауты и те же проверки адреса. Отличие одно — ровно одна попытка, без повторов.
  • Ответ 2xx даёт статус DELIVERED, любой другой — FAILED.
  • Доставка сохраняется в журнале с пометкой «тестовая» и видна в общем списке.
  • Результат показывается сразу: успех или нет, статус, причина и задержка в миллисекундах.
  • Если у подписки нет действующего секрета, тестовая отправка не выполняется.

Журнал доставок и статистика​

Журнал показывает доставки подписки от новых к старым, с фильтрами по периоду и по статусу.

Вместе со списком видна статистика по всей подписке — без учёта выбранного периода: всего доставок, доставлено, отказов, исчерпано, в очереди, отправляется, всего попыток.

Журнал попыток​

По каждой доставке виден полный список её HTTP-попыток:

Что видноЧто означает
Номер попыткиНумерация с единицы
Код ответаПусто, если ответа не было вообще
ПричинаОдно из значений таблицы причин сбоя
ЗадержкаСколько миллисекунд заняла попытка
IP получателяАдрес, с которым соединение действительно было установлено
Срез ответаНачало тела ответа получателя, максимум 512 символов
ВремяКогда попытка выполнялась
Журнал хранится 30 дней

Завершённые доставки — доставленные, отказавшие и исчерпанные — вместе со всеми их попытками удаляются через 30 дней. Очистка идёт раз в сутки. Незавершённые доставки не удаляются никогда.

Учитывайте это при работе с персональными данными: в журнале хранится тело события и фрагмент ответа вашего сервера.

Какие адреса получателя запрещены​

Адрес проверяется дважды: при создании подписки и заново в момент каждого подключения. Вторая проверка закрывает подмену адреса после создания — хост, переехавший на внутренний IP, всё равно не получит соединения.

Запрещены:

  • любая схема, кроме https;
  • адрес «любой» — 0.0.0.0 и ::, а также сеть 0.0.0.0/8;
  • loopback — 127.0.0.0/8 и ::1;
  • link-local — 169.254.0.0/16, включая адрес облачных метаданных 169.254.169.254, и fe80::/10;
  • приватные сети — 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16;
  • сеть операторского NAT — 100.64.0.0/10;
  • IPv6 ULA — fc00::/7;
  • multicast и широковещательный 255.255.255.255.

Адреса вида ::ffff:a.b.c.d разворачиваются и проверяются по полному набору правил; незнакомое семейство адресов запрещается по умолчанию.

Разница между двумя проверками важна: при создании подписки адрес отклоняется, если запрещён хотя бы один его IP; при доставке запрещённые адреса просто отбрасываются, и соединение идёт на оставшиеся — а если не осталось ни одного, попытка падает с причиной blocked.

Ограничения​

  • Не больше 10 подписок на проект.
  • Только HTTPS-адреса получателя, максимум 2048 символов.
  • Событие крупнее 256 КиБ не отправляется.
  • На ответ получателя — 3 секунды, редиректы не выполняются.
  • До 10 попыток на доставку; автоматического отключения подписки при серии провалов нет.
  • Адрес после создания не меняется — только набор типов.
  • Журнал доставок хранится 30 дней.
  • Каталога типов событий продукта нет, события для вебхуков пока не публикуются.
  • Подписка сравнивает тип события целиком: шаблонов и фильтров по содержимому события нет.

→ Вебхуки · Типы событий · Входящие вебхуки · Вебхуки на дашборде · REST API