Saltar al contenido

Guía de instalación

Añadir el widget a tu sitio es una sola línea de código. La identidad del visitante, la integración con el sitio, los webhooks y la API son opcionales; sigue la sección correspondiente cuando la necesites.

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

Tu clave de sitio está en la consola, en Ajustes → Instalación. Si usas WordPress, instálalo con el plugin sin escribir código.

Inicio rápido

  1. En la consola, copia tu clave de sitio (empieza por cd_) desde Ajustes → Instalación.
  2. Añade el código del principio de esta página en todas las páginas de tu sitio, justo antes de la etiqueta </body>. Sustituye TU_CLAVE_DE_SITIO por tu clave.
  3. Recarga la página; el botón de chat aparece en la esquina inferior. Mientras estés en línea en la consola, los visitantes pueden escribirte.

El código no ralentiza tu página: el cargador es pequeño y se almacena en caché, y el widget se carga aparte y con versión. Si no se puede acceder a nuestro servidor, se muestra un botón sencillo hacia la dirección que indiques en Ajustes → General → Enlace de contacto alternativo.

WordPress

Para instalar sin código, descarga el plugin. Añade el widget y, si quieres, identifica a tus miembros con sesión iniciada mediante una identidad firmada.

Descargar el plugin de WordPress

  1. En el administrador de WordPress, sube el archivo zip en Plugins → Añadir nuevo → Subir plugin y actívalo.
  2. Pega tu clave de sitio en la página Ajustes → Layvchat.
  3. Opcional: marca Identidad de miembros e introduce el secreto de firma (en la consola: Ajustes → ID del visitante → Mostrar secreto).

Compatible con plugins de caché: la identidad del miembro no se escribe en el HTML de la página; se obtiene para cada visitante con una petición aparte y sin caché. El secreto de firma se queda en el servidor de tu sitio.

Controlar el widget con código

El código de instalación añade el objeto window.layvchat a la página. Los comandos se pueden usar en cuanto se carga el código: si el widget aún no está listo, las llamadas esperan y se ejecutan cuando lo esté. Cada comando tiene también un nombre en turco (p. ej. open = ac); ambos funcionan.

Comandos

ComandoNombre en turcoQué hace
open()ac()Abre la ventana de chat.
close()kapat()Cierra la ventana.
toggle()degistir()Cierra la ventana si está abierta y la abre si está cerrada.
hide() / show()gizle() / goster()Quita por completo el botón y la ventana de la página / los vuelve a mostrar (p. ej. durante el pago).
prefill(texto)doldur()Abre la ventana y escribe el texto en el cuadro de mensaje; el visitante pulsa enviar.
setVisitor({ ad, eposta, telefon })ziyaretci()Presenta al visitante: los campos del formulario se rellenan y el nombre y el correo aparecen en el chat. Claves: ad = nombre, eposta = correo, telefon = teléfono.
setAttributes({ clave: valor })ozellik()Datos personalizados que ven tus agentes (importe del carrito, nivel de membresía…). Hasta 20 campos; el valor null elimina un campo. Se puede actualizar con el chat abierto.
pageView()sayfa()Notifica un cambio de página. En aplicaciones de una sola página (React, Vue…) los cambios de URL se detectan solos; úsalo para enrutamientos propios.
getState()durum()Devuelve { acik, sohbet, okunmamis, ajan }: ventana abierta, chat en curso, número de no leídos, nombre del agente.
on(evento, fn) / off(evento, fn)igualEscucha un evento / deja de escucharlo.
// Botón "Preguntar por este producto"
document.querySelector('#preguntar-producto').addEventListener('click', function () {
  window.layvchat.prefill('Hola, quisiera más información sobre la "Mochila de cuero".')
})

// Mostrar al agente el miembro con sesión iniciada y su carrito
window.layvchat.setVisitor({ ad: 'Lucía García', eposta: '[email protected]' })
window.layvchat.setAttributes({ 'Carrito': '129,90 €', 'Membresía': 'Oro' })

