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

Вебхуки

Система организации получает события обращений POST-запросом на свой адрес, без опроса API.

Подписка​

Подписка принадлежит сервисному аккаунту, нужно право integration.webhooks (роль «Интеграция: ведение обращений»). Чужие подписки, в том числе другого аккаунта той же организации, отвечают 404. У аккаунта — до 20 подписок.

ОперацияЧто делает
GET webhooksподписки аккаунта
POST webhooksсоздать: url, events, secret, необязательные description и is_active
GET webhooks/{webhook_id}подписка
PUT webhooks/{webhook_id}изменить: url, events, is_active, expected_version (устаревшая — 409), необязательные description и secret (заменяет прежний)
DELETE webhooks/{webhook_id}удалить вместе с журналом доставок
POST webhooks/{webhook_id}/check«Проверить»: подписанный ping на адрес и итог (delivered, status_code, error). Ничего не меняет
GET webhooks/{webhook_id}/deliveriesжурнал доставок за 30 дней, новые первыми (limit до 100): событие, попытки, код ответа, ошибка. Тел нет
  • Адрес. Хост должен быть в списке разрешённых администратором установки (integrations.allowed_hosts), иначе 422. Вне разработки — только https. Соединение идёт на проверенный адрес, перенаправления не выполняются.
  • Секрет подписи задаёт организация, не короче 16 символов. Shtab Connect его не генерирует и не показывает: в ответах — только secret_hint (…1234).
  • Выключить — PUT с is_active = false. Включение обнуляет счётчик сбоев.
  • Создание, изменение, удаление и выключение после сбоев попадают в журнал аудита организации.

События​

typeКогда
message.customerновое публичное сообщение клиента
message.replyпубличное сообщение клиенту от оператора, бота или системы
conversation.stateсменилось состояние, как его видит клиент: waiting, at_bot, assigned, on_hold, resolved, closed
conversation.assignedобращение назначено оператору: взял, передали, перехватил
conversation.transferredобращение передано в очередь
conversation.closedцикл закончился: status — resolved или closed, resolution — код из results, cycle — номер цикла

Приходят события всех обращений организации тех видов, на которые подписан аккаунт. Тело — JSON одного события: type, schema_version, event_id, occurred_at (UTC) и conversation (id, number, customer_id, identities, queue_id). У сообщений есть message: автор, тексты, файлы (attachment_id для GET attachments/{id}); скрытое сотрудником сообщение приходит с hidden без текстов и файлов.

Поля каждого события — на страницах контрактов вебхуков, собранных из их JSON Schema. Те же схемы — в каталоге sdk/contracts/webhooks/ комплекта поставки.

  • Тело собирается в момент отправки: сообщение, скрытое до доставки, уходит скрытым.
  • Порядок доставок не гарантирован: повтор после сбоя может прийти позже следующего события. Порядок внутри обращения — по occurred_at и message.sequence, текущее состояние — GET conversations/{id}.

Заголовки и подпись​

ЗаголовокЗначение
X-Helpdesk-Eventtype события
X-Helpdesk-Event-Id, Idempotency-Keyevent_id: у повтора тот же, по нему отбрасывают дубль
X-Helpdesk-Signaturet=<unix time>,v1=<hex HMAC-SHA256 строки "<t>.<тело>" секретом подписки>

Подпись проверяют по сырому телу запроса, до разбора JSON, и отклоняют старую метку времени. В SDK это одна функция (SDK); без SDK:

import hashlib
import hmac
import time


def verify(secret: str, header: str, body: bytes, tolerance: int = 300) -> bool:
parts = dict(item.split('=', 1) for item in header.split(','))
timestamp = int(parts['t'])
if abs(time.time() - timestamp) > tolerance:
return False
signed = f'{timestamp}.'.encode() + body
expected = hmac.new(secret.encode(), signed, hashlib.sha256).hexdigest()
return hmac.compare_digest(expected, parts['v1'])

Ответ, повторы, выключение​

  • Принято — любой 2xx за 10 с. Тело ответа не читается.
  • Повтор — при таймауте, ошибке соединения, 408, 429, 5xx: пауза 30 с, дальше вдвое, не больше часа, всего до 8 попыток с тем же event_id.
  • Отказ без повтора — прочие 4xx и адрес вне разрешённых.
  • Выключение. После 10 доставок подряд, не прошедших окончательно, подписка выключается: status = disabled, disabled_reason = failures, запись в аудите. События за время выключения не копятся и не досылаются.

Совет: отвечайте 2xx сразу после проверки подписи и записи события к себе, а обрабатывайте его отдельно — так доставка не упрётся в 10 секунд.