Widget del sitio

El widget es el chat de tu bot directamente en tu sitio web: un botón redondo en la esquina de la página desde el que se abre una conversación. Para el visitante es una vía más de contacto; para el bot es un canal corriente, con las mismas reacciones y los mismos diálogos de operador que en los mensajeros. Panorama de la multicanalidad en Canales.

Instalarlo consiste en pegar un fragmento de código en la plantilla del sitio. Todo lo demás se configura en el panel y se aplica sin volver a tocar el sitio.

Dónde obtener el código

En el panel abre Widget → bloque Instalación en el sitio → campo Código para insertar. El botón Copiar deja el fragmento completo en el portapapeles.

El código se ve así, con tu propia clave en lugar de los puntos suspensivos:

<script>
  window.mybot = { key: "eu-1a2b3c4d-..." };
</script>
<script async src="https://getmybot.dev/loader.js"></script>

Si en lugar del código ves el aviso de que aún no se ha emitido una clave de instalación para este bot, el canal del widget no está conectado. Escribe a soporte: la clave se emite al conectar el canal.

Debajo del código hay una indicación de a qué host llamará el widget. Si tu sitio usa CSP (Content Security Policy), permite a ese host cargar scripts y recibir peticiones; de lo contrario el navegador bloqueará el widget en silencio.

Dónde va el código

El fragmento va en el HTML de cada página donde deba aparecer el widget. En la práctica, una sola vez en la plantilla común del sitio: el pie, el campo «código antes de </body>» de tu CMS o un contenedor del gestor de etiquetas.

Las reglas son cortas:

  • El mejor sitio es justo antes del </body> de cierre. En <head> también funciona, pero entonces el navegador dedica tiempo al widget antes que a tu contenido.
  • El orden de las dos etiquetas importa: la primera fija la clave, la segunda carga el widget. No las intercambies ni las separes por la página.
  • La segunda etiqueta lleva async y no bloquea el dibujado. No quites ese atributo.
  • Un fragmento por página. Dos copias son dos intentos de arrancar el widget.

A partir de ahí el cargador determina desde qué centro de datos se atiende a tu bot y trae de allí el código del widget. Por tu parte no hace falta nada más.

La clave de instalación es pública

La clave de la primera etiqueta es un identificador público de tu bot, no una contraseña. Está en el código fuente de la página: cualquier visitante puede abrir «Ver código fuente» y leerla. Todos los widgets de chat funcionan así, y no pasa nada.

Lo importante:

  • Con la clave no se puede entrar al panel, leer diálogos ajenos, exportar tu base de suscriptores ni cambiar nada del bot. Habilita exactamente una cosa: iniciar una conversación nueva con este bot.
  • No trates la clave como un secreto: esconderla u ofuscarla no aporta nada.
  • Si hay que sustituirla (por ejemplo, tras separarte de un proveedor), lo hace soporte. Tras el cambio el código antiguo del sitio deja de funcionar y hay que actualizarlo.

Lista de dominios permitidos

El widget tiene una lista de dominios permitidos (origin allowlist): las direcciones de sitio desde las que se le permite funcionar. Se define al conectar el canal y se puede cambiar después sin reemitir la clave, así que tu código en el sitio no cambia.

Las entradas son orígenes: esquema, host y, si no es el estándar, puerto.

https://example.com
https://www.example.com
https://shop.example.com
http://localhost:3000

La comparación es exacta, carácter a carácter:

  • No hay comodines. https://*.example.com no funciona: enumera los subdominios uno a uno.
  • example.com y www.example.com son entradas distintas. Si tu sitio responde en ambas, añade las dos.
  • http:// y https:// también son entradas distintas. Normalmente basta https://, pero un entorno de pruebas en http:// hay que añadirlo explícitamente.
  • Sin rutas: https://example.com/shop se rechaza; un origen es solo la dirección del sitio.

