Виджет на сайте

Виджет — это чат вашего бота прямо на сайте: круглая кнопка в углу страницы, из которой открывается переписка. Для посетителя это ещё один способ написать вам, для бота — обычный канал: те же реакции, те же диалоги операторов, что и в мессенджерах. Обзор мультиканальности — на странице Каналы.

Установка — это одна вставка кода в шаблон сайта. Всё остальное настраивается в кабинете и подхватывается без правок на сайте.

Где взять код для вставки

Откройте в кабинете раздел «Виджет» → блок «Установка на сайт» → поле «Код для вставки». Кнопка «Скопировать» кладёт весь фрагмент в буфер обмена.

Код выглядит так — с вашим собственным ключом вместо многоточия:

<script>
  window.mybot = { key: "eu-1a2b3c4d-..." };
</script>
<script async src="https://getmybot.dev/loader.js"></script>

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

Под полем с кодом показана подсказка вида «Виджет будет обращаться к …». Если на вашем сайте настроен CSP (Content Security Policy), разрешите этому адресу загрузку скриптов и сетевые запросы, иначе браузер заблокирует виджет молча.

Куда вставлять код

Фрагмент вставляется в HTML каждой страницы, где виджет должен появляться. На практике это значит — один раз в общий шаблон сайта: подвал, «код перед </body>», footer-скрипты в CMS или контейнер в диспетчере тегов.

Правила простые:

  • Лучшее место — перед закрывающим </body>. Можно и в <head>, виджет это переживёт, но тогда браузер тратит время на него раньше, чем на ваш контент.
  • Порядок двух тегов важен: первый задаёт ключ, второй загружает виджет. Не меняйте их местами и не разносите по разным местам страницы.
  • Второй тег помечен async — он не блокирует отрисовку страницы. Не убирайте этот атрибут.
  • Один фрагмент на страницу. Две вставки — две попытки запустить виджет.

Дальше загрузчик сам определит, из какого дата-центра обслуживается ваш бот, и подтянет оттуда основной код виджета. От вас для этого ничего не требуется.

Ключ установки — публичный

Ключ в первом теге — публичный идентификатор вашего бота, а не пароль. Он лежит в исходном коде страницы: любой посетитель может открыть «Просмотр кода страницы» и увидеть его. Так устроен любой чат-виджет, и это нормально.

Что важно понимать:

  • По ключу нельзя войти в кабинет, прочитать чужие диалоги, выгрузить базу подписчиков или что-то изменить в боте. Он открывает ровно одну возможность — начать новую переписку с этим ботом.
  • Не используйте ключ как секрет: не прячьте его, не пытайтесь обфусцировать, не считайте его знание чем-то опасным.
  • Если ключ нужно поменять (например, вы разошлись с подрядчиком и хотите начать с чистого листа), это делается через поддержку. Старый код на сайте после смены ключа перестанет работать — вставку придётся обновить.

Список разрешённых доменов

У виджета есть список разрешённых доменов (origin allowlist) — перечень адресов сайтов, с которых виджету разрешено работать. Список задаётся при подключении канала и меняется потом отдельно, без перевыпуска ключа: ваша вставка на сайте остаётся прежней.

Записи в списке — это origin, то есть схема плюс домен плюс, если он нестандартный, порт:

https://example.com
https://www.example.com
https://shop.example.com
http://localhost:3000

Сверка точная, посимвольная:

  • Масок и звёздочек нет. https://*.example.com работать не будет — перечислите поддомены по отдельности.
  • example.com и www.example.comразные записи. Если сайт открывается по обоим адресам, нужны обе.
  • http:// и https:// — тоже разные записи. Обычно нужен только https://, но тестовый стенд на http:// придётся добавить явно.
  • Путь в записи не допускается: https://example.com/shop не примут, origin — это только адрес сайта.

Если ваш реальный домен в списке не указан, виджет на сайте не запустится. Браузер посетителя получит от платформы отказ «origin not allowed», чат просто не откроется. Проявляется это обычно после переезда на новый домен, добавления поддомена или запуска второй языковой версии сайта на отдельном адресе — все они добавляются в список отдельными строками.

Если список пуст, ограничения нет: виджет работает с любого сайта. Это состояние «ограничение ещё не настроено», а не «ограничение выключено намеренно» — как только вы знаете свои домены, впишите их.

Что это ограничение даёт, а что нет

Даёт: чужой сайт не сможет встроить ваш виджет у себя. Иначе кто-нибудь мог бы скопировать ваш код на свою страницу, а браузеры реальных посетителей отдавали бы содержимое переписки с вашим ботом на чужой сайт. Список разрешённых доменов закрывает именно это.

