Canales

Widget web

Una burbuja de chat con tu agente en tu propio sitio, con una línea de código y sólo en los dominios que autorices.

Actualizado 2026-10-06

Una línea en tu sitio y aparece una burbuja de chat en una esquina: tus visitantes le escriben a tu agente, sin cuenta. Su apariencia (nombre, avatar, colores, tipografía, lado, sugerencias) la cambias sin volver a pegar el código.

Encenderlo

Desde Agentes → tu agente → Canales → Widget web: prende el switch, escribe tus dominios y copia el código. O desde la terminal:

bash
ghosty agents embed enable <agent-id> --domains "mitienda.com,*.mitienda.mx" --welcome "¡Hola! ¿En qué te ayudo?"

Te devuelve el código para pegar antes de </body>:

html
<script src="https://www.ghosty.studio/widget.js" data-agent="emb_…" data-color="#7c3aed" async></script>

Funciona igual en HTML a mano, WordPress, Shopify, Wix o cualquier sitio que te deje pegar un <script>.

Dónde carga: tus dominios

El widget sólo se abre en los dominios que pongas. Si alguien copia tu código en otro sitio, el navegador no lo carga.

  • mitienda.com también vale para www.mitienda.com.
  • *.mitienda.com vale para cualquier subdominio (blog.mitienda.com, tienda.mitienda.com).
  • Sin dominios no carga en ningún lado.
  • Sólo https.

Qué puede hacer el visitante

  • Sólo conversar con tu agente. Cada visitante tiene su conversación y no ve la de nadie más; el agente corre aislado por visitante.
  • Habla como cliente: consulta tus bases y documentos según lo que le diste al canal, pero no administra (no toca tableros, correos ni bases sin permiso de escritura).
  • Su conversación se guarda en su navegador y sigue donde la dejó al volver.
  • Cada mensaje entra a tu CRM como conversación del canal web.

Personalizarlo

Todo se cambia en Canales → Widget web, por API (PATCH /api/v2/agents/{id}/embed) o desde la terminal. El código que ya pegaste no cambia: la burbuja pide su apariencia al cargar (tarda hasta un minuto en verse el cambio).

OpciónFlagDefault
Primer mensaje (el saludo que ve el visitante)--welcome T«¡Hola! ¿En qué te ayudo?»
Nombre del encabezado--title Tel nombre del agente
Renglón bajo el nombre--subtitle Tninguno
Avatar (imagen https JPEG/PNG/WebP)--avatar URL (none lo quita)el fantasma del agente
Ícono del botón--icon chat|avatar|ghostchat
Texto de la caja para escribir--placeholder T«Escríbele a …»
Esquina y separación (px)--position right|left · --offset 20,40derecha, 20 px
Tema del panel--theme light|dark|autolight (auto sigue al visitante)
Color principal--color #hex#7c3aed; el texto encima sale blanco o negro según el contraste
Etiqueta junto al botón--bubble-text "¿Dudas?"ninguna
Abrirse solo--auto-open 10 (off)apagado; una vez por visita y nunca en el teléfono
Sugerencias propias (hasta 4)--starters "¿Precios?|¿Envíos?"ninguna (nunca salen las de Ghosty)

El avatar se copia a img.ghosty.studio: si tu imagen cambia de liga, el widget no se queda con un hueco.

bash
ghosty agents embed <agent-id> --title "Palmera Legal" --subtitle "Responde en minutos" \  --avatar https://palmeralegal.mx/logo.png --icon avatar --position left --theme auto \  --bubble-text "¿Dudas?" --starters "¿Cuánto cuesta una consulta?|¿Dónde están?"

Estilo fino

Para que se vea igual que tu sitio, el bloque style cambia la tipografía, las esquinas y el color de cada pieza. No acepta CSS libre: sólo estas llaves, cada una validada. Una llave en null vuelve a su default; --style none resetea todo.