Si tu dominio real no está en la lista, el widget no arrancará. El navegador del visitante recibe un rechazo «origin not allowed» y el chat simplemente no se abre. Suele aparecer tras mudarse a un dominio nuevo, añadir un subdominio o lanzar una segunda versión idiomática en dirección propia: cada una necesita su propia entrada.

Si la lista está vacía no hay restricción y el widget funciona desde cualquier sitio. Considéralo «aún sin configurar», no una apertura deliberada: en cuanto conozcas tus dominios, anótalos.

Qué protege esta restricción y qué no

Sí protege: impide que otro sitio incruste tu widget. De lo contrario alguien podría copiar tu fragmento en su página y los navegadores de visitantes reales entregarían a ese sitio el contenido de las conversaciones con tu bot. Eso es exactamente lo que cierra la lista.

No protege: no defiende frente a quien copió la clave y llama a la plataforma directamente, no desde un navegador sino, por ejemplo, con un script. La cabecera con la dirección del sitio la pone el navegador; un programa fuera del navegador puede omitirla o enviar cualquier cosa. Trátala como una restricción de incrustación, no como una frontera de seguridad. Frente al abuso protegen los límites de frecuencia y la moderación de diálogos de la plataforma, no esta lista.

Apariencia

En WidgetApariencia se configuran:

  • Posición: el botón en la esquina inferior izquierda o derecha.
  • Color de acento: color del botón y de los elementos del chat. Usa el color de tu marca para que el widget no parezca ajeno.
  • Nombre del chat: etiqueta del botón y título del panel. Suele ser el nombre de la empresa o el nombre con el que se presenta el bot.

Al lado hay una vista previa: un widget vivo que muestra el resultado antes de guardar.

El widget todavía no tiene mensaje de bienvenida: son esos tres ajustes y nada más. El primer mensaje lo envía el bot como respuesta al visitante, según tus reacciones habituales.

Los cambios llegan solos a tu sitio: el widget lee su configuración al cargar la página, así que el código insertado nunca hay que editarlo.

Los visitantes son anónimos

El visitante no rellena nada para escribir. Al primer arranque el widget recibe de la plataforma un identificador anónimo de visitante y lo guarda en el navegador, para que al volver vea su conversación anterior y no un chat vacío.

De ahí se sigue que:

  • En los diálogos ese visitante aparece como anónimo: sin nombre, teléfono ni correo mientras no los escriba él mismo.
  • El identificador vive en un navegador concreto. Otro navegador, otro dispositivo o los datos del sitio borrados significan un visitante nuevo con historial limpio.
  • Un visitante anónimo puede vincularse con un cliente que ya conoces, por ejemplo un usuario autenticado de tu área privada. Cómo hacerlo se explica en la sección siguiente.

Vincular un visitante con tu propio usuario

Si el visitante ya ha iniciado sesión en tu sitio, puedes decirle a la plataforma quién es. Tras la vinculación la conversación deja de ser anónima: se fusiona con el perfil de esa persona y sus mensajes anónimos anteriores no se pierden.

Basta una llamada, con una función que produce la prueba:

mybot.identify(async (visitorId) => {
  const res = await fetch("/mybot-sign", {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ userId: currentUser.id, visitorId }),
  });
  return res.json(); // { userId, signature, expiresAt }
});

El widget llama a tu función con visitorId —el identificador anónimo de visitante actual para este navegador, el único valor que solo conoce el widget— y espera a que devuelva, o resuelva en, { userId, signature, expiresAt }. La única tarea de tu función es entregar visitorId a tu propio servidor, junto con el identificador del usuario ya autenticado (currentUser.id arriba es lo que eso sea en tu sitio), y devolver exactamente lo que responda tu servidor. La firma real ocurre en tu servidor, no en esta función —ver más abajo.

