Документация Виджет для сайта

Виджет для сайта

Как подключить чат поддержки RevBack к сайту. Один тег скрипта — и на сайте появляется кнопка чата; по желанию — своя кнопка, счётчик непрочитанных и связка с пользователем сайта.

Что делает виджет

Скриптhttps://api.revback.ai/widget.js
ТехнологияОбычный скрипт без зависимостей; интерфейс — в Shadow DOM: стили сайта не протекают в виджет, а стили виджета — на сайт
ЯзыкиРусский и английский
Что делаетПереписка посетителя с оператором и AI-ассистентом, вложения, счётчик непрочитанных, «Оператор печатает…», ✓✓ — оператор прочитал, ответ на email, если посетитель его оставил, связка с пользователем сайта (identify)

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

Шаг 1. Вставить код

Тег — перед </body> на всех страницах, где нужен чат.

Код вставки HTML
<!-- перед </body> -->
<script src="https://api.revback.ai/widget.js" data-widget-key="ваш ключ"></script>

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

  • Адрес API настраивать не нужно: виджет обращается туда же, откуда загружен скрипт.
  • data-locale="ru|en" — язык интерфейса. Без атрибута — язык браузера; всё, кроме английского, — русский.
  • data-position="left|right" — сторона кнопки. Действует, только пока положение кнопки не задано в настройках канала в кабинете: заданное там главнее.
  • async и defer можно; тег можно вставить и скриптом. Свой код, который обращается к window.RevBack, тогда запускайте в обработчике load тега скрипта — addEventListener('load', …), примеры ниже. Если тег виджета без async/defer — свой код можно просто положить в обычный тег после него.
  • До загрузки widget.js вызовы window.RevBack?.… ничего не делают, а клик по data-revback-open теряется. После загрузки, но до появления виджета на странице, open() и такой клик запоминаются (шаг 3).
  • Инлайн-копия скрипта и type="module" не работают: виджет находит свой тег через document.currentScript, а из его src берёт адрес API — у инлайн-скрипта src нет, у модуля currentScript пуст.
  • Обновления виджета приезжают сами: браузер хранит скрипт 5 минут, потом сверяется с сервером. Тег менять не нужно.
С атрибутами HTML
<script src="https://api.revback.ai/widget.js" data-widget-key="ваш ключ"
        data-locale="en" data-position="left" async></script>
Свой код после загрузки JavaScript
// тег виджета в разметке с async или defer
function initRevback() {
  // widget.js выполнился — window.RevBack есть
}
// скрипт мог выполниться раньше этого кода (например, из кэша) — тогда load уже не придёт
if (window.RevBack) initRevback()
else document.querySelector('script[data-widget-key]').addEventListener('load', initRevback)
Вставка скриптом JavaScript
// например, в SPA или после согласия посетителя
const s = document.createElement('script')
s.src = 'https://api.revback.ai/widget.js'
s.dataset.widgetKey = 'ваш ключ'
s.addEventListener('load', () => {
  // widget.js выполнился — window.RevBack есть: подписки, identify и т. д.
})
document.body.append(s)

Шаг 2. Проверить

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

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

  • Беседу создаёт первое сообщение посетителя: открытие виджета пустых диалогов у оператора не заводит.
  • На экране шириной до 480 px чат открывается во весь экран.

Шаг 3. Своя кнопка

Без JavaScript: клик по любому элементу с атрибутом data-revback-open (или внутри него) открывает чат. Слушатель висит на document, поэтому работают и элементы, добавленные на страницу позже.

Своя кнопка HTML
<button type="button" data-revback-open>Написать в поддержку</button>

Из кода — open(), close(), toggle():

Открыть из кода JavaScript
window.RevBack?.open()
window.RevBack?.close()
window.RevBack?.toggle()
  • window.RevBack и слушатель data-revback-open появляются, когда widget.js выполнился. Раньше window.RevBack?.open() ничего не делает, а клик теряется (шаг 1).
  • После выполнения widget.js, но до того, как виджет появился на странице, вызов open(), close(), toggle() и клик по data-revback-open запоминаются: открытие применится, когда виджет появится. Если настройки канала не загрузились, виджет не появляется и отложенное открытие теряется.
  • Штатную кнопку можно скрыть в настройках положения кнопки в кабинете — отдельно для компьютера и телефона. Чат и window.RevBack при этом работают.

