Quick start
- In the console, copy your site key (it starts with
cd_) from Settings → Installation. - Add the code at the top of this page to every page of your site, right before the
</body>tag. ReplaceYOUR_SITE_KEYwith your own key. - Reload the page; the chat button appears in the bottom corner. Visitors can write to you while you are online in the console.
The code does not slow your page down: the loader is small and cached, and the widget loads separately and is versioned. If our server cannot be reached, a simple button is shown that links to the address you enter under Settings → General → Fallback contact link.
WordPress
To install without code, download the plugin. It adds the widget and, if you want, identifies your signed-in members with a signed identity.
- In WordPress admin, upload the zip file under Plugins → Add New → Upload Plugin and activate it.
- Paste your site key on the Settings → Layvchat page.
- Optional: tick Member identity and enter the signing secret (in the console: Settings → Visitor ID → Show secret).
Works with caching plugins: the member identity is not written into the page HTML; it is fetched with a separate, uncached request for each visitor. The signing secret stays on your site's server.
Controlling the widget with code
The install code adds a window.layvchat object to the page. Commands can be used as soon as the code loads: if the widget is not ready yet, calls are queued and run once it is. Every command also has a Turkish name (e.g. open = ac); both work.
Commands
| Command | Turkish name | What it does |
|---|---|---|
open() | ac() | Opens the chat window. |
close() | kapat() | Closes the window. |
toggle() | degistir() | Closes the window if it is open, opens it if it is closed. |
hide() / show() | gizle() / goster() | Removes the button and window from the page entirely / brings them back (e.g. during checkout). |
prefill(text) | doldur() | Opens the window and types the text into the message box; the visitor presses send. |
setVisitor({ ad, eposta, telefon }) | ziyaretci() | Introduces the visitor: form fields are prefilled and the name and email appear in the chat. Keys: ad = name, eposta = email, telefon = phone. |
setAttributes({ key: value }) | ozellik() | Custom details your agents see (cart total, membership tier…). Up to 20 fields; a null value removes the field. Can be updated while a chat is open. |
pageView() | sayfa() | Reports a page change. In single-page apps (React, Vue…) URL changes are detected automatically; use it for custom routing. |
getState() | durum() | Returns { acik, sohbet, okunmamis, ajan }: window open, chat in progress, unread count, agent name. |
on(event, fn) / off(event, fn) | same | Listens to an event / stops listening. |
// "Ask about this product" button
document.querySelector('#ask-product').addEventListener('click', function () {
window.layvchat.prefill('Hi, I would like to know more about the "Leather Backpack".')
})
// Show the signed-in member and their cart to the agent
window.layvchat.setVisitor({ ad: 'Jane Smith', eposta: '[email protected]' })
window.layvchat.setAttributes({ 'Cart total': '$129.90', 'Membership': 'Gold' })
setVisitor and setAttributes data comes from the browser and is unsigned; the console marks it as unverified. To identify a member account securely, use visitor identity.
Events
| Event | Turkish name | When | Data |
|---|---|---|---|
ready | hazir | The widget is set up (listeners added later are called immediately). | getState() |
open / close | acildi / kapandi | The window was opened / closed. | — |
chatStarted | sohbetBasladi | The visitor started a new chat. | — |
chatEnded | sohbetBitti | The chat ended. | — |
message | mesaj | The agent or the visitor sent a message. | { kim: 'ajan' | 'ziyaretci', metin, ajan } — sender, text, agent name |
unread | okunmamis | The unread message count changed. | { n } |
window.layvchat.on('chatStarted', function () {
gtag('event', 'live_chat_started') // analytics
})
window.layvchat.on('message', function (m) {
if (m.kim === 'ajan') console.log(m.ajan + ': ' + m.metin)
})
// The same events are also dispatched on window (English and Turkish names)
window.addEventListener('layvchat:unread', function (e) { badge(e.detail.n) })
If your code runs before the install code, push calls onto the queue:
(window.layvchatKuyruk = window.layvchatKuyruk || []).push(['open'], ['setAttributes', { 'Page': 'Checkout' }])
CSS variables
The button's position and stacking order can be overridden from your site's CSS; the window positions itself relative to the button. Use them if the widget collides with a cookie banner or a mobile bottom bar.
| Variable | Default | What it does |
|---|---|---|
--layvchat-alt | Bottom spacing from widget settings | Distance of the button from the bottom of the page. |
--layvchat-yan | Side spacing from widget settings | Distance of the button from the right (or left) edge. |
--layvchat-z | 2147483600 | Stacking order (z-index). |
@media (max-width: 768px) {
:root { --layvchat-alt: 84px; } /* stay above the mobile bottom bar */
}
If you used Tawk.to or Comm100 before, your existing Tawk_API.maximize() and Comm100API.do('livechat.button.click') calls also open the Layvchat window; you don't need to change your buttons.
Visitor identity
When you identify visitors who are signed in to your site, your agents see which member they are chatting with as verified (blue check mark). For verified visitors the pre-chat form can be skipped, and if site integration is on, the customer card is shown.
Identity is proven by a signature created on your site's server, so nobody can impersonate another member from the browser. Get the signing secret in the console under Settings → Visitor ID → Show secret and keep it on your server only.
Signature
imza = HMAC-SHA256(secret, id + "|" + username + "|" + zaman) → lowercase hex (64 characters)
| Field | Rule |
|---|---|
id | The member's ID on your site. 1–64 characters: letters, digits, _ and -. |
username | The username shown to agents. Use exactly the value you signed (up to 80 characters are shown). |
zaman | Timestamp: Unix time in seconds (not milliseconds). A signature is valid for 2 hours; your server clock may be up to 5 minutes ahead. |
imza | Signature: HMAC-SHA256 in lowercase hex. The secret is used as the key exactly as text. |
Three ways to pass the identity to the widget
1. Write it into the page. If you render pages on the server, add this before the install code. Don't use this method with page caching; one member's signature could be served to other visitors.
<script>
window.layvchatKimlik = { id: "123", username: "jsmith", zaman: 1760000000, imza: "…" }
</script>
2. Report sign-in and sign-out. Suits single-page apps (React, Vue…). A signature is valid for 2 hours, so call it again with a fresh signature on pages that stay open for long.
window.layvchat.identify({ id: "123", username: "jsmith", zaman: 1760000000, imza: "…" })
window.layvchat.identify(null) // after sign-out
3. Identity endpoint. In the console under Settings → Visitor ID → Identity endpoint, enter a relative address on your site that returns the signed identity (e.g. /layvchat/identity). The widget reads it from the same origin with cookies, and refreshes it on page load, every 15 seconds, when the tab regains focus and on in-page navigation. For visitors who are not signed in, return {"id": null}; a non-JSON or failed response counts as "signed out".
<?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
When Settings → Visitor ID → Show signed identities only is on, usernames with an invalid signature are never shown to agents. When it is off, they are used only as a display name and are not treated as verified.
Content Security Policy (CSP)
If your site uses a CSP, allow the Layvchat address as follows:
script-src {{KOK}}
connect-src {{KOK}} {{WS}} ('self' too if you use an identity endpoint)
frame-src {{KOK}}
style-src 'unsafe-inline'
img-src https: (only if you use a custom button image)
media-src {{KOK}} (only if you use a custom notification sound)
The widget sets no cookies on your site. The visitor key and the backup link are kept in localStorage with the layv_cd_ prefix; chat content and the access token stay only in the Layvchat window's own origin.
Site integration Pro
During a chat, your agents see the verified visitor's account details (recent orders, status…) and can take the actions you allow. Layvchat fetches this information from a few endpoints on your server using signed requests. Integration works only for verified visitors.
- Implement the endpoints below on your server (e.g. under
https://yoursite.com/layvchat-api). - Enter this address in the console under Settings → Site integration. The signing secret (starts with
entg_) is shown only once; store it on your server. - Grant agents the permissions they need under Settings → Agents (view details, notes, blocking).
Requests
| Request | Body | When |
|---|---|---|
GET /uye/:id | — | When an agent opens the chat (customer card) |
POST /uye/:id/not | { not, yapan } | Customer note |
POST /uye/:id/durum | { engelli: true | false, yapan } | Block / unblock the account |
POST /uye/:id/mesaj | { baslik, govde, yapan } | Reply to your site's inbox (title, body) |
:id is the id from the visitor identity. yapan is the agent who performed the action (canli-destek:username).
Verify the signature
Every request comes with the X-Imza-Zaman (Unix seconds) and X-Imza headers:
X-Imza = HMAC-SHA256(secret, time + "." + METHOD + "." + fullPath + "." + rawBody) → lowercase hex
fullPath is the request path including the path of your address: if the address is https://yoursite.com/layvchat-api, it is /layvchat-api/uye/42. For GET requests the body is an empty string. Reject timestamps older than 5 minutes.
<?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: needs the raw body → 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))
}
Customer card response
GET /uye/:id must return a JSON object. Only uye.id is required; unknown fields are ignored. If the member doesn't exist, return 404.
{
"uye": {
"id": "42", "kullaniciAdi": "jsmith", "ad": "Jane", "soyad": "Smith",
"eposta": "[email protected]", "telefon": "5551112233", "ulke": "GB", "paraBirimi": "GBP",
"toplamHarcama": 5000, "toplamIade": 320,
"durum": "aktif", "kayitTarihi": "2025-03-14T10:00:00Z", "sonGiris": "2026-10-04T21:10:00Z",
"riskEtiketleri": ["new account"]
},
"sonIslemler": [
{ "id": "S1001", "tur": "siparis", "tutar": 500, "durum": "shipped", "aciklama": "2 items", "zaman": "2026-10-04T20:00:00Z" }
]
}
All customer fields
| Type | Fields |
|---|---|
| Text (up to 200 characters) | kullaniciAdi (username), eposta (email), ad (first name), ikinciAd (middle name), soyad (last name), telefon, telefonKodu (phone, dialling code), sehir (city), dil (language), sonGirisIp, kayitIp (last sign-in / sign-up IP), yoneticiNotu (admin note) |
| Country code (2 letters) | ulke, kayitUlke (country, sign-up country) |
| Currency (3 letters) | paraBirimi |
| Yes / no | cevrimici (online), epostaDogrulandi, telefonDogrulandi (email / phone verified) |
| Time (ISO 8601) | sonCevrimici, sonGiris, kayitTarihi (last online, last sign-in, signed up) |
| Date (YYYY-MM-DD) | dogumTarihi (date of birth) |
| Amount (number) | toplamHarcama, toplamIade (total spent, total refunded) |
| Status | durum: aktif (active), engelli (blocked), beklemede (pending), kapali (closed) |
| List | riskEtiketleri (risk labels, up to 20) |
| Recent transactions | sonIslemler[]: id (required), tur (siparis order, odeme payment, iade refund, or your own text), tutar, durum, aciklama, zaman (amount, status, description, time) — the first 10 are shown |
For POST requests, return 2xx and a JSON object on success (e.g. {"ok": true}). To refuse, return {"hata": "short_code"}. 404 is shown to the agent as "not found", other error codes as "the site returned an error".
Requests are made only over https to port 443 and to public internet addresses; redirects are not followed. Timeout is 10 seconds; responses are limited to 512 KB.
Webhooks Pro
Send chat events to your own systems (CRM, notifications, reporting) in real time. Choose the address and events in the console under Settings → Webhooks and API; the signing secret (starts with whsec_) is shown once.
| Event | When | veri (data) |
|---|---|---|
sohbet.basladi | A new chat started | { konusma } |
sohbet.bitti | A chat was closed | { konusma } |
sohbet.kacirildi | The visitor left without a reply | { konusma } |
sohbet.cevrimdisi | A message was left while offline | { konusma, mesaj } |
sohbet.puanlandi | The visitor left a rating | { konusmaId, puan, yorum, anket } |
sohbet.risk | The risk level increased | { konusmaId, risk: { seviye, turler, kelimeler, zaman } } |
{
"id": "6f1c…", // delivery ID — stays the same on retries
"olay": "sohbet.bitti", // event
"zaman": "2026-10-05T09:12:00.000Z",
"deneme": 1, // attempt
"veri": {
"konusma": { // conversation
"id": "…", "durum": "kapandi", "kaynak": "ziyaretci",
"baslatildi": "…", "kapandi": "…", "kapanisSebep": "kapatildi",
"ziyaretci": { "no": 128, "ad": "Jane", "kullaniciAdi": "jsmith", "uyeId": "42", "ulke": "GB" },
"ajan": "Alex", "etiketler": ["payment"], "puan": 5
}
}
}
kullaniciAdi (username) and uyeId (member ID) are filled only for verified visitors.
Headers and signature
X-Imza-Olay | Event name |
X-Imza-Zaman | Unix seconds |
X-Imza-Imza | sha256= + HMAC-SHA256(secret, time + "." + rawBody), lowercase hex |
X-Imza-Teslim | Delivery ID (same as id in the body) |
<?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);
// skip if $event['id'] was already processed; then return 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)
// skip if event.id was already processed
res.sendStatus(204)
})
Return 2xx within 8 seconds; queue heavy work. A failed delivery is retried after 5 s, 30 s and 2 min (up to 4 attempts). After 20 consecutive failed attempts the webhook is disabled; in the console you can see the last 50 deliveries and send a sample with "Test". The address must be https on port 443 and on the public internet.
REST API Pro
Pull reports and chats into your own systems. Create a key in the console under Settings → Webhooks and API → API keys (workspace owner only); the secret key is shown once. Up to 5 active keys; each key can have an allowed IP list. Keys are read-only.
curl -u "layv_…:layvs_…" "{{KOK}}/api/v1/canli/rapor?gun=7"
| Endpoint | Parameters | Response |
|---|---|---|
GET /api/v1/canli/rapor | gun (days): 1, 7, 30 or 90 (default 7) | Summary, daily and hourly breakdown, agent performance, ratings, tags, missed chats |
GET /api/v1/canli/sohbetler | bas, bit (from / to, YYYY-MM-DD, inclusive) · durum: acik | kapandi (open | closed) · adet 1–200 (50) · sayfa (page) | { toplam, sayfa, adet, liste: [konusma] } |
GET /api/v1/canli/sohbet/:id | — | { sohbet, mesajlar: [{ kim, ajan, metin, dosya, zaman }] } — internal notes and whispers are excluded |
GET /api/v1/ben | — | The key's name and scope |
The konusma object is the same as in webhooks. Error codes: 401 key missing or invalid (the reason is not disclosed for security) · 402 your plan does not include the API · 404 chat not found.
Troubleshooting
- The chat button doesn't appear
- Check the site key (it starts with
cd_). In the console, Settings → General → Live chat enabled may be off, or Settings → Widget appearance → Hide on mobile may be on. If your site has a CSP, add the permissions. In the browser console, look at the response of the/cd/v/ayarrequest:404means the key was not recognised. - The visitor doesn't show as verified
- Most common causes:
zamansent in milliseconds (must be seconds), signature in uppercase, wrong server clock, the signed username differs from the one sent, oridcontains disallowed characters. A signature is valid for 2 hours; refresh it on pages that stay open for long. - Webhooks don't arrive
- Check the delivery history in the console under Settings → Webhooks and API. The address must be
https, return2xxwithin 8 seconds and must not redirect. After 20 consecutive errors the webhook is disabled; turn it back on once fixed. - The customer card doesn't open
- The card opens only for verified visitors and only if the agent has permission to view details. When verifying the signature, your server must use the full path (including your address's path).