Una función de retorno, no un valor ya calculado ni una forma de leer visitorId directamente, por dos razones:

  • Momento en el que existe. El widget genera visitorId de forma asíncrona mientras arranca —no existe en el instante en que se ejecuta el script cargador, y mybot.identify en sí solo se instala una vez que eso termina (más abajo hay más detalle). Así que, para cuando tu página puede siquiera llamar a mybot.identify, el identificador que recibe tu función está garantizado como real. Un simple getter no tendría esa garantía: nada impediría que una página lo leyera una línea demasiado pronto y no obtuviera nada, produciendo en silencio una firma que nunca cuadra, sin ninguna pista de por qué.
  • Alcance. El código de tu propia página nunca tiene que guardar, almacenar ni pasar visitorId a mano —solo existe dentro de esta función, para la única llamada que necesita hacer.

Desde el momento en que sale del widget, visitorId es una credencial al portador (bearer capability): quien consiga una firma para él puede quedar fusionado con la sesión de ese visitante. Envíalo solo a tu propio servidor, en tu propia petición autenticada, y a ningún otro sitio: no lo registres en logs, no lo reenvíes a terceros ni lo pongas en una llamada de analítica del lado del cliente.

La firma se calcula solo en tu propio servidor

Dentro de tu función de arriba, tu propio servidor —nunca el navegador— calcula la firma: un HMAC-SHA256, con el secreto del widget como clave, en hexadecimal, los primeros 32 caracteres, de tres valores unidos en un solo mensaje. La misma fórmula aparece en el panel junto al secreto, bajo «Signing algorithm»:

signature = hex(HMAC-SHA256(key: secret, message: userId + "\n" + visitorId + "\n" + expiresAt)).slice(0, 32)

Cada + "\n" + de arriba es un salto de línea real entre las partes, no los dos caracteres barra invertida y n. Si tu función HMAC recibe el mensaje como una sola cadena, une las tres partes con un salto de línea real: tres llamadas .update() separadas sin salto de línea entre ellas calculan sobre un mensaje distinto e incorrecto.

Las tres partes:

  • userId — el identificador del usuario autenticado, el mismo valor que tu servidor recibe de la función de arriba y devuelve en su respuesta.
  • visitorId — el identificador anónimo de visitante que tu función recibió como argumento y reenvió sin cambios a tu servidor.
  • expiresAt — una marca de tiempo unix en segundos (no en milisegundos) hasta la que esta firma concreta sigue siendo válida, generada por tu servidor al firmar. La plataforma rechaza una llamada cuyo expiresAt ya está en el pasado, y también una que esté más de 24 horas en el futuro: fírmala justo antes de devolverla desde tu función, no una vez para reutilizarla en peticiones posteriores.

Esa caducidad es el verdadero motivo del cambio, no un detalle secundario. La fórmula anterior solo cubría el identificador de usuario, así que una firma capturada una vez —registrada en algún sitio, interceptada en la red, como sea— seguía siendo válida para siempre y encajaba con cualquier visitante, no solo con aquel para el que se emitió. Quien consiguiera una podía reproducirla en un navegador completamente distinto, y la plataforma acabaría fusionando la navegación anónima de un desconocido con el perfil de un cliente real. Atar la firma a un visitorId concreto y darle una vida corta cierra ambos extremos de ese agujero: solo se valida para la sesión para la que se emitió y en ninguna otra, y deja de validarse en cuanto pasa expiresAt —así que incluso una firma filtrada es solo un riesgo breve y de una sola sesión, no permanente.

Calcula las tres cosas en tu servidor y devuélvelas desde tu función ya hechas. No es un formalismo: para calcular la firma en el navegador tendrías que enviar allí el secreto, es decir, entregárselo a cada visitante de la página. A partir de ahí cualquiera podría hacerse pasar por cualquier cliente tuyo y leer su conversación. La firma (y el visitorId/expiresAt para los que se calculó) es segura para devolver al navegador; el secreto no.