Los datos de setVisitor y setAttributes vienen del navegador y no están firmados; la consola los muestra como no verificados. Para identificar una cuenta de miembro con seguridad, usa la identidad del visitante.

Eventos

EventoNombre en turcoCuándoDatos
readyhazirEl widget está listo (los oyentes añadidos después se llaman al instante).getState()
open / closeacildi / kapandiSe abrió / cerró la ventana.—
chatStartedsohbetBasladiEl visitante inició un chat nuevo.—
chatEndedsohbetBittiEl chat terminó.—
messagemesajEl agente o el visitante envió un mensaje.{ kim: 'ajan' | 'ziyaretci', metin, ajan } — remitente, texto, nombre del agente
unreadokunmamisCambió el número de mensajes no leídos.{ n }
window.layvchat.on('chatStarted', function () {
  gtag('event', 'chat_en_vivo_iniciado')        // analítica
})
window.layvchat.on('message', function (m) {
  if (m.kim === 'ajan') console.log(m.ajan + ': ' + m.metin)
})

// Los mismos eventos se emiten también en window (nombres en inglés y en turco)
window.addEventListener('layvchat:unread', function (e) { badge(e.detail.n) })

Si tu código se ejecuta antes que el código de instalación, añade las llamadas a la cola:

(window.layvchatKuyruk = window.layvchatKuyruk || []).push(['open'], ['setAttributes', { 'Página': 'Pago' }])

Variables CSS

La posición y el orden de apilamiento del botón se pueden sobrescribir desde el CSS de tu sitio; la ventana se coloca respecto al botón. Útil si el widget choca con un aviso de cookies o una barra inferior en móvil.

VariablePredeterminadoQué hace
--layvchat-altMargen inferior de los ajustes del widgetDistancia del botón al borde inferior de la página.
--layvchat-yanMargen lateral de los ajustes del widgetDistancia del botón al borde derecho (o izquierdo).
--layvchat-z2147483600Orden de apilamiento (z-index).
@media (max-width: 768px) {
  :root { --layvchat-alt: 84px; }   /* quedarse encima de la barra inferior móvil */
}

Si antes usabas Tawk.to o Comm100, tus llamadas existentes Tawk_API.maximize() y Comm100API.do('livechat.button.click') también abren la ventana de Layvchat; no hace falta cambiar tus botones.

Identidad del visitante

Si identificas a los visitantes con sesión iniciada en tu sitio, tus agentes ven como verificado (marca azul) con qué miembro están chateando. Con visitantes verificados se puede omitir el formulario previo al chat y, si la integración con el sitio está activa, se muestra la ficha del cliente.

La identidad se demuestra con una firma creada en el servidor de tu sitio, así nadie puede suplantar a otro miembro desde el navegador. Obtén el secreto de firma en la consola en Ajustes → ID del visitante → Mostrar secreto y guárdalo solo en tu servidor.

Firma

imza = HMAC-SHA256(secreto, id + "|" + username + "|" + zaman)   → hexadecimal en minúsculas (64 caracteres)
CampoRegla
idID del miembro en tu sitio. De 1 a 64 caracteres: letras, números, _ y -.
usernameNombre de usuario que ven los agentes. Usa exactamente el valor firmado (se muestran hasta 80 caracteres).
zamanMarca de tiempo: hora Unix en segundos (no milisegundos). Una firma es válida 2 horas; el reloj de tu servidor puede ir como mucho 5 minutos adelantado.
imzaFirma: HMAC-SHA256 en hexadecimal en minúsculas. El secreto se usa como clave tal cual, como texto.

Tres formas de pasar la identidad al widget

1. Escribirla en la página. Si generas las páginas en el servidor, añade esto antes del código de instalación. No lo uses con caché de páginas: la firma de un miembro podría servirse a otros visitantes.

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

