Вебхуки
Обмін подіями з іншими системами в обидва боки: підпис і повтори вихідних, ключ і формат тіла для вхідних.
Що це
Вебхук — це спосіб для іншої системи дізнаватись про те, що сталось у Systemer, не питаючи нас про це щохвилини. Ви вказуєте адресу й перелік подій; щойно одна з них настає, ми надсилаємо на цю адресу підписаний POST із JSON.
Звідси інтегрується будь-що, що вміє приймати HTTP: сценарій у n8n чи Make, чужа CRM, бот у чаті команди, власний сервіс. Підписка налаштовується в Налаштування → Вебхуки й потребує права керувати інтеграціями.
Підписка на «усі події» не підтримується навмисно. Перелік завжди явний: поява нової події в продукті не має сама почати відправляти дані в систему, власник якої про цю подію не знає.
Як підключити
- Налаштування → Вебхуки → «Нова адреса». Адреса приймача має бути на https.
- Оберіть події. Надсилатимуться лише вони.
- Збережіть секрет підпису. Він показується один раз: у нас він зберігається в сховищі секретів і не читається назад.
- Натисніть «Надіслати тест» — у журналі доставок зʼявиться подія webhook.ping із відповіддю вашого сервера.
Якщо секрет загубився, створіть новий кнопкою «Новий секрет»: попередній лишається чинним ще добу, тому оновлення приймача не призводить до розриву.
Що приходить
Заголовки запиту несуть ідентифікатор події, її тип і підпис. Ідентифікатор належить події, а не спробі доставки: повтор тієї самої події приходить із тим самим значенням, і саме за ним приймач має відкидати дублі.
Заголовки
POST /hooks/systemer HTTP/1.1
Content-Type: application/json
Systemer-Event-Id: 5f0c6f1e-...
Systemer-Event-Type: deal.won
Systemer-Signature: t=1790000000,v1=5257a869e7bcd...Тіло
{
"id": "5f0c6f1e-...",
"type": "deal.won",
"created_at": "2026-10-01T10:00:00Z",
"organization_id": "0a9d...",
"data": {
"entity_type": "deal",
"entity_id": "7c31...",
"title": "Угоду виграно",
"summary": null,
"url": "https://www.systemer.app/ваша-організація/crm/deals/7c31...",
"actor_person_id": "3b12..."
}
}Поля перелічені поіменно, а не віддаються «як є» з нашої бази. Внутрішні службові дані — кому саме пішло сповіщення всередині вашої команди, які права для цього потрібні — назовні не виходять ніколи.
Як перевірити підпис
Підписується рядок «мітка часу, крапка, сире тіло запиту» алгоритмом HMAC-SHA256 із вашим секретом. Мітка часу входить у підпис, а не стоїть поруч: інакше перехоплений запит можна було б переграти пізніше, підмінивши лише її.
Node.js
import { createHmac, timingSafeEqual } from 'node:crypto';
export function verify(rawBody, header, secret) {
const parts = Object.fromEntries(header.split(',').map((p) => p.split('=')));
const timestamp = Number(parts.t);
// Запит, старший за пʼять хвилин, відхиляємо — захист від переграння.
if (Math.abs(Date.now() / 1000 - timestamp) > 300) return false;
const expected = createHmac('sha256', secret)
.update(`${timestamp}.${rawBody}`)
.digest('hex');
return header
.split(',')
.filter((p) => p.startsWith('v1='))
.some((p) => timingSafeEqual(Buffer.from(p.slice(3)), Buffer.from(expected)));
}- Перевіряйте підпис ДО того, як розбирати тіло: воно ще не ваше, поки підпис не зійшовся.
- Підписів у заголовку може бути два — це доба після зміни секрета. Приймач має вважати запит дійсним, якщо збігся будь-який.
- Відкидайте запити з міткою часу, старшою за пʼять хвилин.
- Будьте ідемпотентними за Systemer-Event-Id: повтор доставки — штатна ситуація, а не помилка.
Доставка й повтори
Ми чекаємо на відповідь 2xx протягом пʼяти секунд. Довга робота на боці приймача має відбуватись після відповіді, а не до неї — інакше доставка вважається невдалою, хоча подію вже прийнято.
- Мережева помилка, таймаут, 408, 429 і 5xx — повторюємо: через 10 секунд, 30 секунд, 2 хвилини, 10 хвилин, годину й шість годин.
- Інші 4xx — не повторюємо. Це свідома відмова приймача, і шість однакових запитів нічого не змінять, лише сховають причину.
- Після сьомої невдачі подія вважається недоставленою. Її видно в журналі, і її можна відправити знову кнопкою «Повторити».
- Якщо поспіль не доходить сто подій, підписка вимикається, а власник отримує сповіщення. Це не мовчазне зникнення інтеграції — вимкнення видно на екрані з поясненням.
Події не надсилаються миттєво: черга розбирається кожні пʼять хвилин. Це стеля затримки, а не типове значення.
Журнал доставок показує не лише код відповіді, а й її початок. Питання «чому не доходить» майже завжди має відповідь у тілі відповіді вашого сервера, а не в коді статусу.
Вхідні: коли надсилають нам
Зворотний напрямок: зовнішня система надсилає POST нам, і в Systemer зʼявляється лід, задача або операція. Джерело створюється в Налаштування → Вебхуки → Вхідні; воно має свою адресу, свій ключ і рівно один намір.
Операція ззовні лягає ЧЕРНЕТКОЮ і не впливає на жоден звіт, доки людина її не підтвердить. Це не обережність заради обережності: зовнішня система надсилає намір, а не факт. Баг чи дубль на її боці інакше миттєво стає рядком у звіті, а звіт — підставою для рішення про гроші.
- Один намір на джерело. Ключ, що вміє створювати і лід, і операцію, потрапивши не в ті руки, коштує більше за два ключі.
- Контекст, якого запит не несе — рахунок для операції, проєкт для задачі, відповідальний за лід, — задається в налаштуваннях джерела один раз.
- Ключ показується один раз при створенні. Новий ключ можна створити будь-коли; старий перестає діяти тієї ж секунди.
Вхідні: як надіслати запит
Ключ передається заголовком Systemer-Key або як Bearer-токен в Authorization — обидва приймаються. Тіло — JSON.
Новий лід
POST /api/webhooks/inbound/<ідентифікатор джерела>
Systemer-Key: whin_...
Systemer-Idempotency-Key: form-2026-10-01-17f3
Content-Type: application/json
{
"name": "Олена Ковальчук",
"company": "ТОВ «Приклад»",
"email": "olena@example.com",
"phone": "+380671234567",
"message": "Потрібен редизайн сайту"
}Чернетка операції
{
"kind": "expense",
"amount": "2400.50",
"currency": "UAH",
"date": "2026-10-01",
"description": "Хостинг за жовтень",
"counterparty": "Hetzner"
}- Валюта операції має збігатися з валютою рахунку, обраного в джерелі, — інакше запит відхиляється з поясненням.
- Переказ ззовні не приймається: це рух між вашими власними рахунками, і зовнішня система про них не знає.
- Сума приймається і рядком, і числом, але рядок точніший: число в JSON — це double, і 2400.50 в ньому вже не 2400.50.
Вхідні: що ми відповідаємо
- 201 — запис створено. У тілі відповіді його тип та ідентифікатор.
- 401 — джерела немає або ключ не підходить. Відповідь однакова навмисно: інакше адреса стає способом дізнатись, які джерела існують.
- 409 — джерело на паузі.
- 422 — тіло або налаштування джерела не дають створити запис. Причина — у полі error, і вона написана для людини.
- 500 — наш збій. Саме цей запит має сенс повторити.
Надішліть заголовок Systemer-Idempotency-Key — і повтор із тим самим значенням поверне той самий результат замість другого запису. Без нього ключем стає хеш тіла: повтором вважається рівно однакове тіло, що слабше, але краще, ніж нічого.
Кожен запит видно в журналі джерела: що створилось або чому відхилено. Тіло запиту зберігається 30 днів — далі лишається сам запис без вмісту.
Які події доступні
- CRM: ліду й угоді призначено відповідального, лід конвертовано, угода перейшла на новий етап, угоду виграно, угоду втрачено.
- Проєкти: проєкту призначено відповідального, проєкт наближається до бюджету, проєкт перевищив бюджет, задачі призначено виконавця.
- Фінанси: рахунок прострочено, прогнозується касовий розрив, маржа помітно впала.
Внутрішні події — листування в чатах, робота над самою Systemer, підказки AI — назовні не виходять. Перелік розширюється, і кожна нова подія зʼявляється в переліку вимкненою: щоб вона почала надходити, її треба обрати.
Читати далі
Дізнайтесь свою справжню
маржу вже сьогодні.
Один вечір на налаштування — і замість зведення таблиць ви бачите маржу кожного проєкту з накладними й прогноз касового розриву. Без картки, тариф Free назавжди.
Уже маєте акаунт? Увійти