Llave
Qué cambia
Cómo se ve
fontFamily
Tipografía de Google Fonts de una lista: Inter, Barlow, Barlow Condensed, Poppins, Montserrat, Roboto, Open Sans, Lato, Nunito, DM Sans, Work Sans, Manrope, Rubik, IBM Plex Sans, Source Sans 3, Raleway, Merriweather, Lora, Playfair Display, Libre Baskerville, Cormorant Garamond, Space Grotesk, Source Serif 4
AaAaAa
radius
Esquinas del panel, las burbujas, la caja y el botón de enviar (0–24 px; 0 = rectas)
headerBg · headerText
Fondo y texto del encabezado
Asistente
titleUppercase
Título en MAYÚSCULAS con espaciado
Asistente
statusDot
Punto de estado junto al título (color)
Asistente
headerAvatar
false quita el avatar del encabezado
panelBg
Fondo del panel
userBubbleBg · userBubbleText
Burbuja del visitante
¿Tienen envíos?
agentBubbleBg · agentBubbleText
Burbuja del agente (sin ella, el agente escribe sin burbuja)
¡Claro!
inputBg
Caja para escribir
Escribe…
sendBg · sendIcon · sendArrow
Botón de enviar, su flecha y su dirección (up o right)
↑→
launcherBg · launcherIcon
Botón flotante (siempre redondo)
typing · typingColor
Los puntitos de «escribiendo» mientras responde: bounce (rebote, el de siempre), flow (ola), flashing (parpadeo) o shimmer («Pensando…» con brillo, sin puntos), y su color (sin él, el de tu marca)

Los colores van en #rrggbb. Un texto que no pongas se calcula por contraste contra su fondo.

Ejemplo: estilo Palmera Legal — encabezado verde con el título en mayúsculas, punto dorado, todo en esquinas rectas, burbujas verde y beige, Barlow y puntitos dorados en ola:

bash
ghosty agents embed <agent-id> --style '{  "fontFamily": "Barlow", "radius": 0,  "headerBg": "#2d4a2e", "headerText": "#ffffff", "titleUppercase": true, "statusDot": "#c8a96a", "headerAvatar": false,  "panelBg": "#fbf9f4",  "userBubbleBg": "#2d4a2e", "userBubbleText": "#ffffff",  "agentBubbleBg": "#f3efe6", "agentBubbleText": "#1f2a1f",  "inputBg": "#ffffff", "sendBg": "#f3efe6", "sendIcon": "#2d4a2e", "sendArrow": "right",  "launcherBg": "#2d4a2e", "launcherIcon": "#ffffff",  "typing": "flow", "typingColor": "#c8a96a"}'

--font y --radius son atajos de style.fontFamily y style.radius.

Puntitos de «escribiendo»

Mientras el agente responde, el chat muestra tres puntitos. Por defecto toman el color de tu marca (el del encabezado). Con --typing-color les pones otro y con --typing cambias la animación:

typing
Qué hace
Cómo se ve
bounce
Rebotan uno tras otro (el de siempre)
flow
Crecen en ola
flashing
Parpadean, como en WhatsApp
shimmer
Sin puntos: el «Pensando…» con un brillo que lo recorre
Pensando
bash
ghosty agents embed <agent-id> --typing-color "#e11d48"ghosty agents embed <agent-id> --typing flow

--typing-color none regresa al color de la marca. En la ficha del agente está en Canales → Widget web → Avanzado.

De dónde sale el tema

En la tarjeta eliges Nuevo (tus valores), De Teams o De mi sitio, con una vista previa:

  • De Teams: toma la marca activa de tu espacio de Ghosty Teams (Ajustes → Marca): el primario va al encabezado, al botón y a la burbuja del visitante; el acento, al botón de enviar y al punto de estado; la superficie, al fondo; y su tipografía si está en la lista.
  • De mi sitio: lee los colores y la tipografía del CSS de tu página (no de imágenes). Si tu sitio pinta todo con JavaScript, avisa que no encontró colores.

Los dos se importan como valores editables: después ajustas lo que quieras en Avanzado → Estilo. Para refrescarlos, vuelve a aplicarlos.

