postMessage versionado con un destino explícito en ambos lados.
El widget incrusta al agente directamente: el cerebro, no una página. No necesita ni proyecto ni página publicada: un agente con conocimiento y una site key es todo lo que un embed requiere.
Lo que ve el visitante
El componente es el ask-block de wondeya, exactamente como se ve en una página de wondeya: un encabezado que invita a preguntar, un campo de texto redondeado con un botón de enviar, preguntas sugeridas para empezar y el aviso de divulgación de IA. Cuando el visitante pregunta, el agente responde con generative UI (texto junto a componentes renderizados), usando el mismo renderizador que las páginas de wondeya. La conversación se extiende hacia abajo en tu página y tu página se desplaza; el componente nunca se desplaza dentro de sí mismo.Instálalo
El CDN del loader aún no está en vivo:
cdn.wondeya.com empieza a servir con el lanzamiento 0.1.0 del SDK, así que puedes crear tu site key y colocar el snippet ahora, y el widget aparece en el momento en que el loader se publique.div donde el componente deba aparecer: la mitad de una sección de la landing, el final de una página de producto, una columna lateral. La etiqueta script puede ir en cualquier parte; antes del cierre de body es el lugar clásico, y head también es seguro, porque si el loader corre antes de que tu contenedor se haya parseado, espera a DOMContentLoaded y busca una vez más. Si [data-wondeya] no encaja con tu marcado, apunta el loader a cualquier elemento con data-container (mira las opciones más abajo).
Esa es toda la instalación, con una condición: el origen de tu sitio debe estar registrado en esa site key. Hasta que lo esté, el frame se niega a cargar. Eso es lo que impide que alguien que copió tu clave ponga tu agente en una página que no controlas. Un dominio no registrado es un frame en blanco, por diseño.
Obtener una site key
Una site key identifica a un agente y no autoriza nada. Es pública por diseño: viaja dentro de tu HTML, donde cualquiera puede leerla. Lo que protege el endpoint es la lista de orígenes permitidos más la infraestructura anti-abuso de wondeya, no el secreto de la clave. Creas una para un agente junto con los orígenes exactos autorizados a incrustarlo. Hoy eso ocurre por MCP: la herramientacreate_site_key recibe el agente y la lista de permitidos,
wdy_site_…), el único lugar donde aparece el valor completo: los listados muestran únicamente un prefijo de referencia. list_site_keys y rotate_site_key cubren el resto del ciclo de vida; la referencia de herramientas documenta las tres.
Reglas para allowedOrigins:
- Hasta 20 entradas, cada una un origen exacto: esquema, host, puerto opcional. Sin ruta, sin barra final, sin comodines.
https://*.example.comse rechaza: de lo contrario, el takeover de un subdominio olvidado de tu lado se convertiría en el derecho a enmarcar tu agente. http://se acepta solo en loopback (http://localhost:3000), para desarrollo. Un sitio en producción sobre HTTP plano entregaría la decisión de “quién puede enmarcar esto” a quien controle la red.- Un origen que nunca podría funcionar se rechaza con un error que lo nombra, para que puedas corregir lo que escribiste.
- La lista queda fija al crear la clave y no se puede editar después: para agregar o cambiar un origen, crea una clave nueva con la lista completa y cambia el snippet.
La pantalla de la consola para site keys y orígenes de embed está en camino; hasta que llegue, las herramientas MCP son la forma de crearlas y gestionarlas.
Llamar a la API antes de que llegue el loader
async significa que el loader puede no haberse parseado cuando corre tu propio código. Agrega el stub de cola encima del snippet y llama a wondeya(...) cuando quieras; las llamadas se reproducen en orden una vez que el widget existe:
Opciones
Todas opcionales, todas se definen como atributos en la etiquetascript.
Apariencia
El widget viene con la apariencia de wondeya; estos atributos la reemplazan para este embed. Viajan al frame en la URL del iframe y se validan en ambos lados: un valor inválido es una sola advertencia en la consola del navegador y esa clave cayendo a su valor predeterminado, nunca un widget roto.
Los colores son hex de 6 dígitos y nada más; los radios y las tipografías son listas cerradas. No hay forma de pasar CSS libre al widget, a propósito: el mismo vocabulario cerrado que protege una página de wondeya protege el embed.
data-font="inherit" no puede usar tus fuentes web. El widget es un iframe, un documento separado, y las fuentes que carga tu página nunca cruzan hacia él. inherit lee el font stack calculado de tu página desde body y lo reenvía, así que el widget coincide con tu sitio allí donde ese stack se resuelve en fuentes del sistema o instaladas localmente, y cae a la tipografía predeterminada del widget donde no.
Cómo se dimensiona
El componente se comporta como un bloque de tu propia página, y las dos dimensiones vienen de dos lugares distintos:- El ancho sigue al contenedor. El iframe siempre ocupa el 100% del elemento donde se montó, así que controlas el ancho como controlas cualquier layout: dimensiona el contenedor. Una columna de 360px te da un componente angosto; una sección de ancho completo te da uno ancho.
- La altura sigue a la conversación. El frame mide su propio documento y reporta la altura por el canal
postMessage; el loader la aplica al iframe. A medida que llegan respuestas y se renderizan componentes, el componente crece y el contenido que está debajo baja: tu página se desplaza, el componente no. No hay nada que configurar. El loader verifica la sensatez de cada altura reportada antes de aplicarla, así que ni siquiera un frame comprometido podría convertir tu página en un iframe de un kilómetro de largo.
loading="lazy": un componente que queda fuera de la primera pantalla no le cuesta nada a tu página hasta que el visitante se desplaza cerca de él.
La API de JavaScript
init acepta todas las opciones de las dos tablas de arriba, con los mismos nombres sin el prefijo data- y con la misma validación; container puede ser una cadena con un selector CSS o el propio elemento del DOM. Cualquier cosa que el loader no reconozca (un comando desconocido, un argumento malformado) es un único console.warn con el prefijo [wondeya] y una operación nula, nunca una excepción en tu página.
Eventos
Si quieres saber si el loader en sí ya está activo antes de todo eso, lee
window.wondeya.loaded.
identify no es autenticación
Son datos de visualización. Cualquier script de tu página, como una etiqueta de analítica o una extensión del navegador, puede enviarlo, así que nada se autoriza jamás con él y nunca debe llevar un secreto. Los traits están limitados a 12 claves y 120 caracteres cada una, y todo lo que no sea una cadena corta se descarta antes de salir de tu página.
React
@wondeya/react aún no está en el registro de npm; se publica con el mismo lanzamiento 0.1.0 del SDK que el loader.
div simple en su posición dentro de tu árbol y monta el widget adentro. className y style caen en ese div; así controlas el ancho y la ubicación, exactamente como con cualquier otro bloque. appearance toma los mismos valores que los atributos data-* de arriba, con la misma validación y la misma limitación de inherit; onEvent recibe todos los eventos de la tabla de arriba. Al desmontarse elimina todo lo que agregó, que es la parte que una etiqueta script simple no puede hacer en una single-page app.
El envoltorio es deliberadamente delgado: todo el comportamiento vive en el loader, así que una app React y una etiqueta script reciben el mismo widget en lugar de dos que divergen.
Content-Security-Policy
Si tu sitio envía una CSP (y debería), agrega estas tres fuentes:script-src: el loader en sí. Con una política basada en nonce, pon tu nonce en la etiquetascript; el loader no agrega scripts inline propios.frame-src: el iframe del widget. Sin él, el frame queda bloqueado y tu contenedor se queda vacío.connect-src: la solicitud del chat. La hace el iframe, desde su propio origen, así que en rigor la gobierna la política del iframe y no la tuya. Algunos linters de CSP y navegadores antiguos igual la marcan; agregarla no cuesta nada y evita un reporte confuso.
style-src 'unsafe-inline'. El loader da estilo solo a los elementos que él creó, usando atributos style de elemento, que una CSP no gobierna. No carga fuentes, ni imágenes, ni analítica desde tu página.
La parte del trato que pone wondeya
El documento del embed trae su propia política, construida por site key en el servidor antes de que el documento exista:default-src 'none', y luego solo lo que la página genuinamente necesita;connect-srces el propio origen del frame más la API de wondeya;img-srces el propio origen del frame, URIsdata:y el CDN de wondeya;object-src 'none'ybase-uri 'none';- cuando la protección anti-bots está activada, el origen del desafío de Cloudflare Turnstile (
https://challenges.cloudflare.com) se suma ascript-srcyframe-src; frame-ancestorslista solo los orígenes que registraste. No se aceptan comodines, y una lista de permitidos vacía se convierte en'none'en lugar de*. Si algún origen registrado no pasa la validación, la lista entera colapsa a'none'en vez de servir en silencio una política más débil que la que configuraste.
Versionado y el compromiso con SRI
Cada lanzamiento se publica en dos URLs:
Subresource Integrity es incompatible con el canal de actualización automática, y eso no es un defecto de ninguno de los dos.
integrity="sha384-…" le dice al navegador que rechace un archivo cuyos bytes cambiaron, que es exactamente lo que hace el canal de actualización automática cuando sale una corrección. Elige uno:
- actualización automática, sin SRI: recibes correcciones de seguridad sin tocar tu sitio, y confías en el CDN de wondeya. El predeterminado correcto para casi todos.
- versión fija más SRI: una garantía byte a byte, de modo que un CDN comprometido no puede alcanzar a tus visitantes. A cambio, una corrección te llega cuando despliegas, no antes.
v0.1 y el siguiente lanzamiento con cambios incompatibles abre v0.2, manteniendo vivo el anterior. Desde la 1.0.0 en adelante es v1, y toda corrección 1.x llega por ahí.
Solución de problemas
El loader nunca lanza excepciones hacia tu página: un widget no debe poder romper el sitio donde está. Todo aquello de lo que se queja es unconsole.warn con el prefijo [wondeya].