Перейти к содержанию

Руководство по установке

Чтобы добавить виджет на сайт, достаточно одной строки кода. Идентификация посетителей, интеграция с сайтом, вебхуки и API необязательны; обращайтесь к нужному разделу, когда понадобится.

<script src="{{KOK}}/cd/w.js?k=КЛЮЧ_ВАШЕГО_САЙТА" async></script>

Ключ сайта находится в консоли: Настройки → Установка. На WordPress установите виджет с помощью плагина без кода.

Быстрый старт

  1. В консоли скопируйте ключ сайта (начинается с cd_) на странице Настройки → Установка.
  2. Добавьте код из начала этой страницы на все страницы сайта, прямо перед тегом </body>. Замените КЛЮЧ_ВАШЕГО_САЙТА своим ключом.
  3. Обновите страницу: в нижнем углу появится кнопка чата. Пока вы в сети в консоли, посетители могут вам писать.

Код не замедляет страницу: загрузчик маленький и кэшируется, а виджет загружается отдельно и с версией. Если наш сервер недоступен, показывается простая кнопка со ссылкой, указанной в Настройки → Общие → Резервная ссылка для связи.

WordPress

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

Скачать плагин для WordPress

  1. В админке WordPress загрузите zip-файл через Плагины → Добавить новый → Загрузить плагин и активируйте его.
  2. Вставьте ключ сайта на странице Настройки → Layvchat.
  3. Необязательно: отметьте Личность участника и введите секрет подписи (в консоли: Настройки → ID посетителя → Показать секрет).

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

Управление виджетом из кода

Код установки добавляет на страницу объект window.layvchat. Команды можно вызывать сразу после загрузки кода: если виджет ещё не готов, вызовы ставятся в очередь и выполняются, когда он будет готов. У каждой команды есть и турецкое имя (например, open = ac); работают оба.

Команды

КомандаТурецкое имяЧто делает
open()ac()Открывает окно чата.
close()kapat()Закрывает окно.
toggle()degistir()Закрывает окно, если оно открыто, и открывает, если закрыто.
hide() / show()gizle() / goster()Полностью убирает кнопку и окно со страницы / возвращает их (например, на шаге оплаты).
prefill(текст)doldur()Открывает окно и вписывает текст в поле ввода; посетитель нажимает «Отправить».
setVisitor({ ad, eposta, telefon })ziyaretci()Представляет посетителя: поля формы заполняются заранее, имя и e-mail видны в чате. Ключи: ad — имя, eposta — e-mail, telefon — телефон.
setAttributes({ ключ: значение })ozellik()Дополнительные сведения для операторов (сумма корзины, уровень участника…). До 20 полей; значение null удаляет поле. Можно обновлять во время открытого чата.
pageView()sayfa()Сообщает о смене страницы. В одностраничных приложениях (React, Vue…) смена адреса определяется автоматически; используйте при собственной маршрутизации.
getState()durum()Возвращает { acik, sohbet, okunmamis, ajan }: окно открыто, идёт чат, число непрочитанных, имя оператора.
on(событие, fn) / off(событие, fn)то жеПодписывается на событие / отписывается.
// Кнопка «Спросить об этом товаре»
document.querySelector('#vopros-tovar').addEventListener('click', function () {
  window.layvchat.prefill('Здравствуйте, расскажите подробнее о «Кожаном рюкзаке».')
})

// Показать оператору авторизованного участника и его корзину
window.layvchat.setVisitor({ ad: 'Анна Иванова', eposta: '[email protected]' })
window.layvchat.setAttributes({ 'Корзина': '12 990 ₽', 'Статус': 'Золото' })

Данные setVisitor и setAttributes приходят из браузера и не подписаны; в консоли они отмечены как непроверенные. Чтобы надёжно определить аккаунт участника, используйте идентификацию посетителя.

События

СобытиеТурецкое имяКогдаДанные
readyhazirВиджет установлен (подписчики, добавленные позже, вызываются сразу).getState()
open / closeacildi / kapandiОкно открыто / закрыто.—
chatStartedsohbetBasladiПосетитель начал новый чат.—
chatEndedsohbetBittiЧат завершён.—
messagemesajОператор или посетитель отправил сообщение.{ kim: 'ajan' | 'ziyaretci', metin, ajan } — отправитель, текст, имя оператора
unreadokunmamisИзменилось число непрочитанных сообщений.{ n }
window.layvchat.on('chatStarted', function () {
  gtag('event', 'online_chat_start')        // аналитика
})
window.layvchat.on('message', function (m) {
  if (m.kim === 'ajan') console.log(m.ajan + ': ' + m.metin)
})