Не даёт: это не защита от того, кто скопировал ключ и обращается к платформе напрямую — не из браузера, а, скажем, скриптом. Заголовок с адресом сайта проставляет браузер; программа, работающая без браузера, может его не отправлять или отправить какой угодно. Так что относитесь к списку как к ограничению встраивания, а не как к границе безопасности. Ваша защита от злоупотреблений — это ограничения частоты запросов и модерация диалогов на стороне платформы, а не этот список.

Внешний вид

В разделе «Виджет»«Внешний вид» настраиваются:

  • Положение — кнопка виджета в левом или правом нижнем углу.
  • Цвет акцента — цвет кнопки и элементов чата. Задавайте фирменный цвет сайта, чтобы виджет не выглядел чужеродным.
  • Название чата — подпись на кнопке запуска и заголовок панели чата. Обычно это название компании или имя, которым бот представляется.

Рядом есть предпросмотр — живой виджет, на котором сразу видно результат, ещё до сохранения.

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

Изменения применяются на вашем сайте сами: виджет читает настройки при загрузке страницы, править вставленный код не нужно.

Посетители анонимны

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

Что из этого следует:

  • В диалогах такой посетитель виден как аноним: у него нет имени, телефона и почты, пока он сам их не напишет.
  • Идентификатор живёт в конкретном браузере. Другой браузер, другое устройство или очищенные данные сайта — это уже новый посетитель с чистой перепиской.
  • Анонимного посетителя можно связать с известным вам клиентом — например, с авторизованным пользователем вашего личного кабинета. Как это сделать, описано в следующем разделе.

Связывание посетителя с вашим пользователем

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

Делается это одним вызовом с функцией, которая возвращает доказательство:

mybot.identify(async (visitorId) => {
  const res = await fetch("/mybot-sign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, visitorId }),
  });
  return res.json(); // { userId, signature, expiresAt }
});

Виджет вызывает вашу функцию с аргументом visitorId — текущим анонимным идентификатором посетителя для этого браузера, единственным значением, которое известно только виджету, — и ждёт, пока она вернёт (или разрешится в) { userId, signature, expiresAt }. Единственная задача вашей функции — передать visitorId на ваш собственный сервер, вместе с идентификатором авторизованного пользователя (currentUser.id выше — это то, как он выглядит именно у вас), и вернуть ровно то, что ответил ваш сервер. Само подписание происходит на вашем сервере, а не в этой функции — см. ниже.

Функция обратного вызова, а не готовое значение и не способ напрямую прочитать visitorId, по двум причинам:

  • Время появления. Виджет создаёт visitorId асинхронно, во время своего запуска, — его не существует в момент, когда отработал скрипт-загрузчик, а сам mybot.identify устанавливается только после того, как это завершилось (подробнее — ниже). Поэтому к моменту, когда ваша страница вообще может вызвать mybot.identify, значение, которое получит ваша функция, гарантированно настоящее. У обычного геттера такой гарантии не было бы: ничто не мешало бы странице прочитать его на одну строчку раньше и получить пустоту — молча получив подпись, которая никогда не сойдётся, без единой подсказки почему.
  • Область видимости. Коду вашей страницы вообще не нужно хранить, запоминать или передавать visitorId вручную — он существует только внутри этой одной функции, для единственного нужного ей вызова.

С момента, когда visitorId покидает виджет, это предъявительский идентификатор (bearer capability): кто угодно, получивший для него подпись, может быть объединён с сессией этого посетителя. Отправляйте его только на ваш собственный сервер, по вашему же авторизованному запросу, и никуда больше — не логируйте его, не пересылайте третьим лицам и не кладите в клиентские аналитические вызовы.

Подпись считается только на вашем собственном сервере

Внутри вашей функции выше ваш собственный сервер — никогда не браузер — вычисляет подпись: HMAC-SHA256 на секретном ключе виджета, в шестнадцатеричном виде, первые 32 символа, от трёх значений, объединённых в одно сообщение. Та же формула показана в кабинете рядом с секретом, под заголовком «Signing algorithm»:

signature = hex(HMAC-SHA256(key: secret, message: userId + "\n" + visitorId + "\n" + expiresAt)).slice(0, 32)

Каждое + "\n" + в формуле — это настоящий символ переноса строки между частями, а не два символа: обратный слэш и буква n. Если ваша HMAC-функция принимает сообщение одной строкой, склейте три части реальным переводом строки — три отдельных вызова .update() без переноса строки между ними хэшируют другое, неверное сообщение.