bash
ghosty agents embed <agent-id> --theme-source teams [--workspace mi-espacio]ghosty agents embed <agent-id> --theme-source site --site-url https://palmeralegal.mxghosty agents embed <agent-id> theme > tema.json        # exporta el tema actualghosty agents embed <agent-id> --style-file tema.json   # lo vuelve a aplicar (o aplica uno tuyo)

Textos mientras trabaja

Mientras el agente piensa, el widget enseña unas frases con la voz de Ghosty («Me estoy poniendo los lentes 👓», «Estoy abriendo mi libreta», «Estoy pensando»…). Si no van con tu negocio, cámbialas en la tarjeta (Avanzado → Textos, con vista previa), por API (copy) o con el CLI. Lo que no cambies sigue con la frase de Ghosty.

LlaveCuándo saleDefault
wakingMientras despierta«Me estoy poniendo los lentes 👓»
connectingMientras se conecta«Estoy abriendo mi libreta»
thinkingMientras piensa (lista de 1 a 8; rotan cada 4 s)«Estoy pensando», «Estoy leyendo tu mensaje»…
stillWorkingPasado minuto y medio«Sigo trabajando»
cantStartSi no arrancó en 45 s (junto al botón)«No pude arrancar.»
interruptedSi la respuesta se cortó por una falla nuestra (con texto propio va sin el motivo técnico)«Me quedé a medias: …»
retryLabelEl botón de reintentar / retomar«Reintentar»
showDurationEl «· 18 s» de la espera y el «Trabajó N s» del pietrue
showCopyButtonEl botón «Copiar» bajo las respuestastrue

Texto plano de una línea, hasta 80 caracteres (sin < ni >). Es un PATCH por llave: null regresa esa frase a la de Ghosty y --copy none las regresa todas. Una llave que no existe o un texto largo contesta 400 con el motivo.

Ejemplo, un despacho («Palmera Legal»):

bash
ghosty agents embed <agent-id> --copy '{  "waking": "Un momento, por favor",  "connecting": "Abriendo su expediente",  "thinking": ["Revisando su consulta…", "Consultando el expediente…"],  "stillWorking": "Seguimos revisando su caso",  "showDuration": false}'ghosty agents embed <agent-id> --copy-file textos.json   # el mismo objeto desde un archivo

agents embed <id> theme exporta también copy, y --style-file lo vuelve a aplicar.

React

Si tu sitio es React (Next.js, Remix, Vite…), tienes tres caminos. Los tres hablan con el mismo agente y respetan tus dominios y topes.

Quieres…Usa
La burbuja de siempre, montada por React<GhostyWidget token="emb_…" />
Tu propio diseño, con la lógica resueltauseGhostyChat({ token }) (headless)
Un componente tuyo, editable y sin dependenciasghosty agents embed <agent-id> --out src/components/GhostyChat.tsx
Un sitio sin React (WordPress, Shopify, HTML)el <script> de arriba
bash
npm i @ghostystudio/widget

ghosty agents embed <agent-id> imprime este snippet ya con tu token (y en --json, en el campo react).

La burbuja: <GhostyWidget>

tsx
import { GhostyWidget } from "@ghostystudio/widget";
export default function Layout({ children }) {  return (    <>      {children}      <GhostyWidget token="emb_…" position="left" style={{ launcherBg: "#2d4a2e", radius: 0, fontFamily: "Barlow" }} />    </>  );}

Se monta y se limpia con el componente. Toma la apariencia guardada y lo que pases en props le gana: position, offsetX, offsetY, bubbleText, defaultOpen, open + onOpenChange (controlado), style con las mismas llaves de (también las del interior: burbujas, caja y enviar, que se validan igual que al guardarlas) y copy con las de (copy={{ thinking: ["Revisando su consulta…"] }}).

Tu propio chat: useGhostyChat

