Inicio rápido
- En la consola, copia tu clave de sitio (empieza por
cd_) desde Ajustes → Instalación. - 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>. SustituyeTU_CLAVE_DE_SITIOpor tu clave. - 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
- En el administrador de WordPress, sube el archivo zip en Plugins → Añadir nuevo → Subir plugin y actívalo.
- Pega tu clave de sitio en la página Ajustes → Layvchat.
- 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
| Comando | Nombre en turco | Qué 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) | igual | Escucha 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
| Evento | Nombre en turco | Cuándo | Datos |
|---|---|---|---|
ready | hazir | El widget está listo (los oyentes añadidos después se llaman al instante). | getState() |
open / close | acildi / kapandi | Se abrió / cerró la ventana. | — |
chatStarted | sohbetBasladi | El visitante inició un chat nuevo. | — |
chatEnded | sohbetBitti | El chat terminó. | — |
message | mesaj | El agente o el visitante envió un mensaje. | { kim: 'ajan' | 'ziyaretci', metin, ajan } — remitente, texto, nombre del agente |
unread | okunmamis | Cambió 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.
| Variable | Predeterminado | Qué hace |
|---|---|---|
--layvchat-alt | Margen inferior de los ajustes del widget | Distancia del botón al borde inferior de la página. |
--layvchat-yan | Margen lateral de los ajustes del widget | Distancia del botón al borde derecho (o izquierdo). |
--layvchat-z | 2147483600 | Orden 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)
| Campo | Regla |
|---|---|
id | ID del miembro en tu sitio. De 1 a 64 caracteres: letras, números, _ y -. |
username | Nombre de usuario que ven los agentes. Usa exactamente el valor firmado (se muestran hasta 80 caracteres). |
zaman | Marca 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. |
imza | Firma: 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.
- Implementa en tu servidor los endpoints siguientes (p. ej. bajo
https://tusitio.es/layvchat-api). - 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. - Da a los agentes los permisos necesarios en Ajustes → Agentes (ver datos, notas, bloqueo).
Peticiones
| Petición | Cuerpo | Cuá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
| Tipo | Campos |
|---|---|
| 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í / no | cevrimici (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) |
| Estado | durum: aktif (activo), engelli (bloqueado), beklemede (pendiente), kapali (cerrado) |
| Lista | riskEtiketleri (etiquetas de riesgo, hasta 20) |
| Últimas operaciones | sonIslemler[]: 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.
| Evento | Cuándo | veri (datos) |
|---|---|---|
sohbet.basladi | Empezó un chat nuevo | { konusma } |
sohbet.bitti | Se cerró un chat | { konusma } |
sohbet.kacirildi | El visitante se fue sin respuesta | { konusma } |
sohbet.cevrimdisi | Se dejó un mensaje fuera de línea | { konusma, mesaj } |
sohbet.puanlandi | El visitante dejó una valoración | { konusmaId, puan, yorum, anket } |
sohbet.risk | Subió 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-Olay | Nombre del evento |
X-Imza-Zaman | Segundos Unix |
X-Imza-Imza | sha256= + HMAC-SHA256(secreto, tiempo + "." + cuerpoBruto), hexadecimal en minúsculas |
X-Imza-Teslim | ID 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"
| Endpoint | Parámetros | Respuesta |
|---|---|---|
GET /api/v1/canli/rapor | gun (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/sohbetler | bas, 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:404significa que no se reconoce la clave. - El visitante no aparece como verificado
- Causas más comunes:
zamanenviado en milisegundos (deben ser segundos), firma en mayúsculas, reloj del servidor desajustado, el usuario firmado no coincide con el enviado oidcontiene 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, responder2xxen 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).