// Те же события публикуются и на window (английские и турецкие имена)
window.addEventListener('layvchat:unread', function (e) { badge(e.detail.n) })

Если ваш код выполняется до кода установки, добавляйте вызовы в очередь:

(window.layvchatKuyruk = window.layvchatKuyruk || []).push(['open'], ['setAttributes', { 'Страница': 'Оплата' }])

CSS-переменные

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

ПеременнаяПо умолчаниюЧто делает
--layvchat-altНижний отступ из настроек виджетаРасстояние кнопки от нижнего края страницы.
--layvchat-yanБоковой отступ из настроек виджетаРасстояние кнопки от правого (или левого) края.
--layvchat-z2147483600Порядок слоёв (z-index).
@media (max-width: 768px) {
  :root { --layvchat-alt: 84px; }   /* над нижним меню на мобильных */
}

Если раньше вы пользовались Tawk.to или Comm100, существующие вызовы Tawk_API.maximize() и Comm100API.do('livechat.button.click') тоже открывают окно Layvchat; менять кнопки не нужно.

Идентификация посетителя

Если вы передаёте личность посетителей, вошедших на ваш сайт, операторы видят, с каким участником общаются, как подтверждённого (синяя галочка). Для подтверждённых посетителей можно пропустить форму перед чатом, а при включённой интеграции с сайтом показывается карточка клиента.

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

Подпись

imza = HMAC-SHA256(секрет, id + "|" + username + "|" + zaman)   → hex в нижнем регистре (64 символа)
ПолеПравило
idID участника на вашем сайте. 1–64 символа: буквы, цифры, _ и -.
usernameИмя пользователя, которое видят операторы. Используйте точно то же значение, что подписали (показывается до 80 символов).
zamanМетка времени: Unix-время в секундах (не миллисекундах). Подпись действует 2 часа; часы сервера могут спешить не более чем на 5 минут.
imzaПодпись: HMAC-SHA256 в hex в нижнем регистре. Секрет используется как ключ без изменений, как текст.

Три способа передать личность виджету

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

<script>
  window.layvchatKimlik = { id: "123", username: "aivanova", zaman: 1760000000, imza: "…" }
</script>

2. Сообщать о входе и выходе. Подходит для одностраничных приложений (React, Vue…). Подпись действует 2 часа, поэтому на долго открытых страницах вызывайте повторно с новой подписью.

window.layvchat.identify({ id: "123", username: "aivanova", zaman: 1760000000, imza: "…" })
window.layvchat.identify(null)   // после выхода

3. Конечная точка личности. В консоли в поле Настройки → ID посетителя → Конечная точка личности укажите относительный адрес на вашем сайте, который возвращает подписанную личность (например, /layvchat/identity). Виджет читает его с того же источника вместе с cookie и обновляет при загрузке страницы, каждые 15 секунд, при возврате на вкладку и при навигации внутри страницы. Для неавторизованных посетителей возвращайте {"id": null}; ответ не в JSON или с ошибкой считается «вышел».

<?php // /layvchat/identity
session_start();
header('Content-Type: application/json');
header('Cache-Control: no-store');
$secret = getenv('LAYVCHAT_IDENTITY_SECRET');
if (empty($_SESSION['member_id'])) { echo json_encode(['id' => null]); exit; }
$id = (string) $_SESSION['member_id'];
$name = (string) $_SESSION['member_username'];
$time = time();
echo json_encode([
  'id' => $id, 'username' => $name, 'zaman' => $time,
  'imza' => hash_hmac('sha256', "$id|$name|$time", $secret),
]);
// Express — /layvchat/identity
import crypto from 'node:crypto'
app.get('/layvchat/identity', (req, res) => {
  res.set('Cache-Control', 'no-store')
  const member = req.session?.member
  if (!member) return res.json({ id: null })
  const id = String(member.id), name = String(member.username), time = Math.floor(Date.now() / 1000)
  const imza = crypto.createHmac('sha256', process.env.LAYVCHAT_IDENTITY_SECRET).update(`${id}|${name}|${time}`).digest('hex')
  res.json({ id, username: name, zaman: time, imza })
})
# Flask — /layvchat/identity
import hmac, hashlib, os, time
from flask import jsonify, session

