Skip to content

Setup guide

Adding the widget to your site takes one line of code. Visitor identity, site integration, webhooks and the API are optional; follow the relevant section when you need it.

<script src="{{KOK}}/cd/w.js?k=YOUR_SITE_KEY" async></script>

Your site key is in the console under Settings → Installation. On WordPress, install it with the plugin without writing code.

Quick start

  1. In the console, copy your site key (it starts with cd_) from Settings → Installation.
  2. Add the code at the top of this page to every page of your site, right before the </body> tag. Replace YOUR_SITE_KEY with your own key.
  3. 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.

Download the WordPress plugin

  1. In WordPress admin, upload the zip file under Plugins → Add New → Upload Plugin and activate it.
  2. Paste your site key on the Settings → Layvchat page.
  3. 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

CommandTurkish nameWhat 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)sameListens 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

EventTurkish nameWhenData
readyhazirThe widget is set up (listeners added later are called immediately).getState()
open / closeacildi / kapandiThe window was opened / closed.—
chatStartedsohbetBasladiThe visitor started a new chat.—
chatEndedsohbetBittiThe chat ended.—
messagemesajThe agent or the visitor sent a message.{ kim: 'ajan' | 'ziyaretci', metin, ajan } — sender, text, agent name
unreadokunmamisThe 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.

VariableDefaultWhat it does
--layvchat-altBottom spacing from widget settingsDistance of the button from the bottom of the page.
--layvchat-yanSide spacing from widget settingsDistance of the button from the right (or left) edge.
--layvchat-z2147483600Stacking 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)
FieldRule
idThe member's ID on your site. 1–64 characters: letters, digits, _ and -.
usernameThe username shown to agents. Use exactly the value you signed (up to 80 characters are shown).
zamanTimestamp: Unix time in seconds (not milliseconds). A signature is valid for 2 hours; your server clock may be up to 5 minutes ahead.
imzaSignature: 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.

  1. Implement the endpoints below on your server (e.g. under https://yoursite.com/layvchat-api).
  2. 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.
  3. Grant agents the permissions they need under Settings → Agents (view details, notes, blocking).

Requests

RequestBodyWhen
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
TypeFields
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 / nocevrimici (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)
Statusdurum: aktif (active), engelli (blocked), beklemede (pending), kapali (closed)
ListriskEtiketleri (risk labels, up to 20)
Recent transactionssonIslemler[]: 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.

EventWhenveri (data)
sohbet.basladiA new chat started{ konusma }
sohbet.bittiA chat was closed{ konusma }
sohbet.kacirildiThe visitor left without a reply{ konusma }
sohbet.cevrimdisiA message was left while offline{ konusma, mesaj }
sohbet.puanlandiThe visitor left a rating{ konusmaId, puan, yorum, anket }
sohbet.riskThe 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-OlayEvent name
X-Imza-ZamanUnix seconds
X-Imza-Imzasha256= + HMAC-SHA256(secret, time + "." + rawBody), lowercase hex
X-Imza-TeslimDelivery 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"
EndpointParametersResponse
GET /api/v1/canli/raporgun (days): 1, 7, 30 or 90 (default 7)Summary, daily and hourly breakdown, agent performance, ratings, tags, missed chats
GET /api/v1/canli/sohbetlerbas, 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/ayar request: 404 means the key was not recognised.
The visitor doesn't show as verified
Most common causes: zaman sent in milliseconds (must be seconds), signature in uppercase, wrong server clock, the signed username differs from the one sent, or id contains 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, return 2xx within 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).