Руководство по интеграции: Webhook уведомлений о сменах и корректировках
Webhook позволяет автоматически отправлять HTTP POST-уведомления на ваш сервер (CRM, ERP, 1C, корпоративные боты и т.д.) при любых изменениях в расписании смен и при изменении статусов запросов на корректировку в рамках организации.
Права доступа и безопасность
- Кто может настраивать: Настройка и тестирование Webhook доступны исключительно Владельцу организации. Администраторы и сотрудники без роли владельца не имеют доступа к чтению секретных ключей и редактированию параметров вебхука.
- Подтверждение подлинности запросов: Если указан Секретный ключ (Secret), каждый запрос подписывается с помощью алгоритма
HMAC-SHA256.
Параметры конфигурации
| Поле | Тип | Описание |
|---|---|---|
| Включить Webhook | Переключатель | Активирует фоновую отправку событий на указанный URL. |
| URL вебхука | URL | Публичный endpoint вашего сервера (например, https://api.your-company.com/webhooks/shifts). |
| Секретный ключ (Secret) | Строка (опционально) | Секретный токен для проверки подписи входящих запросов. |
| Подписанные события | Список событий | shift.changed, shift.correction.requested, shift.correction.resolved. |
Формат HTTP-запроса
Все запросы отправляются методом POST с телом в формате application/json.
HTTP-заголовки запроса:
POST /webhooks/shifts HTTP/1.1
Host: api.your-company.com
Content-Type: application/json
User-Agent: LabRB-Webhooks/1.0
X-Webhook-Event: shift.changed
X-Webhook-Timestamp: 2026-10-12T13:50:00.000Z
X-Webhook-Secret: my_secret_token
X-Webhook-Signature: sha256=a1b2c3d4e5f6...7890
X-Webhook-Event— тип наступившего события.X-Webhook-Timestamp— время генерации события по стандарту ISO 8601.X-Webhook-Secret— передаётся, если в настройках задан секретный ключ.X-Webhook-Signature— хеш HMAC-SHA256 от тела запроса (sha256=<hex-сигнатура>).
Структура событий (Payload)
В каждом событии профили сотрудников и руководителей разворачиваются полностью (profile, actor, creator, manager, resolver) с указанием имени, фамилии, должности, почты и userId.
1) Изменение смены: shift.changed
Срабатывает при назначении новой смены, изменении времени или отмене смены сотрудника.
{
"event": "shift.changed",
"organizationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"timestamp": "2026-10-12T13:50:00.000Z",
"data": {
"action": "add", // "add" (добавлена) | "update" (изменена) | "delete" (отменена)
"date": "2026-10-15",
"startTime": "09:00",
"endTime": "18:00",
"scheduleName": "Основной график",
"eventTypeName": "Рабочая смена",
"reason": "Замена сотрудника",
"profileId": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"profile": {
"id": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"userId": "a1c3569e-648b-402a-bf39-44be788229b1",
"name": "Алексей",
"lastName": "Смирнов",
"email": "alex.smirnov@example.com",
"position": "Старший бариста",
"department": "Зал обслуживания"
},
"actorProfileId": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"actor": {
"id": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"userId": "d748f219-4a92-4217-a006-2581001f31a2",
"name": "Ольга",
"lastName": "Петрова",
"email": "olga.petrova@example.com",
"position": "Управляющий",
"department": "Администрация"
},
"previousDate": "2026-10-14", // При изменении даты/времени
"previousStartTime": "10:00",
"previousEndTime": "19:00"
}
}
2) Запрос на корректировку: shift.correction.requested
Срабатывает, когда сотрудник подаёт запрос руководителю на изменение смены.
{
"event": "shift.correction.requested",
"organizationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"timestamp": "2026-10-12T13:50:00.000Z",
"data": {
"correctionId": "f12015df-424a-4467-bc85-5a5857e4e040",
"action": "update", // "add" | "update" | "delete"
"targetDate": "2026-10-15",
"startTime": "10:00",
"endTime": "19:00",
"reason": "Семейные обстоятельства",
"scheduleName": "Основной график",
"creatorProfileId": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"creator": {
"id": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"userId": "a1c3569e-648b-402a-bf39-44be788229b1",
"name": "Алексей",
"lastName": "Смирнов",
"email": "alex.smirnov@example.com",
"position": "Старший бариста",
"department": "Зал обслуживания"
},
"managerProfileId": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"manager": {
"id": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"userId": "d748f219-4a92-4217-a006-2581001f31a2",
"name": "Ольга",
"lastName": "Петрова",
"email": "olga.petrova@example.com",
"position": "Управляющий",
"department": "Администрация"
}
}
}
3) Статус корректировки: shift.correction.resolved
Срабатывает, когда руководитель принимает решение по запросу (одобрено или отклонено).
{
"event": "shift.correction.resolved",
"organizationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"timestamp": "2026-10-12T13:50:00.000Z",
"data": {
"correctionId": "f12015df-424a-4467-bc85-5a5857e4e040",
"status": "approved", // "approved" (одобрено) | "rejected" (отклонено)
"action": "update",
"date": "2026-10-15",
"startTime": "10:00",
"endTime": "19:00",
"reason": "Согласовано руководителем",
"profileId": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"profile": {
"id": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"userId": "a1c3569e-648b-402a-bf39-44be788229b1",
"name": "Алексей",
"lastName": "Смирнов",
"email": "alex.smirnov@example.com",
"position": "Старший бариста",
"department": "Зал обслуживания"
},
"resolverProfileId": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"resolver": {
"id": "0e5b7226-5dfb-47e9-a411-4091a92e1061",
"userId": "d748f219-4a92-4217-a006-2581001f31a2",
"name": "Ольга",
"lastName": "Петрова",
"email": "olga.petrova@example.com",
"position": "Управляющий",
"department": "Администрация"
}
}
}
4) Тестовый запрос: ping
Отправляется при нажатии кнопки «Проверить подключение»:
{
"event": "ping",
"organizationId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"timestamp": "2026-10-12T13:50:00.000Z",
"message": "Тестовое уведомление вебхука смен организации (LabRB)",
"sampleData": {
"action": "update",
"date": "2026-10-15",
"startTime": "09:00",
"endTime": "18:00",
"profile": {
"id": "8f8b3c91-2a14-49c5-8422-921471b48b64",
"userId": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"name": "Иван",
"lastName": "Иванов",
"email": "ivan.ivanov@example.com",
"position": "Бариста",
"department": "Обслуживание"
}
}
}
Пример верификации подписи на приёмнике
Node.js (Express)
const crypto = require("crypto");
app.post("/webhooks/shifts", express.raw({ type: "application/json" }), (req, res) => {
const secret = process.env.SHIFT_WEBHOOK_SECRET;
const signatureHeader = req.headers["x-webhook-signature"]; // sha256=...
if (secret && signatureHeader) {
const rawBody = req.body.toString("utf-8");
const expectedSignature = "sha256=" + crypto
.createHmac("sha256", secret)
.update(rawBody)
.digest("hex");
if (signatureHeader !== expectedSignature) {
return res.status(401).send("Invalid signature");
}
}
const payload = JSON.parse(req.body.toString("utf-8"));
console.log("Получено событие:", payload.event, payload.data);
// Всегда возвращайте статус 2xx
res.status(200).json({ received: true });
});
Python (FastAPI)
import hmac, hashlib
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.post("/webhooks/shifts")
async def handle_shift_webhook(request: Request):
secret = "your_secret_here"
body = await request.body()
signature = request.headers.get("x-webhook-signature")
if secret and signature:
expected = "sha256=" + hmac.new(secret.encode(), body, hashlib.sha256).hexdigest()
if not hmac.compare_digest(signature, expected):
raise HTTPException(status_code=401, detail="Invalid signature")
payload = await request.json()
print("Event:", payload.get("event"), payload.get("data"))
return {"status": "ok"}
Политика повторов и таймауты
- Ожидание ответа сервера: таймаут 10 секунд.
- Успешный ответ: ваш сервер должен вернуть любой статус из диапазона
2xx(например,200 OKили204 No Content). - Политика повторных попыток: при ответе со статусом
>= 400, сетевом сбое или таймауте система автоматически повторяет отправку:- До 3 попыток;
- С экспоненциальной задержкой (2 сек, 4 сек, 8 сек).