2. Avisar al iniciar y cerrar sesión. Adecuado para aplicaciones de una sola página (React, Vue…). Como una firma dura 2 horas, vuelve a llamarlo con una firma nueva en páginas que quedan abiertas mucho tiempo.

window.layvchat.identify({ id: "123", username: "lgarcia", zaman: 1760000000, imza: "…" })
window.layvchat.identify(null)   // tras cerrar sesión

3. Endpoint de identidad. En la consola, en Ajustes → ID del visitante → Endpoint de identidad, escribe una dirección relativa de tu sitio que devuelva la identidad firmada (p. ej. /layvchat/identity). El widget la lee desde el mismo origen con cookies y la actualiza al cargar la página, cada 15 segundos, al volver a la pestaña y al navegar dentro de la página. Para visitantes sin sesión, devuelve {"id": null}; una respuesta que no sea JSON o que falle cuenta como "sesión cerrada".

<?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

Con Ajustes → ID del visitante → Mostrar solo identidades firmadas activado, los agentes nunca ven nombres de usuario con firma no válida. Desactivado, solo se usan como nombre visible y no cuentan como verificados.

Content Security Policy (CSP)

Si tu sitio usa CSP, permite la dirección de Layvchat así:

script-src   {{KOK}}
connect-src  {{KOK}} {{WS}}     (también 'self' si usas un endpoint de identidad)
frame-src    {{KOK}}
style-src    'unsafe-inline'
img-src      https:            (solo con una imagen de botón propia)
media-src    {{KOK}}           (solo con un sonido de aviso propio)

El widget no crea cookies en tu sitio. La clave del visitante y el enlace alternativo se guardan en localStorage con el prefijo layv_cd_; el contenido del chat y el token de acceso se quedan solo en el origen propio de la ventana de Layvchat.

Integración con el sitio Pro

Durante el chat, tus agentes ven los datos de la cuenta del visitante verificado (últimos pedidos, estado…) y pueden hacer las acciones que permitas. Layvchat obtiene estos datos de unos pocos endpoints de tu servidor mediante peticiones firmadas. La integración solo funciona con visitantes verificados.

  1. Implementa en tu servidor los endpoints siguientes (p. ej. bajo https://tusitio.es/layvchat-api).
  2. Escribe esa dirección en la consola, en Ajustes → Integración con el sitio. El secreto de firma (empieza por entg_) se muestra una sola vez; guárdalo en tu servidor.
  3. Da a los agentes los permisos necesarios en Ajustes → Agentes (ver datos, notas, bloqueo).

Peticiones

PeticiónCuerpoCuándo
GET /uye/:id—Cuando un agente abre el chat (ficha del cliente)
POST /uye/:id/not{ not, yapan }Nota del cliente
POST /uye/:id/durum{ engelli: true | false, yapan }Bloquear / desbloquear la cuenta
POST /uye/:id/mesaj{ baslik, govde, yapan }Respuesta en el buzón de tu sitio (título, texto)

:id es el id de la identidad del visitante. yapan indica el agente que hizo la acción (canli-destek:nombredeusuario).

Verifica la firma

Cada petición llega con las cabeceras X-Imza-Zaman (segundos Unix) y X-Imza:

X-Imza = HMAC-SHA256(secreto, tiempo + "." + MÉTODO + "." + rutaCompleta + "." + cuerpoBruto)   → hexadecimal en minúsculas

rutaCompleta es la ruta de la petición incluida la ruta de tu dirección: si la dirección es https://tusitio.es/layvchat-api, es /layvchat-api/uye/42. En peticiones GET el cuerpo es una cadena vacía. Rechaza marcas de tiempo de más de 5 minutos.

<?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: necesita el cuerpo bruto → 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))
}

Respuesta de la ficha del cliente

GET /uye/:id debe devolver un objeto JSON. Solo uye.id es obligatorio; los campos desconocidos se ignoran. Si el miembro no existe, devuelve 404.