tsx
import { useState } from "react";import { useGhostyChat } from "@ghostystudio/widget";
export function MiChat() {  const { messages, send, stop, status, error } = useGhostyChat({ token: "emb_…" });  const [text, setText] = useState("");  const busy = status === "thinking" || status === "streaming";  return (    <div className="mi-chat">      {messages.map((m) => (        <p key={m.id} className={m.role === "user" ? "yo" : "agente"}>{m.text}</p>      ))}      {status === "thinking" && <p className="agente">…</p>}      {error && <p className="error">{error}</p>}      <form onSubmit={(e) => { e.preventDefault(); send(text); setText(""); }}>        <input value={text} onChange={(e) => setText(e.target.value)} placeholder="Escribe…" />        {busy ? <button type="button" onClick={stop}>Detener</button> : <button>Enviar</button>}      </form>    </div>  );}
  • messages: { id, role: "user" | "assistant", text, pending? }; el texto del agente llega en vivo.
  • status: connecting · idle · thinking (mandado, sin texto todavía) · streaming.
  • error: los topes y avisos llegan como texto listo para enseñar («Llegaste al límite de mensajes por hoy…»).
  • copy: tus (completos, con los de Ghosty donde no cambiaste nada) para pintar tu propia espera: copy?.thinking[0]. null hasta que llegan.
  • Al volver, messages trae la conversación anterior del visitante (un id por navegador en localStorage, llave ghosty:widget:vid).

Tu propio componente: --out

Si prefieres no depender de un paquete (estilo shadcn), el CLI escribe en tu proyecto un componente completo y editable, con tu token y tu tema actual (colores, tipografía, esquinas, título, placeholder, bienvenida, sugerencias y tus textos de espera) como constantes arriba del archivo, y comentarios que dicen dónde cambiar cada cosa:

bash
ghosty agents embed <agent-id> --out src/components/GhostyChat.tsx   # o .jsx sin tipos

No pisa un archivo que ya existe sin --force. Trae useGhostyChat() (la lógica) y GhostyChat (la UI) por separado: cambia la UI sin tocar la conexión.

Dominios y CORS

Desde tu página, las peticiones del modo headless y del componente van directo a Ghosty: sólo pasan si el Origin está en tus dominios del widget (mismas reglas: mitienda.com incluye www., *.mitienda.com sus subdominios, sólo https). Cualquier otro sitio recibe 403 y el navegador le bloquea la respuesta. En localhost no funciona: prueba en un dominio autorizado (o usa <GhostyWidget>, que vive en el iframe).

Topes y costo

Los turnos se cobran a tu cuenta, como cualquier otro canal. Para que nadie te los queme:

TopeDefaultCambiar
Mensajes por visitante al día30--per-visitor N
Mensajes por IP al día60--per-ip N

Si tu plan o tu llave no alcanzan, el visitante ve «no disponible por ahora» y tú ves el motivo con ghosty agents embed <agent-id>.

Cambiar o apagar

bash
ghosty agents embed <agent-id>                         # estado, dominios y el códigoghosty agents embed <agent-id> --domains "otro.com"    # reemplaza la listaghosty agents embed <agent-id> --color "#0e1317"       # color principal (no hace falta volver a copiar el código)ghosty agents embed <agent-id> --rotate                # token nuevo: el código viejo deja de funcionarghosty agents embed disable <agent-id>                 # lo apaga en todos lados al instante

Requisitos

  • Motor Claude o MiniGhosty (ghosty-lite): son los que aíslan a cada visitante. Con otro motor no se puede encender.

Agentes de EasyBits

Los agentes creados en Agentes → EasyBits siguen con su widget de siempre: un <iframe> con su token de embed.

html
<iframe  src="https://www.ghosty.studio/embed/TU_EMBED_TOKEN"  style="width:100%;max-width:420px;height:600px;border:0;border-radius:16px"  allow="clipboard-write"></iframe>
RutaQué hace
GET /api/embed/lookup?embedToken=…Resuelve el agentId del token.
POST /api/embed/message/:agentIdManda un mensaje; responde en SSE. Header Authorization: Bearer <embedToken>.