@app.get("/layvchat/identity")
def layvchat_identity():
    if "member_id" not in session:
        response = jsonify(id=None)
    else:
        uid, name, ts = str(session["member_id"]), str(session["member_username"]), int(time.time())
        imza = hmac.new(os.environ["LAYVCHAT_IDENTITY_SECRET"].encode(), f"{uid}|{name}|{ts}".encode(), hashlib.sha256).hexdigest()
        response = jsonify(id=uid, username=name, zaman=ts, imza=imza)
    response.headers["Cache-Control"] = "no-store"
    return response

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

Content Security Policy (CSP)

Если на сайте есть CSP, разрешите адрес Layvchat так:

script-src   {{KOK}}
connect-src  {{KOK}} {{WS}}     (и 'self', если используете конечную точку личности)
frame-src    {{KOK}}
style-src    'unsafe-inline'
img-src      https:            (только при собственном изображении кнопки)
media-src    {{KOK}}           (только при собственном звуке уведомления)

Виджет не создаёт cookie на вашем сайте. Ключ посетителя и резервная ссылка хранятся в localStorage с префиксом layv_cd_; содержимое чата и токен доступа остаются только в собственном источнике окна Layvchat.

Интеграция с сайтом Pro

Во время чата операторы видят данные аккаунта подтверждённого посетителя (последние заказы, статус…) и могут выполнять разрешённые вами действия. Layvchat получает эти данные с нескольких конечных точек вашего сервера подписанными запросами. Интеграция работает только для подтверждённых посетителей.

  1. Реализуйте на сервере конечные точки ниже (например, под https://vashsait.ru/layvchat-api).
  2. Укажите этот адрес в консоли: Настройки → Интеграция с сайтом. Секрет подписи (начинается с entg_) показывается только один раз; сохраните его на сервере.
  3. Выдайте операторам нужные права в разделе Настройки → Операторы (просмотр данных, заметки, блокировка).

Запросы

ЗапросТелоКогда
GET /uye/:id—Когда оператор открывает чат (карточка клиента)
POST /uye/:id/not{ not, yapan }Заметка о клиенте
POST /uye/:id/durum{ engelli: true | false, yapan }Заблокировать / разблокировать аккаунт
POST /uye/:id/mesaj{ baslik, govde, yapan }Ответ во внутренние сообщения сайта (заголовок, текст)

:id — это id из личности посетителя. yapan — оператор, выполнивший действие (canli-destek:имяпользователя).

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

Каждый запрос приходит с заголовками X-Imza-Zaman (Unix-секунды) и X-Imza:

X-Imza = HMAC-SHA256(секрет, время + "." + МЕТОД + "." + полныйПуть + "." + сыроеТело)   → hex в нижнем регистре

полныйПуть — путь запроса включая путь вашего адреса: если адрес https://vashsait.ru/layvchat-api, то это /layvchat-api/uye/42. Для запросов GET тело — пустая строка. Отклоняйте метки времени старше 5 минут.

<?php
function layvchat_verify(string $secret): bool {
  $time = $_SERVER['HTTP_X_IMZA_ZAMAN'] ?? '';
  $sig  = $_SERVER['HTTP_X_IMZA'] ?? '';
  if (!ctype_digit($time) || abs(time() - (int) $time) > 300) return false;
  $body = file_get_contents('php://input');
  $expected = hash_hmac('sha256', $time . '.' . $_SERVER['REQUEST_METHOD'] . '.' . $_SERVER['REQUEST_URI'] . '.' . $body, $secret);
  return hash_equals($expected, $sig);
}
// Express: нужно сырое тело → app.use(express.json({ verify: (req, _r, buf) => { req.rawBody = buf.toString() } }))
import crypto from 'node:crypto'
function layvchatVerify(req, secret) {
  const time = req.get('x-imza-zaman') || '', sig = req.get('x-imza') || ''
  if (!/^\d+$/.test(time) || Math.abs(Date.now() / 1000 - Number(time)) > 300) return false
  const expected = crypto.createHmac('sha256', secret)
    .update(`${time}.${req.method}.${req.originalUrl}.${req.rawBody || ''}`).digest('hex')
  return expected.length === sig.length && crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(sig))
}

Ответ для карточки клиента

GET /uye/:id должен возвращать JSON-объект. Обязательно только uye.id; неизвестные поля игнорируются. Если участника нет, верните 404.

