Быстрый старт
- В консоли скопируйте ключ сайта (начинается с
cd_) на странице Настройки → Установка. - Добавьте код из начала этой страницы на все страницы сайта, прямо перед тегом
</body>. ЗаменитеКЛЮЧ_ВАШЕГО_САЙТАсвоим ключом. - Обновите страницу: в нижнем углу появится кнопка чата. Пока вы в сети в консоли, посетители могут вам писать.
Код не замедляет страницу: загрузчик маленький и кэшируется, а виджет загружается отдельно и с версией. Если наш сервер недоступен, показывается простая кнопка со ссылкой, указанной в Настройки → Общие → Резервная ссылка для связи.
WordPress
Чтобы установить без кода, скачайте плагин. Он добавляет виджет и при желании передаёт подписанную личность авторизованных участников.
- В админке WordPress загрузите zip-файл через Плагины → Добавить новый → Загрузить плагин и активируйте его.
- Вставьте ключ сайта на странице Настройки → Layvchat.
- Необязательно: отметьте Личность участника и введите секрет подписи (в консоли: Настройки → 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 приходят из браузера и не подписаны; в консоли они отмечены как непроверенные. Чтобы надёжно определить аккаунт участника, используйте идентификацию посетителя.
События
| Событие | Турецкое имя | Когда | Данные |
|---|---|---|---|
ready | hazir | Виджет установлен (подписчики, добавленные позже, вызываются сразу). | getState() |
open / close | acildi / kapandi | Окно открыто / закрыто. | — |
chatStarted | sohbetBasladi | Посетитель начал новый чат. | — |
chatEnded | sohbetBitti | Чат завершён. | — |
message | mesaj | Оператор или посетитель отправил сообщение. | { kim: 'ajan' | 'ziyaretci', metin, ajan } — отправитель, текст, имя оператора |
unread | okunmamis | Изменилось число непрочитанных сообщений. | { 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-z | 2147483600 | Порядок слоёв (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 символа)
| Поле | Правило |
|---|---|
id | ID участника на вашем сайте. 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 получает эти данные с нескольких конечных точек вашего сервера подписанными запросами. Интеграция работает только для подтверждённых посетителей.
- Реализуйте на сервере конечные точки ниже (например, под
https://vashsait.ru/layvchat-api). - Укажите этот адрес в консоли: Настройки → Интеграция с сайтом. Секрет подписи (начинается с
entg_) показывается только один раз; сохраните его на сервере. - Выдайте операторам нужные права в разделе Настройки → Операторы (просмотр данных, заметки, блокировка).
Запросы
| Запрос | Тело | Когда |
|---|---|---|
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-Zaman | Unix-секунды |
X-Imza-Imza | sha256= + HMAC-SHA256(секрет, время + "." + сыроеТело), hex в нижнем регистре |
X-Imza-Teslim | ID доставки (совпадает с 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/rapor | gun (дни): 1, 7, 30 или 90 (по умолчанию 7) | Сводка, распределение по дням и часам, работа операторов, оценки, метки, пропущенные чаты |
GET /api/v1/canli/sohbetler | bas, 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 ошибок подряд вебхук отключается; после исправления включите его снова. - Карточка клиента не открывается
- Карточка открывается только для подтверждённых посетителей и только если у оператора есть право просмотра данных. При проверке подписи ваш сервер должен использовать полный путь (включая путь вашего адреса).