Три части:

  • userId — идентификатор авторизованного пользователя, то же значение, что ваш сервер получает от функции выше и возвращает обратно в своём ответе.
  • visitorId — анонимный идентификатор посетителя, который ваша функция получила аргументом и без изменений передала на ваш сервер.
  • expiresAtunix-время в секундах (не в миллисекундах), до которого именно эта подпись остаётся действительной; его выдаёт ваш сервер в момент подписи. Платформа отклоняет вызов, если expiresAt уже в прошлом, а также если он больше чем на 24 часа в будущем — подписывайте прямо перед тем, как вернуть значение из функции, а не один раз с последующим кэшированием для следующих запросов.

Этот срок годности — не второстепенная деталь, а главная причина изменения формулы. Прежняя формула покрывала только идентификатор пользователя, поэтому однажды перехваченная подпись — где-то залогированная, снятая с трафика, неважно как — оставалась действительной навсегда и подходила для любого посетителя, а не только для того, кому была выдана. Кто угодно, получив такую подпись, мог воспроизвести её в совершенно другом браузере — и платформа объединила бы анонимную переписку постороннего человека с профилем реального клиента. Привязка подписи к конкретному visitorId и короткий срок жизни закрывают оба конца этой дыры: подпись проходит проверку только для той сессии, для которой выдана, и нигде больше, а после наступления expiresAt перестаёт проходить вовсе — так что даже утёкшая подпись остаётся разовой угрозой для одной сессии на короткое время, а не постоянной уязвимостью.

Считайте все три значения на своём сервере и возвращайте их из функции уже готовыми. Это не формальность и не «так принято»: чтобы посчитать подпись в браузере, туда пришлось бы отдать сам секрет — то есть выдать его каждому посетителю страницы. После этого любой смог бы представиться любым вашим клиентом и прочитать его переписку. Подпись (и visitorId/expiresAt, для которых она посчитана) отдавать обратно в браузер безопасно, секрет — нет.

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

Вызов mybot.identify появляется не мгновенно, а после того как загрузится основной код виджета — вместе с настоящим visitorId (см. «Время появления» выше). Вызывайте его из обработчика загрузки страницы, а не первой строкой в <head>.

Где взять секрет и как его сменить

Секрет виджета — это ключ, которым вы подписываете. Он лежит в настройках виджета, в разделе «Секрет для identify()». Если раздел говорит, что виджет ещё не подключён, — сначала подключите веб-канал, секрет появится вместе с ним.

Показать. Кнопка «Показать секрет» запрашивает значение. Появляется оно не открытым текстом: сначала вы увидите точки, а символы покажет отдельный переключатель «Показать» (обратно — «Скрыть символы»). Рядом есть кнопка «Убрать с экрана», которая убирает значение из виду.

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

Скопировали — уберите за собой сами. Буфер обмена платформа очистить не может: из браузера это ненадёжно, поэтому и не обещаем. Уберите значение с экрана и очистите буфер вручную, когда закончите, особенно на общем компьютере.

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

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

Поэтому меняйте секрет осознанно: сначала подготовьте выкладку бэкенда с новым значением, и только потом нажимайте смену. Нажать «Сменить секрет», чтобы посмотреть, что будет, — плохая идея. Новое значение показывается на экране сразу после смены, так что скопировать его можно тут же.

Как виджет ведёт себя на странице

Виджет намеренно устроен так, чтобы не пересекаться с вашим сайтом:

  • Он живёт в изолированном контейнере (закрытый Shadow DOM). Ваши стили не попадают внутрь виджета, а стили виджета не попадают на страницу. Побочный эффект — «покрасить» виджет своим CSS не получится; для оформления используйте настройки внешнего вида.
  • Контейнер растянут на весь экран, но не перехватывает клики: нажатия проходят на страницу насквозь, реагируют только сама кнопка и панель чата.
  • Виджет всегда поверх контента страницы, перекрыть его вашими слоями не выйдет.
  • Любая ошибка внутри виджета остаётся внутри: сломаться должна максимум кнопка чата, ваш сайт продолжает работать. Если виджет не появился, ищите причину в консоли браузера — чаще всего это CSP или домен, не добавленный в список разрешённых.

Если виджет не появился

Пройдите по порядку:

  1. Откройте исходный код страницы и убедитесь, что оба тега на месте и ключ в них не пустой.
  2. Проверьте, что адрес сайта — ровно тот, что вписан в список разрешённых доменов, включая https:// и www.
  3. Посмотрите консоль браузера: сообщение про Content Security Policy означает, что нужно разрешить адрес платформы в CSP сайта.
  4. Убедитесь, что виджет не режется блокировщиком рекламы в вашем браузере — проверьте в приватном окне без расширений.

Что дальше