La llamada no muestra nada en pantalla: la vinculación ocurre en el servidor y en silencio. Si tu función lanza un error o su promesa se rechaza, o si la plataforma rechaza la firma resultante —no cuadra, o expiresAt falta, ya pasó o está a más de 24 horas en el futuro—, no ocurre nada más: el visitante no nota nada y sigue escribiendo como anónimo.

mybot.identify no existe al instante: aparece cuando se ha cargado el paquete principal del widget, junto con un visitorId real (ver «Momento en el que existe» arriba). Llámalo desde un manejador de carga de la página, no como primera línea del <head>.

Dónde está el secreto y cómo cambiarlo

El secreto del widget es la clave con la que firmas. Vive en la configuración del widget, en la sección «identify() secret». Si esa sección dice que el widget aún no está conectado, conecta primero el canal web: el secreto aparece con él.

Mostrar. El botón «Reveal secret» pide el valor. No aparece en claro: primero verás puntos, y un conmutador aparte, «Show», muestra los caracteres («Mask» los vuelve a ocultar). Al lado está «Clear from screen», que quita el valor de la vista.

Puedes mostrarlo tantas veces como quieras, y mostrarlo no invalida nada: el secreto sigue siendo válido después de cerrar la pantalla. Es así porque se guarda cifrado y no con hash: la plataforma necesita su valor real para verificar cada firma. Si dentro de medio año despliegas un backend nuevo, entra y vuelve a consultarlo.

Cuando lo hayas copiado, recoge tú mismo. La plataforma no puede vaciar tu portapapeles: desde el navegador no se puede hacer de forma fiable, así que no lo prometemos. Quita el valor de la pantalla y vacía el portapapeles a mano cuando termines, sobre todo en un ordenador compartido.

Cambiar. El botón «Rotate secret», con paso de confirmación. Es una acción que rompe al instante: en cuanto existe el secreto nuevo, el viejo deja de verificar. No hay ventana de solapamiento, ni un momento en que valgan ambas claves.

Lo que se rompe es precisamente la vinculación de visitantes con tus usuarios. Las conversaciones no se interrumpen: los visitantes siguen escribiendo y recibiendo respuesta, solo que como anónimos, hasta que tu backend firme con el secreto nuevo. El código de instalación y la lista de dominios permitidos no se tocan, así que en tu sitio no hay nada que cambiar.

Cambia el secreto con intención: prepara primero el despliegue del backend con el valor nuevo y solo entonces pulsa «Rotate secret». Pulsarlo para ver qué pasa es mala idea. El valor nuevo se muestra en pantalla justo después del cambio, así que puedes copiarlo de inmediato.

Cómo se comporta el widget en la página

El widget está hecho a propósito para no interferir con tu sitio:

  • Vive en un contenedor aislado (Shadow DOM cerrado). Tus estilos no entran en el widget y los del widget no salen a la página. El efecto secundario es que no puedes reestilizarlo con tu CSS: usa los ajustes de apariencia.
  • El contenedor cubre la pantalla pero no intercepta clics: pasan a la página, y solo responden el botón y el panel del chat.
  • El widget siempre queda por encima de tu contenido; tus capas no pueden taparlo.
  • Cualquier error dentro del widget se queda dentro: en el peor caso falla el botón del chat y tu sitio sigue funcionando. Si el widget no aparece, mira la consola del navegador: casi siempre es la CSP o un dominio ausente de la lista.

Si el widget no aparece

Repasa por orden:

  1. Abre el código fuente y comprueba que están las dos etiquetas y que la clave no está vacía.
  2. Comprueba que la dirección del sitio es exactamente la de la lista, incluidos https:// y www.
  3. Mira la consola del navegador: un mensaje de Content Security Policy significa que hay que permitir el host de la plataforma en la CSP del sitio.
  4. Asegúrate de que ningún bloqueador de anuncios elimina el widget: pruébalo en una ventana privada sin extensiones.

Qué sigue