{
  "uye": {
    "id": "42", "kullaniciAdi": "aivanova", "ad": "Анна", "soyad": "Иванова",
    "eposta": "[email protected]", "telefon": "5551112233", "ulke": "RU", "paraBirimi": "RUB",
    "toplamHarcama": 5000, "toplamIade": 320,
    "durum": "aktif", "kayitTarihi": "2025-03-14T10:00:00Z", "sonGiris": "2026-10-04T21:10:00Z",
    "riskEtiketleri": ["новый аккаунт"]
  },
  "sonIslemler": [
    { "id": "S1001", "tur": "siparis", "tutar": 500, "durum": "отправлен", "aciklama": "2 товара", "zaman": "2026-10-04T20:00:00Z" }
  ]
}
Все поля клиента
ТипПоля
Текст (до 200 символов)kullaniciAdi (имя пользователя), eposta (e-mail), ad (имя), ikinciAd (второе имя), soyad (фамилия), telefon, telefonKodu (телефон, код), sehir (город), dil (язык), sonGirisIp, kayitIp (IP последнего входа / регистрации), yoneticiNotu (заметка администратора)
Код страны (2 буквы)ulke, kayitUlke (страна, страна регистрации)
Валюта (3 буквы)paraBirimi
Да / нетcevrimici (в сети), epostaDogrulandi, telefonDogrulandi (e-mail / телефон подтверждён)
Время (ISO 8601)sonCevrimici, sonGiris, kayitTarihi (был в сети, последний вход, регистрация)
Дата (ГГГГ-ММ-ДД)dogumTarihi (дата рождения)
Сумма (число)toplamHarcama, toplamIade (всего потрачено, всего возвращено)
Статусdurum: aktif (активен), engelli (заблокирован), beklemede (ожидает), kapali (закрыт)
СписокriskEtiketleri (метки риска, до 20)
Последние операцииsonIslemler[]: id (обязательно), tur (siparis заказ, odeme оплата, iade возврат или свой текст), tutar, durum, aciklama, zaman (сумма, статус, описание, время) — показываются первые 10

Для запросов POST при успехе верните 2xx и JSON-объект (например, {"ok": true}). Чтобы отказать, верните {"hata": "kratkiy_kod"}. Ответ 404 оператор увидит как «не найдено», остальные коды ошибок — как «сайт вернул ошибку».

Запросы выполняются только по https на порт 443 и только к публичным адресам; перенаправления не выполняются. Тайм-аут 10 секунд, ответ — не более 512 КБ.

Вебхуки Pro

Отправляйте события чата в свои системы (CRM, уведомления, отчёты) в реальном времени. Адрес и события выбираются в консоли: Настройки → Webhooks и API; секрет подписи (начинается с whsec_) показывается один раз.

СобытиеКогдаveri (данные)
sohbet.basladiНачался новый чат{ konusma }
sohbet.bittiЧат закрыт{ konusma }
sohbet.kacirildiПосетитель ушёл без ответа{ konusma }
sohbet.cevrimdisiОставлено сообщение в офлайне{ konusma, mesaj }
sohbet.puanlandiПосетитель поставил оценку{ konusmaId, puan, yorum, anket }
sohbet.riskПовысился уровень риска{ konusmaId, risk: { seviye, turler, kelimeler, zaman } }
{
  "id": "6f1c…",                 // ID доставки — не меняется при повторах
  "olay": "sohbet.bitti",          // событие
  "zaman": "2026-10-05T09:12:00.000Z",
  "deneme": 1,                     // попытка
  "veri": {
    "konusma": {                   // разговор
      "id": "…", "durum": "kapandi", "kaynak": "ziyaretci",
      "baslatildi": "…", "kapandi": "…", "kapanisSebep": "kapatildi",
      "ziyaretci": { "no": 128, "ad": "Анна", "kullaniciAdi": "aivanova", "uyeId": "42", "ulke": "RU" },
      "ajan": "Мария", "etiketler": ["оплата"], "puan": 5
    }
  }
}

kullaniciAdi (имя пользователя) и uyeId (ID участника) заполняются только у подтверждённых посетителей.

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

