Globask

Вебхуки

События ваших диалогов приходят на ваш адрес в тот момент, когда происходят. Это обратное направление к серверному API: там вы зовёте нас, здесь мы зовём вас.

Настраиваются в кабинете: проект → вкладка «Вебхуки». У одного проекта их может быть несколько, каждый со своим набором событий и своим секретом. Ключ API для них не нужен — они ходят к вам, а не вы к нам.

Что приходит

POST на ваш адрес, тело — JSON в UTF-8.

{
    "event": "message.created.in",
    "occurred_at": "2026-08-23T10:15:30+03:00",
    "sequence": 1787472930123456,
    "delivery_id": "0198f3c1-...",
    "project": { "public_key": "pk_...", "name": "Мой сайт" },
    "data": {
        "message": {
            "id": "0198f3c0-...",
            "direction": "in",
            "body": "Сколько стоит доставка?",
            "author": { "type": "contact", "via": null, "name": null },
            "created_at": "2026-08-23T10:15:30+03:00"
        },
        "conversation": {
            "id": "0198f3bf-...",
            "status": "open",
            "created_at": "..."
        },
        "contact": {
            "id": "0198f3be-...",
            "number": 5,
            "name": null,
            "email": null
        }
    }
}

Заголовки:

Заголовок Что это
X-Globask-Event имя события, то же, что в поле event
X-Globask-Delivery уникальный id доставки — ключ идемпотентности
X-Globask-Signature подпись вида t=<unix>,v1=<hex>
User-Agent Globask-Webhook/1

Состав полей message, conversation и contact тот же, что у чтения, — описание значений direction, author.type и author.via смотрите там.

События

Имя Когда
message.created.in посетитель написал в виджет
message.created.out менеджер или автоответ ответил посетителю
conversation.reopened закрытый диалог ожил: посетитель написал снова
conversation.closed диалог закрыт командой из топика
widget.opened посетитель открыл панель чата
inbox.failing доставка менеджерам сломана: бота выгнали из группы
ping проверочная доставка кнопкой из кабинета

Про widget.opened важно знать, что именно он считает. Это охват, а не клики: событие уходит при первом открытии панели на загруженной странице. Панель живёт в iframe, который повторно не создаётся, поэтому второе и третье открытие до нас просто не доходят. Если вам нужно «сколько раз кликнули» — этого события у нас нет, и делать вид, что есть, мы не станем.

Проверка подписи

Подписывается метка времени вместе с телом, а не одно тело. Так записанный запрос нельзя проиграть вам повторно через месяц: подпись без метки оставалась бы валидной вечно.

Строка, от которой считается HMAC-SHA256: <timestamp>.<сырое тело>.

$parts = [];
foreach (explode(',', $request->header('X-Globask-Signature')) as $part) {
    [$key, $value] = explode('=', trim($part), 2);
    $parts[$key] = $value;
}

$expected = hash_hmac('sha256', $parts['t'].'.'.$request->getContent(), $secret);

// Окно в 5 минут: часы на наших серверах и на вашем могут разойтись.
$fresh = abs(time() - (int) $parts['t']) <= 300;

if (! $fresh || ! hash_equals($expected, $parts['v1'])) {
    abort(403);
}

Три вещи, на которых обычно ошибаются:

  1. Считайте подпись от сырого тела, до разбора JSON. Перекодированный JSON — уже другие байты, и подпись не сойдётся.
  2. Сравнивайте hash_equals, а не ===: обычное сравнение строк утекает время и позволяет подобрать подпись побайтно.
  3. Проверяйте метку времени. Без неё подпись защищает от подделки, но не от повтора.

Секрет показывается один раз — сразу после создания вебхука. Повторно мы его не покажем: он хранится шифрованным, и ключ, который можно подсмотреть в интерфейсе спустя месяц, защищает хуже, чем кажется. Потеряли — заведите вебхук заново.

Идемпотентность

X-Globask-Delivery не меняется между попытками: если мы отправили событие, не дождались ответа и повторили, у обеих отправок будет один и тот же id. Запоминайте его и отбрасывайте повторы — иначе сетевой таймаут на нашей стороне обернётся вторым лидом в вашей CRM.

Мы этот урок выучили на входе: у нас так же дедуплицируются сообщения виджета и апдейты Telegram.

Порядок

Порядок доставки не гарантирован. События независимы, и медленный ответ на одно не должен задерживать остальные.

Для упорядочивания есть два поля: occurred_at (человекочитаемое время события) и sequence (то же время в микросекундах, целым числом). Сортируйте по sequence.

Ретраи и что значит «доставка не проходит»

Отвечайте 2xx — этого достаточно, тело ответа мы не читаем. Любой другой код и любой сетевой сбой считаются неудачей.

  • Повторяем с паузами 5, 15, 60 и 300 секунд.
  • Общий бюджет одной доставки — 30 минут. После него событие помечается неудачным и больше не повторяется.
  • Три исчерпанных бюджета подряд переводят вебхук в состояние «доставка не проходит»: отправка останавливается совсем.

Остановка — не каприз: мёртвый адрес иначе занимал бы очередь бесконечно. Починив приём, включите вебхук в кабинете. Накопленные за простой события повторно не уйдут — если для вас это важно, вычитайте пропущенное чтением: списки листаются курсором как раз для таких случаев.

Ответьте быстро: у нас 5 секунд на ответ и 3 на соединение. Тяжёлую работу кладите в свою очередь, а нам отвечайте сразу.

Требования к адресу

  • Только https. Подпись защищает от подделки, но не от чтения, а в теле едет переписка ваших посетителей.
  • Адрес должен быть доступен из интернета. Внутренние диапазоны (127.0.0.1, 10.*, 192.168.*, 169.254.* и прочие приватные сети) мы не вызываем — ни напрямую, ни через имя, которое в них резолвится.
  • Редиректы не выполняются: отвечайте по тому адресу, который вы указали.
  • Логин и пароль в адресе не поддерживаются — для авторизации используйте подпись.

Что наружу не уходит никогда

Настоящие имена наших и ваших сотрудников. В поле author.name приходит публичное имя менеджера, а если оно не задано — «Оператор». Внутренние числовые идентификаторы тоже не уходят: все сущности адресуются uuid, проект — своим public_key.