Вебхуки
Система организации получает события обращений 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-Event | type события |
X-Helpdesk-Event-Id, Idempotency-Key | event_id: у повтора тот же, по нему отбрасывают дубль |
X-Helpdesk-Signature | t=<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 секунд.