X-Imza-OlayНазвание события
X-Imza-ZamanUnix-секунды
X-Imza-Imzasha256= + HMAC-SHA256(секрет, время + "." + сыроеТело), hex в нижнем регистре
X-Imza-TeslimID доставки (совпадает с id в теле)
<?php
$body = file_get_contents('php://input');
$time = $_SERVER['HTTP_X_IMZA_ZAMAN'] ?? '';
$expected = 'sha256=' . hash_hmac('sha256', $time . '.' . $body, getenv('LAYVCHAT_WEBHOOK_SECRET'));
if (!ctype_digit($time) || abs(time() - (int) $time) > 300 || !hash_equals($expected, $_SERVER['HTTP_X_IMZA_IMZA'] ?? '')) {
  http_response_code(401); exit;
}
$event = json_decode($body, true);
// пропустите, если $event['id'] уже обработан; затем верните 2xx
http_response_code(204);
import crypto from 'node:crypto'
app.post('/layvchat/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const time = req.get('x-imza-zaman') || '', raw = req.body.toString()
  const expected = 'sha256=' + crypto.createHmac('sha256', process.env.LAYVCHAT_WEBHOOK_SECRET).update(`${time}.${raw}`).digest('hex')
  const sig = req.get('x-imza-imza') || ''
  const valid = /^\d+$/.test(time) && Math.abs(Date.now() / 1000 - Number(time)) <= 300 &&
    sig.length === expected.length && crypto.timingSafeEqual(Buffer.from(sig), Buffer.from(expected))
  if (!valid) return res.sendStatus(401)
  const event = JSON.parse(raw)
  // пропустите, если event.id уже обработан
  res.sendStatus(204)
})

Отвечайте 2xx в течение 8 секунд; тяжёлую работу ставьте в очередь. Неудачная доставка повторяется через 5 с, 30 с и 2 мин (не более 4 попыток). После 20 неудачных попыток подряд вебхук отключается; в консоли видны последние 50 доставок, а кнопка «Тест» отправляет пример. Адрес должен быть https, на порту 443 и доступен из интернета.

REST API Pro

Забирайте отчёты и чаты в свои системы. Ключ создаётся в консоли: Настройки → Webhooks и API → API-ключи (только владелец рабочего пространства); секретный ключ показывается один раз. До 5 активных ключей; для каждого можно задать список разрешённых IP. Ключи только для чтения.

curl -u "layv_…:layvs_…" "{{KOK}}/api/v1/canli/rapor?gun=7"
Конечная точкаПараметрыОтвет
GET /api/v1/canli/raporgun (дни): 1, 7, 30 или 90 (по умолчанию 7)Сводка, распределение по дням и часам, работа операторов, оценки, метки, пропущенные чаты
GET /api/v1/canli/sohbetlerbas, bit (с / по, ГГГГ-ММ-ДД, включительно) · durum: acik | kapandi (открыт | закрыт) · adet 1–200 (50) · sayfa (страница){ toplam, sayfa, adet, liste: [konusma] }
GET /api/v1/canli/sohbet/:id—{ sohbet, mesajlar: [{ kim, ajan, metin, dosya, zaman }] } — без внутренних заметок и шёпота
GET /api/v1/ben—Название и область действия ключа

Объект konusma такой же, как в вебхуках. Коды ошибок: 401 ключ отсутствует или недействителен (причина не раскрывается из соображений безопасности) · 402 в вашем тарифе нет API · 404 чат не найден.

Устранение неполадок

Кнопка чата не появляется
Проверьте ключ сайта (начинается с cd_). В консоли может быть выключено Настройки → Общие → Онлайн-чат включен или включено Настройки → Внешний вид виджета → Скрывать на мобильных. Если на сайте есть CSP, добавьте разрешения. В консоли браузера посмотрите ответ на запрос /cd/v/ayar: 404 означает, что ключ не распознан.
Посетитель не отображается как подтверждённый
Частые причины: zaman передан в миллисекундах (нужны секунды), подпись в верхнем регистре, неверные часы сервера, подписанное имя не совпадает с отправленным или в id есть недопустимые символы. Подпись действует 2 часа; обновляйте её на долго открытых страницах.
Вебхуки не приходят
Проверьте историю доставок в консоли: Настройки → Webhooks и API. Адрес должен быть https, отвечать 2xx в течение 8 секунд и не перенаправлять. После 20 ошибок подряд вебхук отключается; после исправления включите его снова.
Карточка клиента не открывается
Карточка открывается только для подтверждённых посетителей и только если у оператора есть право просмотра данных. При проверке подписи ваш сервер должен использовать полный путь (включая путь вашего адреса).