Вихідні вебхуки
З вихідним вебхуком exoCortex надсилає запит POST до твоєї системи щоразу, коли стається подія, на яку ти підписався: створено тікет, змінено статус, спливає SLA. Так твоя система реагує одразу, без періодичного опитування API.
Як створити підписку
Section titled “Як створити підписку”- В exoCortex Core відкрий Integrations → Webhooks. Потрібна адміністративна роль.
- Вкажи URL, який отримуватиме події. Він має починатися з
https://. - Вибери події.
*означає всі події. - Вибери формат:
json: стандартний формат exoCortex, описаний на цій сторінці;datadog,pagerduty,slack-block: рідний формат цих сервісів, який слід використовувати, коли URL належить їм.
- Якщо цього вимагає твій шлюз, додай фіксовані заголовки, наприклад заголовок автентифікації.
- Збережи. Якщо ти не вказав секрет підпису, exoCortex згенерує його (
whsec_…) і покаже лише один раз: збережи його, він потрібен для перевірки підписів.
Тестовою дією ти надсилаєш у підписку подію webhook.test і перевіряєш, що твоя система її отримує.
| Подія | Коли надходить |
|---|---|
ticket.created |
Створено тікет |
ticket.comment_added |
Додано коментар |
ticket.field_changed |
Змінилося поле |
ticket.assignee_changed |
Змінився виконавець |
ticket.status_changed |
Змінився статус |
ticket.priority_changed |
Змінився пріоритет |
ticket.sla_warning |
SLA ось-ось спливе |
ticket.sla_breach |
SLA сплив |
ticket.resolved |
Тікет вирішено |
ticket.closed |
Тікет закрито |
ticket.reopened |
Тікет повторно відкрито |
ticket.link_created |
Створено зв’язок між тікетами |
ticket.link_deleted |
Видалено зв’язок між тікетами |
routing.no_match |
Жодне правило маршрутизації не знайшло черги для тікета |
routing.manual_routed |
Оператор спрямував тікет вручну |
webhook.test |
Ти надіслав у підписку тестову подію |
Крім цих, можна підписатися за назвою на події правил сповіщень свого тенанта, наприклад approval_requested.
exoCortex може додавати нові типи подій: твоя система має ігнорувати ті, яких не знає.
Формат події
Section titled “Формат події”Кожна подія у форматі json має однакову структуру:
{ "id": "evt_8a1f4c2e9b7d4e3f8a6b5c4d3e2f1a00", "type": "ticket.status_changed", "created_at": "2026-11-01T09:31:47.120Z", "api_version": "1", "tenant_id": "ACME", "data": { "ticket": { "key": "ACME-1042", "summary": "Payment gateway returns HTTP 502", "status": "In Progress", "priority": "High", "issuetype": "IT-INC", "assignee": "jane.doe", "created": "2026-11-01T09:14:05.000Z", "updated": "2026-11-01T09:31:47.000Z" }, "actor": "jane.doe", "changes": { "from": "Open", "to": "In Progress" } }}idідентифікує подію і однаковий для всіх підписок, які її отримують.actor— користувач, який спричинив подію, абоsystem.changesмістить деталі події: наприклад, попередній і новий статус або текст коментаря.
До 30 червня 2027 року тіло містить також застарілі поля event, timestamp і source та копію полів із data на першому рівні. Нова система має читати лише описані вище поля.
Заголовки
Section titled “Заголовки”| Заголовок | Приклад | Значення |
|---|---|---|
X-Exo-Timestamp |
1793491200 |
Момент підпису в секундах Unix |
X-Exo-Signature |
v1=5257a869… |
Підпис події |
X-Exo-Event |
ticket.created |
Тип події, такий самий, як type |
X-Exo-Delivery |
dlv_0b9d… |
Унікальний ідентифікатор спроби доставки |
Як перевірити підпис
Section titled “Як перевірити підпис”Перш ніж довіряти події, перевір підпис:
- Читай тіло саме в тому вигляді, у якому воно надійшло, не розбираючи й не переписуючи JSON: інакше зміняться байти.
- Відхиляй запит, якщо
X-Exo-Timestampнемає або він відрізняється від твого годинника більш ніж на 300 секунд. Так ти блокуєш повторні запити. Синхронізуй годинник через NTP. - Обчисли HMAC-SHA256 від
timestamp + "." + bodyіз секретом підписки в шістнадцятковому вигляді малими літерами. - Порівняй результат за сталий час з кожним значенням
v1=уX-Exo-Signature. Під час ротації секрету заголовок може містити кілька значень, розділених комами.
Приклад на Node.js:
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verifyExoWebhook(rawBody, headers, secret, toleranceSec = 300) { const ts = headers['x-exo-timestamp']; const sigHeader = headers['x-exo-signature'] || ''; if (!ts || Math.abs(Date.now() / 1000 - Number(ts)) > toleranceSec) return false; const expected = Buffer.from(createHmac('sha256', secret).update(`${ts}.${rawBody}`).digest('hex')); return sigHeader .split(',') .map((s) => s.trim()) .filter((s) => s.startsWith('v1=')) .some((s) => { const got = Buffer.from(s.slice(3)); return got.length === expected.length && timingSafeEqual(got, expected); });}Приклад на Python:
import hashlib, hmac, time
def verify_exo_webhook(raw_body: bytes, headers, secret: str, tolerance_sec: int = 300) -> bool: ts = headers.get('X-Exo-Timestamp') if not ts or abs(time.time() - int(ts)) > tolerance_sec: return False expected = hmac.new(secret.encode(), f"{ts}.".encode() + raw_body, hashlib.sha256).hexdigest() sigs = [s.strip()[3:] for s in headers.get('X-Exo-Signature', '').split(',') if s.strip().startswith('v1=')] return any(hmac.compare_digest(s, expected) for s in sigs)Заголовок X-NH-Signature, який підписує лише тіло без позначки часу, застарів і буде вилучений 30 червня 2027 року.
Як відповідати й обробляти дублікати
Section titled “Як відповідати й обробляти дублікати”- Відповідай будь-яким кодом
2xxпротягом 5 секунд. Якщо потрібна тривала робота, постав її в чергу і відповідай одразу. - Доставку гарантовано щонайменше один раз: та сама подія може надійти більше ніж один раз. Використовуй
idабоX-Exo-Delivery, щоб розпізнавати дублікати, наприклад зберігаючи ідентифікатори, отримані за останні 5 хвилин. - Події можуть надходити не в тому порядку, у якому вони сталися: впорядковуй їх за
created_at.