Шаг 4. Счётчик и события

Для своей кнопки: число непрочитанных и состояние чата — геттерами и подписками. Код ниже выполняется после загрузки widget.js (шаг 1).

Счётчик и события JavaScript
// счётчик непрочитанных на своей кнопке
const offUnread = RevBack.onUnread((n) => {
  badge.textContent = n > 0 ? String(n) : ''
})

// чат открыт / закрыт
const offToggle = RevBack.onToggle((open) => {
  menuItem.classList.toggle('active', open)
})

// сессия выдана и первая история на экране
RevBack.onReady(() => { /* … */ })

// отписка
offUnread()
offToggle()
  • getUnread() — число непрочитанных; onUnread(cb) — подписка на него.
  • getOpen() — открыт ли чат; onToggle(cb) — подписка на открытие и закрытие.
  • onUnread и onToggle зовут cb сразу текущим значением, затем при каждом изменении. Возвращают функцию отписки.
  • onReady(cb) — один раз за загрузку страницы: сессия выдана и первая история на экране. У вернувшегося посетителя с живой сессией (ей осталось больше минуты) — сразу после загрузки, иначе — после первого открытия чата. Если виджет уже готов, cb вызывается сразу. Возвращает функцию отписки.
  • Исключение в вашем cb виджет ловит и пишет в console.warn — чат продолжает работать.

Шаг 5. Личность пользователя и выход

RevBack.identify({ token }) связывает посетителя с пользователем вашего сайта: в чате видно, кто пишет, переписка привязывается к его аккаунту. Код этого шага выполняется после загрузки widget.js (шаг 1).

По умолчанию секрет не выпущен — его нужно выпустить: в настройках канала строка «Секрет для подписи» → кнопка «Выпустить». Кабинет покажет секрет один раз, и этим же выпуском включится подтверждение личности. Посмотреть секрет потом нельзя, только «Перевыпустить»; после перевыпуска токены, подписанные старым секретом, получают 401 …/widget-identity-bad-signature. Пока секрет не выпущен, identify отвечает 422 identity/disabled.

Токен — JWT HS256, его выпускает только ваш бэкенд: секрет в браузер не кладут, токен на время жизни равен доступу к переписке пользователя. Ключ подписи — секрет как есть, его байты UTF-8; из base64 его не декодируют.

Имена claims — как в OpenID Connect. Обязательны sub — id пользователя строкой и exp — срок (unix-секунды; потолка нет, советуем минуты). Один токен — один вход: на каждый вход выпускайте новый токен со случайным jti — повтор токена из другой сессии получит 410. Остальное — по желанию:

  • aud — ключ канала: привязывает токен к одному виджету.
  • name, email, phone_number — E.164 (+79001234567; пробелы, дефисы и скобки сервер уберёт сам); отметки подтверждения email_verified, phone_number_verified.
  • Как пользователь вошёл: amr — массив способов (pwd, otp, sms…, fed), при fed — idp (vk_id, sber_id…); auth_time — время входа, unix-секунды.
  • ids — ваши идентификаторы пользователя, attributes — сведения о нём.

Поле неверного вида вход не роняет: сервер его просто не сохраняет. Отказ бывает только по безопасности токена — sub, exp, nbf, aud, подпись, структура.

Полная таблица claims, готовые сниппеты для Node, PHP, Python и Go и тест-вектор — в описании операции identify: https://api.revback.ai/v1/widget/openapi.json.

Вход и выход JavaScript
// токен выпускает ваш бэкенд для вошедшего пользователя; myBackend.revbackToken() — ваш запрос к нему
async function revbackIdentify() {
  try {
    return await RevBack.identify({ token: await myBackend.revbackToken() })
  } catch (e) {
    // 410: токен истёк или уже использован в другой сессии — один повтор новым токеном
    if (e.status !== 410) throw e
    return RevBack.identify({ token: await myBackend.revbackToken() })
  }
}

// пользователь вошёл — и при каждой загрузке страницы, пока он в аккаунте
async function onLogin() {
  const { identity_state } = await revbackIdentify()
}