{
  "uye": {
    "id": "42", "kullaniciAdi": "lgarcia", "ad": "Lucía", "soyad": "García",
    "eposta": "[email protected]", "telefon": "5551112233", "ulke": "ES", "paraBirimi": "EUR",
    "toplamHarcama": 5000, "toplamIade": 320,
    "durum": "aktif", "kayitTarihi": "2025-03-14T10:00:00Z", "sonGiris": "2026-10-04T21:10:00Z",
    "riskEtiketleri": ["cuenta nueva"]
  },
  "sonIslemler": [
    { "id": "S1001", "tur": "siparis", "tutar": 500, "durum": "enviado", "aciklama": "2 artículos", "zaman": "2026-10-04T20:00:00Z" }
  ]
}
Todos los campos del cliente
TipoCampos
Texto (hasta 200 caracteres)kullaniciAdi (usuario), eposta (correo), ad (nombre), ikinciAd (segundo nombre), soyad (apellido), telefon, telefonKodu (teléfono, prefijo), sehir (ciudad), dil (idioma), sonGirisIp, kayitIp (IP del último acceso / del registro), yoneticiNotu (nota del administrador)
Código de país (2 letras)ulke, kayitUlke (país, país de registro)
Moneda (3 letras)paraBirimi
Sí / nocevrimici (en línea), epostaDogrulandi, telefonDogrulandi (correo / teléfono verificado)
Fecha y hora (ISO 8601)sonCevrimici, sonGiris, kayitTarihi (última conexión, último acceso, registro)
Fecha (AAAA-MM-DD)dogumTarihi (fecha de nacimiento)
Importe (número)toplamHarcama, toplamIade (gasto total, reembolso total)
Estadodurum: aktif (activo), engelli (bloqueado), beklemede (pendiente), kapali (cerrado)
ListariskEtiketleri (etiquetas de riesgo, hasta 20)
Últimas operacionessonIslemler[]: id (obligatorio), tur (siparis pedido, odeme pago, iade reembolso, o tu propio texto), tutar, durum, aciklama, zaman (importe, estado, descripción, fecha) — se muestran las 10 primeras

En peticiones POST, si todo va bien devuelve 2xx y un objeto JSON (p. ej. {"ok": true}). Para rechazar, devuelve {"hata": "codigo_corto"}. El agente ve un 404 como "no encontrado" y los demás códigos de error como "el sitio devolvió un error".

Las peticiones se hacen solo por https al puerto 443 y a direcciones públicas de internet; no se siguen redirecciones. Tiempo de espera de 10 segundos; respuestas de 512 KB como máximo.

Webhooks Pro

Envía los eventos de chat a tus propios sistemas (CRM, avisos, informes) en tiempo real. Elige la dirección y los eventos en la consola, en Ajustes → Webhooks y API; el secreto de firma (empieza por whsec_) se muestra una vez.

EventoCuándoveri (datos)
sohbet.basladiEmpezó un chat nuevo{ konusma }
sohbet.bittiSe cerró un chat{ konusma }
sohbet.kacirildiEl visitante se fue sin respuesta{ konusma }
sohbet.cevrimdisiSe dejó un mensaje fuera de línea{ konusma, mesaj }
sohbet.puanlandiEl visitante dejó una valoración{ konusmaId, puan, yorum, anket }
sohbet.riskSubió el nivel de riesgo{ konusmaId, risk: { seviye, turler, kelimeler, zaman } }
{
  "id": "6f1c…",                 // ID de entrega — se mantiene en los reintentos
  "olay": "sohbet.bitti",          // evento
  "zaman": "2026-10-05T09:12:00.000Z",
  "deneme": 1,                     // intento
  "veri": {
    "konusma": {                   // conversación
      "id": "…", "durum": "kapandi", "kaynak": "ziyaretci",
      "baslatildi": "…", "kapandi": "…", "kapanisSebep": "kapatildi",
      "ziyaretci": { "no": 128, "ad": "Lucía", "kullaniciAdi": "lgarcia", "uyeId": "42", "ulke": "ES" },
      "ajan": "Pablo", "etiketler": ["pago"], "puan": 5
    }
  }
}

