ЛоготипВернуться на сайт
Навигация

Руководство по интеграции: 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 сек).