// пользователь вышел
async function onLogout() {
  try {
    await RevBack.reset()
  } catch (e) {
    // локально гость уже сброшен, новая сессия не выдана (чужой домен, нет сети)
  }
}

// смена пользователя: сначала reset, затем identify токеном нового
async function onSwitchUser() {
  await onLogout()
  await revbackIdentify()
}

identify возвращает { identity_state }:

  • reaffirmed — тот же пользователь, ничего не менялось;
  • promoted — гость этой сессии стал этим пользователем, беседа та же;
  • switched — анонимная переписка слилась в беседу пользователя, лента перезагружена;
  • relogged_in — вошёл другой пользователь: новый посетитель, лента перезагружена.
  • Подтверждённая сессия хранится в браузере и переживает перезагрузку. Звать identify при каждой загрузке страницы, где пользователь вошёл, безопасно: повтор даёт reaffirmed и ленту не трогает.
  • Одновременные вызовы identify выполняются по очереди.
  • Отказ сервера — Error с полями status, type (адрес вида https://revback.ai/errors/… — сверяйте окончание), invalidParams ([{ field, code }], у 422) и retryAfter (секунды из Retry-After или null). Сбой сети — TypeError без поля type. Автоповтора нет.
  • reset() — выход: сессия отзывается на сервере (без сети — только локально), заводится новый гость, лента перезагружается; прежняя переписка в этом браузере недоступна. Без reset() виджет в этом браузере остаётся под прежним пользователем, пока жива подтверждённая сессия (до 30 дней); после её истечения виджет сам заводит нового гостя.
  • reset() может отклонить промис: после отзыва он ждёт выдачи новой сессии, а на чужом домене или без сети её не будет. Оборачивайте в try/catch: при отказе гость локально уже сброшен, новая сессия не выдана.

RevBack.setAttributes(obj) — сведения о посетителе (атрибуты контакта). Работает у любой сессии, подтверждения личности не требует. Значения — строки, null удаляет ключ; всего не больше 32 ключей.

Токен identify с claim attributes заменяет сохранённый набор целиком ({} — очистить), токен без этого claim набор не трогает. Если кладёте attributes в токен, записанное через setAttributes перепишется при каждом identify.

Сведения о посетителе JavaScript
// значения — строки; null удаляет ключ
RevBack.setAttributes({ plan: 'pro', city: 'Казань', promo: null })
  .catch((e) => console.warn('setAttributes', e.status, e.type))

Ошибки identify

Что пришло в отказе identify и что с этим делать. Для 422 во второй строке — field / code из invalidParams.

РезультатЧто случилосьЧто делать
TypeErrorбез status и type запрос не дошёл до сервера — сбой сети повторить позже: автоповтора нет, его планирует ваш код
TypeErrorexpected { token: string } аргумент — не { token: строка }; запрос не отправлялся исправить вызов
401…/widget-identity-bad-signature подпись токена не совпала: не тот секрет, секрет перевыпущен или декодирован из base64. Сессия посетителя жива подписывать строкой «Секрет для подписи» как есть; после перевыпуска — новым секретом
401другой type сессия посетителя истекла — виджет пересоберёт её сам повторить identify
410…/widget-identity-expired exp в прошлом (допуск часов 60 с) или этим токеном уже вошли из другой сессии выпустить новый токен и повторить
422identity / disabled секрет для подписи на канале не выпущен — подтверждение личности не включено выпустить секрет в настройках канала: строка «Секрет для подписи» → «Выпустить»
422token / unsupported_alg алгоритм подписи не HS256 (включая none) подписывать HS256
422nbf / not_yet_valid nbf позже, чем «сейчас + 60 с» проверить nbf и часы сервера
422aud / mismatch aud не содержит ключ канала положить в aud ключ канала или убрать claim
422другой field / code токен собран неправильно — invalidParams называет поле исправить названное поле
409…/widget-identity-merge-too-large анонимная история слишком велика, чтобы слить её одной операцией такие случаи разбирает служба поддержки
503…/widget-identity-merge-busy слияние истории занято параллельной операцией повторить через retryAfter секунд
429 превышен лимит запросов с адреса или на ключ канала повторить через retryAfter секунд
404 канал по этому ключу не найден — удалён или ключ не тот сверить ключ с кабинетом
5xx временный сбой на стороне сервера повторить позже

Шаг 6. Хранение и CSP

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

  • ccw:<ключ>:visitor — id гостя;
  • ccw:<ключ>:session — сессия и её срок;
  • ccw:<ключ>:prechat_email — посетитель уже оставил email или скрыл вопрос о нём;
  • ccw:<ключ>:read_wm — до какого места посетитель прочитал;
  • ccw:<ключ>:changes_cursor — отметка догона ленты.

Cookie ccw_<ключ>_visitor — копия id гостя: срок 1 год, Path=/, SameSite=Lax, Secure на https. Очистка одного из хранилищ гостя не меняет — id восстанавливается из другого. Safari (ITP) может сократить срок cookie. Если недоступны оба хранилища (бывает в приватном режиме), состояние живёт в памяти вкладки, а после перезагрузки это новый гость.

Content-Security-Policy

Если на сайте включена CSP, добавьте к своим директивам. Если у вас задан только default-src, а connect-src, img-src, media-src или style-src нет, браузер применяет к ним default-src — добавьте эти директивы явно, сохранив в них свои значения вроде 'self'.

Content-Security-Policy CSP
script-src  https://api.revback.ai
connect-src https://api.revback.ai wss://api.revback.ai
style-src   'unsafe-inline'
img-src     blob:
media-src   blob:
  • script-src — сам widget.js.
  • connect-src — запросы к API и WebSocket wss://api.revback.ai/v1/widget/ws, по которому приходят новые сообщения. Если сокет недоступен, виджет опрашивает историю.
  • style-src 'unsafe-inline' — стили виджет вставляет элементами <style> внутрь Shadow DOM, без nonce.
  • img-src — картинки вложений и их превью показываются через blob:-адреса; плюс хост вашего логотипа, если он задан в настройках канала: логотип в шапке чата грузится по этой ссылке.
  • media-src blob: — обложку видео, которое прикладывает посетитель, виджет снимает в браузере из blob:-адреса.

Что видит посетитель, если что-то не так

Что видноЧто случилосьЧто делать
«Виджет не активирован для этого домена» страница открыта не на домене канала (localhost, стенд, другой сайт) или домен в канале не указан; видно при открытии чата открыть сайт на домене из поля «Адрес сайта»; для стенда — отдельный канал с доменом стенда
«Чат временно недоступен» сервер отказал в сессии по другой причине, не из-за домена посмотреть ответ на POST /v1/widget/…/session во вкладке сети браузера
Кнопки чата нетскрыта в кабинете кнопка скрыта в настройках положения для этого экрана — компьютера или телефона открывать чат своим элементом (шаг 3) или вернуть кнопку в настройках
Кнопки чата нетв консоли: не задан data-widget-key у тега нет атрибута data-widget-key добавить ключ канала
Кнопки чата нетв консоли: конфиг недоступен настройки канала не загрузились: неизвестный ключ, выключенный канал или сеть сверить ключ с кабинетом
Кнопки чата нетв консоли пусто скрипт вставлен инлайн-копией или с type="module": у инлайн-скрипта нет src, а из src виджет берёт адрес API; модуль не видит свой тег — виджет молча выходит подключать тегом с src, как в шаге 1
Кнопки чата нетв консоли: Refused to load the script … Content Security Policy CSP сайта не пускает widget.js дополнить CSP — шаг 6

Дополнительно

«Назад» и Escзакрывают открытую панель чата, а не уводят с сайта — в браузерах с поддержкой CloseWatcher.
Сайт по httpвиджет работает, но межвкладочная блокировка браузера есть только на https: две вкладки, открытые одновременно новым посетителем, могут завести двух гостей. Рекомендуем https.
Один чат — один сайтдомен, www и поддомены входят в один канал; второй домен — второй канал.
«Спрашивать email»если в канале включено и посетитель оставил адрес, ответ оператора придёт ему и письмом «Вам ответили в чате».
Вложениядо 10 файлов в одном сообщении, до 10 МБ каждый (или меньше, если задано в настройках канала).