kullaniciAdi (usuario) y uyeId (ID de miembro) solo se rellenan con visitantes verificados.

Cabeceras y firma

X-Imza-OlayNombre del evento
X-Imza-ZamanSegundos Unix
X-Imza-Imzasha256= + HMAC-SHA256(secreto, tiempo + "." + cuerpoBruto), hexadecimal en minúsculas
X-Imza-TeslimID de entrega (igual que id en el cuerpo)
<?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);
// omitir si $event['id'] ya se procesó; después devolver 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)
  // omitir si event.id ya se procesó
  res.sendStatus(204)
})

Responde 2xx en menos de 8 segundos; pasa el trabajo pesado a una cola. Una entrega fallida se reintenta a los 5 s, 30 s y 2 min (4 intentos como máximo). Tras 20 intentos fallidos seguidos, el webhook se desactiva; en la consola puedes ver las últimas 50 entregas y enviar una prueba con "Probar". La dirección debe ser https, en el puerto 443 y accesible desde internet.

API REST Pro

Lleva informes y chats a tus propios sistemas. Crea una clave en la consola, en Ajustes → Webhooks y API → Claves de API (solo el propietario del espacio de trabajo); la clave secreta se muestra una vez. Hasta 5 claves activas; cada una puede tener una lista de IP permitidas. Las claves son de solo lectura.

curl -u "layv_…:layvs_…" "{{KOK}}/api/v1/canli/rapor?gun=7"
EndpointParámetrosRespuesta
GET /api/v1/canli/raporgun (días): 1, 7, 30 o 90 (7 por defecto)Resumen, reparto diario y por horas, rendimiento de agentes, valoraciones, etiquetas, chats perdidos
GET /api/v1/canli/sohbetlerbas, bit (desde / hasta, AAAA-MM-DD, incluidos) · durum: acik | kapandi (abierto | cerrado) · adet 1–200 (50) · sayfa (página){ toplam, sayfa, adet, liste: [konusma] }
GET /api/v1/canli/sohbet/:id—{ sohbet, mesajlar: [{ kim, ajan, metin, dosya, zaman }] } — sin notas internas ni susurros
GET /api/v1/ben—Nombre y alcance de la clave

El objeto konusma es el mismo que en los webhooks. Códigos de error: 401 clave ausente o no válida (por seguridad no se indica el motivo) · 402 tu plan no incluye la API · 404 chat no encontrado.

Solución de problemas

No aparece el botón de chat
Comprueba la clave de sitio (empieza por cd_). En la consola puede que Ajustes → General → Chat en vivo activado esté desactivado o Ajustes → Apariencia del widget → Ocultar en móviles activado. Si tu sitio tiene CSP, añade los permisos. En la consola del navegador, mira la respuesta de la petición /cd/v/ayar: 404 significa que no se reconoce la clave.
El visitante no aparece como verificado
Causas más comunes: zaman enviado en milisegundos (deben ser segundos), firma en mayúsculas, reloj del servidor desajustado, el usuario firmado no coincide con el enviado o id contiene caracteres no permitidos. Una firma dura 2 horas; renuévala en páginas que permanecen abiertas mucho tiempo.
No llegan los webhooks
Revisa el historial de entregas en la consola, en Ajustes → Webhooks y API. La dirección debe ser https, responder 2xx en menos de 8 segundos y no redirigir. Tras 20 errores seguidos el webhook se desactiva; vuelve a activarlo cuando lo corrijas.
No se abre la ficha del cliente
La ficha solo se abre con visitantes verificados y si el agente tiene permiso para ver datos. Al verificar la firma, tu servidor debe usar la ruta completa (incluida la ruta de tu dirección).