Widget Marco · Documentación técnica
El widget de Marco se instala de tres formas distintas, según dónde viva su aplicación y dónde deban permanecer los datos de la sesión. Esta guía cubre las tres. Elija su flujo y siga solo esa columna: cada una es autocontenida.
Paso cero
La pregunta que decide es dónde se crea la sesión y qué recibe el navegador. Todo lo demás se deriva de eso.
Flujo A
Sitios sin autenticación. Dos etiquetas HTML y nada más: la sesión arranca sola. La confianza se apoya en el origen del dominio y en el appId.
Flujo B
Su frontend ya gestiona usuarios autenticados. Inicia la sesión con init() y una identidad firmada con HMAC por su backend. Sin endpoint propio.
Flujo C
El contexto clínico nunca toca el navegador. Su backend crea la sesión por API y el frontend solo recibe un token opaco de un solo uso.
| Página pública | Página privada | Servidor a servidor | |
|---|---|---|---|
| Quién crea la sesión | El script, automáticamente | Su frontend, vía init() |
Su backend, por API |
| Qué recibe el navegador | Solo el appId |
user_id, firma y metadata |
Solo un token opaco |
| Autenticación | Origen + appId |
Firma HMAC-SHA256 | Bearer client_secret |
| Incrustación | Script + elemento <marco> |
Script + API JavaScript | <iframe> con token en la URL |
| Backend requerido | No | Solo para firmar | Sí |
| Contexto clínico | No aplica | Pasa por el navegador, firmado | Nunca sale del servidor |
| Renderizado inline | No, siempre flotante | Sí, vía component_id |
Sí, usted controla el iframe |
| Sesiones reanudables | Sí, vía localStorage |
Sí, por user_id/external_id |
No, por diseño |
Los estados se escriben igual en los tres flujos
Los tres publican los nombres de estado en mayúscula (READY, SESSION_CREATED, STARTED), así que un manejador escrito para un flujo se puede reutilizar en otro sin normalizar. Lo que cambia entre flujos es qué estados existen, no cómo se escriben.
Guía por flujo
La sesión se identifica por origen (dominio) y appId, ambos configurados de antemano con Marco. El widget se carga con una sola etiqueta de script y arranca por sí mismo, sin llamar a ninguna API de JavaScript.
appId.Agregue estas dos etiquetas a su página, una en el head y otra en el body. Eso es todo: no se requiere ninguna otra llamada.
<html>
<head>
<script
src="https://production.deep-talk.ai/widget/widget.js"
defer>
</script>
</head>
<body>
<marco
id="my-marco-widget"
api-base-url="https://production.deep-talk.ai/widget"
app-id="YOUR_APP_ID">
</marco>
</body>
</html>
Sustituya YOUR_APP_ID por el identificador que Marco le entrega. Cargar el script es suficiente: el widget aparece automáticamente como burbuja flotante.
La configuración vive en el backend de Marco
Colores de marca, comportamiento y demás ajustes se configuran una vez con su contacto en Marco y quedan asociados a su appId. Un cambio se actualiza del lado de Marco y se refleja automáticamente en su página, sin volver a desplegar. La URL del script también es estática: las correcciones del producto llegan solas.
La sesión se crea en cuanto el widget termina de cargar. Cada navegador mantiene una única conversación continua, identificada por su propio session_id.
El widget genera un identificador de sesión aleatorio la primera vez que se carga en un navegador y lo guarda en localStorage. No está vinculado a ninguna identidad real: solo permite reanudar la conversación en ese mismo navegador y sirve como identificador de correlación para eventos y auditoría.
Recargar la página, navegar y volver, o cerrar y reabrir la pestaña o el navegador (mismo perfil): en todos esos casos el widget encuentra el identificador guardado y, si la sesión sigue dentro de su tiempo de espera por inactividad, reanuda la conversación y publica SESSION_RELOADED en lugar de SESSION_CREATED.
Una sesión sin actividad más allá de su límite configurado se cierra automáticamente. La siguiente carga del widget en ese navegador ya no encuentra sesión que reanudar y publica SESSION_CREATED.
Por defecto la sesión solo termina por inactividad o por acción del usuario dentro del widget. Si necesita cerrarla de forma programática, use MarcoWidget.finish().
Para la mayoría de las integraciones, la sección anterior es todo lo necesario. Si su aplicación necesita reaccionar a eventos o cerrar la sesión explícitamente, el script expone un namespace global opcional, MarcoWidget, en window.
MarcoWidget.on('READY', function () {
console.log('Widget Marco listo');
});
MarcoWidget.on('SESSION_CREATED', function (msg) {
console.log('Nueva sesión:', msg.session_id);
});
// Cierra la sesión y limpia el identificador de localStorage.
// La próxima carga inicia una sesión nueva.
MarcoWidget.finish();
MarcoWidget.on() suscribe un callback a los eventos de estado y gestiona la verificación de origen por usted. En este flujo la sesión ya está activa al cargar el script, así que MarcoWidget sirve únicamente para escuchar eventos y, opcionalmente, cerrar la sesión.
El widget publica el estado de la sesión con postMessage, emitido desde su iframe hacia la ventana que lo contiene. MarcoWidget.on() es un wrapper sobre ese mismo flujo.
| Estado | Se publica cuando |
|---|---|
READY |
El script terminó de cargar. La sesión aún se está creando. |
SESSION_CREATED |
Se creó una sesión nueva para este navegador. |
SESSION_RELOADED |
Se encontró y reanudó una sesión activa existente en este navegador. |
STARTED |
El visitante envió una consulta y la interacción está en curso. |
RESPONSE_CREATED |
El agente produjo una nueva respuesta dentro de la sesión activa. |
COMPLETED |
La sesión finalizó normalmente. Incluye created_at y finished_at. |
CANCELLED |
Finalizó antes de tiempo: acción del visitante, finish(), o inactividad. |
ERROR |
La sesión o la llamada en curso finalizó por una falla. |
Estados terminales
COMPLETED, CANCELLED y ERROR son terminales. Ninguno puede ir seguido de más actividad en la misma sesión, y la siguiente carga del widget publicará SESSION_CREATED.
READY, STARTED, CANCELLED y ERROR comparten esta estructura base. session_id está ausente en READY y presente desde SESSION_CREATED o SESSION_RELOADED en adelante.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "READY",
"session_id": "sess_9f2a1c",
"timestamp": "2026-08-20T14:32:04Z"
}
COMPLETED agrega dos campos sobre la base: created_at y finished_at.
Los errores llegan como un evento de estado ERROR, en la misma estructura anterior, con los campos code y message.
| Código | Causa habitual |
|---|---|
unauthorized_origin |
La solicitud provino de un origen fuera de la lista permitida para este appId. |
invalid_app_id |
El appId no corresponde a ninguna cuenta configurada, o fue omitido. |
rate_limited |
Se superó el límite de solicitudes para este appId o esta dirección IP. |
network_error |
La solicitud al backend de Marco falló: conectividad o timeout. |
El origen es la señal de confianza primaria de este flujo. Cada carga del widget se compara contra los dominios autorizados para el appId indicado, y toda solicitud desde un origen fuera de esa lista se rechaza con unauthorized_origin. Marco aplica además límites de tasa por appId y por dirección IP.
El script y sus llamadas se sirven únicamente por HTTPS, y su aplicación debe servirse por HTTPS en todos los entornos. El widget se ejecuta dentro de un iframe en sandbox con permisos mínimos, gestionado automáticamente por el script cargador.
Si su aplicación aplica una Content-Security-Policy, autorice el dominio del widget en las tres directivas que gobiernan la carga del script, la incrustación del iframe y las llamadas de red del widget.
Content-Security-Policy:
script-src https://production.deep-talk.ai;
frame-src https://production.deep-talk.ai;
connect-src 'self' https://production.deep-talk.ai;
Su frontend inicia y gestiona la sesión directamente, llamando a MarcoWidget con una identidad de usuario firmada por su backend. El navegador recibe la firma, nunca el secreto.
component_id.El contexto clínico pasa por el navegador
El objeto metadata viaja por el navegador camino a Marco, firmado junto con la identidad del usuario. Evite incluir información directamente identificable: nombre, fecha de nacimiento o número de historia clínica. Si el contexto no puede salir del servidor, use el flujo servidor a servidor.
Esta etiqueta carga el shell del widget. La sesión se inicia por separado, con la llamada a init. No se renderiza ninguna interfaz visible hasta entonces.
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://production.deep-talk.ai/widget/widget.js"
defer onload="initializeMarco()"></script>
</head>
<body>
<script>
const signature = '<SIGNATURE_FROM_BACKEND>';
const timestamp = '<TIMESTAMP_FROM_BACKEND>';
function initializeMarco() {
window.$MarcoWidget.init({
apiBaseUrl: 'https://production.deep-talk.ai/widget',
appId: 'marco-demo',
floating: true,
user_id: '323232',
created_at: timestamp,
signature: signature,
});
}
</script>
</body>
</html>
init(), normalmente en el mismo lugar donde ya gestiona la autenticación: un handler de login exitoso, un efecto de React, un callback de auth.init() con el nuevo external_id.finish(), que cierra la sesión activa y limpia los datos almacenados localmente.Su backend calcula localmente una firma HMAC-SHA256 con un secreto compartido emitido por Marco, y su frontend la envía junto con el user_id. Trate el secreto como una clave de API del lado del servidor: léalo desde su gestor de secretos y nunca lo envíe al frontend, a un cliente móvil ni a un tercero.
// Fórmula base
signature = HMAC-SHA256(shared_secret, user_id)
// Con protección contra reproducción (recomendado en producción)
signature = HMAC-SHA256(shared_secret, `${user_id}:${created_at}`)
// Node.js
const crypto = require('crypto');
const signature = crypto
.createHmac('sha256', sharedSecret) // secreto, como UTF-8
.update(userId, 'utf8') // user_id, como UTF-8
.digest('hex'); // hexadecimal en minúscula
Tanto el secreto como el user_id deben codificarse en UTF-8 antes de firmar, y la firma resultante debe representarse en hexadecimal minúscula. Una firma que no respete esta codificación exacta falla la verificación aunque el secreto sea correcto.
Por defecto la firma solo demuestra que el user_id fue firmado por alguien en posesión de su secreto, no cuándo. Pasar un created_at opcional cierra esa brecha. Es una marca de tiempo UTC en formato ISO 8601 y, cuando se incluye, debe formar parte tanto del payload como de la firma. Marco rechaza llamadas cuyo created_at se aleje más de cinco minutos de la hora actual, en cualquier dirección.
Manejo de la clave
Marco genera el secreto compartido y se lo entrega. Guárdelo únicamente en su backend: no debe aparecer en código de frontend, almacenamiento del navegador ni en ningún payload de solicitud. Cada entorno tiene su propio secreto. Si sospecha que uno fue expuesto, contacte a Marco para rotarlo de inmediato.
| Campo | Usado en | Requerido | Descripción |
|---|---|---|---|
user_id |
init, finish |
Sí | Su propio identificador para el usuario final. |
signature |
init, finish |
Sí | Firma HMAC-SHA256 del user_id, y del created_at si se incluye. |
created_at |
init, finish |
No | Marca de tiempo UTC ISO 8601 para protección contra reproducción. Recomendado en producción. |
external_id |
init |
No | Identifica el contexto abierto, por ejemplo el registro de paciente. Omítalo para una sola conversación global por user_id; provéalo de forma consistente si un mismo usuario debe ver una conversación distinta por contexto. |
component_id |
init |
No | Id del elemento DOM donde Marco debe renderizarse inline. Si se omite, se renderiza flotante. |
metadata |
init |
No | JSON de formato libre pasado a la sesión del agente como contexto. No hay esquema fijo. |
{
"specialty": "oncology",
"diagnosis": "HER2-positive breast cancer, stage III",
"current_treatment": "trastuzumab + pertuzumab, cycle 4 of 6",
"disease_state": "partial response, stable since last imaging",
"ecog_status": "1",
"line_of_therapy": "second line"
}
Prefiera claves descriptivas y autoexplicativas por sobre abreviaciones. metadata es interpretado por el agente al iniciar la sesión, así que la claridad acá mejora directamente la calidad de las respuestas. El ejemplo es orientativo: también se puede trabajar con texto más extenso o resúmenes si eso representa mejor sus datos.
Se aplican dos tiempos de vida independientes: la ventana de validez de la firma y la sesión misma.
Cuando se incluye created_at, la firma es válida por cinco minutos desde su emisión. Se recalcula, o se reutiliza dentro de su ventana, en cada llamada a init o finish.
Corre durante un tiempo de vida configurado de forma independiente una vez iniciada. Se extiende con la actividad y finaliza por cierre explícito, por inactividad, o al expirar ese tiempo de vida.
Las sesiones se pueden reanudar
El widget guarda un identificador de la sesión activa en localStorage, por user_id y external_id. Cuando su aplicación vuelve a llamar a init() con el mismo par, Marco busca una sesión activa. Si existe y sigue dentro de su tiempo de espera por inactividad, la conversación se restaura y se publica SESSION_RELOADED en lugar de SESSION_CREATED.
Para dirigir el widget a un paciente o registro diferente, llame a init() con el nuevo external_id. Esto no actualiza la sesión abierta: es una sesión distinta, nueva o reanudada si ese contexto ya tenía una activa.
Las tres acciones se comportan igual: el iframe se descarta, pero el identificador persiste en localStorage. La próxima llamada a init() con el mismo user_id y external_id intenta reanudar automáticamente, como efecto de esa misma llamada.
MarcoWidget.finish() cierra la sesión de inmediato y limpia el identificador guardado. Después de finish(), una llamada a init() con el mismo par inicia una sesión nueva.
El widget publica el estado con postMessage desde su iframe hacia la ventana contenedora. El script cargador expone además MarcoWidget.on(evento, callback), un wrapper sobre ese mismo flujo que gestiona la verificación de origen por usted.
MarcoWidget.on('READY', function () {
console.log('Widget Marco listo para recibir init()');
});
MarcoWidget.on('SESSION_CREATED', function (msg) {
console.log('Nueva sesión:', msg.session_id);
});
MarcoWidget.on('ERROR', function (err) {
console.error('Error del widget Marco:', err.code, err.message);
});
| Estado | Se publica cuando |
|---|---|
READY |
El script terminó de cargar y está listo para recibir init(). Aún no existe sesión. |
SESSION_CREATED |
init() creó una sesión nueva para el user_id/external_id indicado. |
SESSION_RELOADED |
init() encontró y reanudó una sesión activa para el mismo par. |
STARTED |
El usuario envió una consulta y la interacción está en curso. |
RESPONSE_CREATED |
El agente produjo una nueva respuesta dentro de la sesión activa. |
COMPLETED |
Finalizó normalmente. Incluye created_at, finished_at y, opcionalmente, metadata. |
CANCELLED |
Finalizó antes de tiempo: acción del usuario, finish(), o inactividad. |
ERROR |
La sesión o la llamada en curso finalizó por una falla. |
session_id y external_id están ausentes en READY y presentes desde SESSION_CREATED o SESSION_RELOADED en adelante.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "SESSION_RELOADED",
"session_id": "sess_9f2a1c",
"external_id": "patient_789",
"timestamp": "2026-08-20T14:32:04Z"
}
La firma se calcula localmente en su backend, sin ida y vuelta con Marco. Una firma inválida o vencida se descubre cuando el widget intenta usarla, y llega como un evento de estado ERROR.
| Código | Causa habitual |
|---|---|
invalid_signature |
La firma no coincide con el valor que Marco recalcula para el payload dado. |
expired_timestamp |
created_at se incluyó pero cae fuera de la ventana de cinco minutos. |
missing_required_field |
Faltó user_id o signature en la llamada. |
session_not_found |
No hay sesión activa que coincida, por ejemplo al llamar a finish() sobre una ya finalizada. |
component_not_found |
Se incluyó component_id pero no coincide con ningún elemento presente en el DOM. |
unauthorized_origin |
La solicitud provino de un origen fuera de su lista de permitidos. |
network_error |
La solicitud al backend de Marco falló: conectividad o timeout. |
El user_id nunca se confía por sí solo. Marco recalcula la firma de forma independiente y la compara antes de iniciar o continuar cualquier sesión. Cada llamada a init o finish se compara además contra los dominios autorizados para su cuenta, y se rechaza con unauthorized_origin si el origen no está en la lista, aunque la firma fuera válida.
Una firma válida confirma que su backend, en posesión del secreto, firmó ese user_id. Comprobar si ese usuario debe tener acceso al registro descrito en metadata es responsabilidad de su propia aplicación, antes de llamar a init(). La decisión de acceso se toma en ese momento; la firma solo la transmite.
Content-Security-Policy:
script-src https://production.deep-talk.ai; // script de arranque
frame-src https://production.deep-talk.ai; // iframe en sandbox
connect-src 'self' https://production.deep-talk.ai; // llamadas del widget
El script y sus llamadas se sirven únicamente por HTTPS. El widget corre en un iframe en sandbox con permisos mínimos, y usa localStorage para la reanudación; esa caché se limpia al llamar a finish().
Su backend crea la sesión directamente con el backend de Marco. Su frontend nunca recibe datos del alcance de la sesión, solo un token opaco, de un solo uso y de corta duración para iniciar el chat.
client_secret, de servidor a servidor.<iframe> que usted controla.Su backend llama al endpoint de sesión de Marco por HTTPS. La credencial se presenta como bearer token y es de larga duración: se emite una vez por cliente, no por sesión.
POST /launch_token
Host: production.deep-talk.ai
Authorization: Bearer <client_secret>
Content-Type: application/json
{
"app_id": "oncology-widget",
"user_id": "doctor_123",
"metadata": {
"specialty": "oncology",
"diagnosis": "HER2-positive breast cancer, stage III",
"current_treatment": "trastuzumab + pertuzumab, cycle 4 of 6"
}
}
| Campo | Requerido | Descripción |
|---|---|---|
app_id |
Sí | Identifica qué configuración de app o agente de Marco usa esta sesión. Asignado por Marco durante la incorporación. |
user_id |
No | Su propio identificador para el usuario final. Use un ID interno, no un nombre ni un correo. Útil para estadísticas de uso, pero puede omitirse. |
metadata |
No | JSON de formato libre que describe el contexto de la sesión. Se usa únicamente del lado del servidor. |
{
"launch_token": "lt_7Kx0mQe2vB…",
"session_id": "sess_9f2a1c",
"expires_at": "2026-08-20T14:37:00Z"
}
launch_token es opaco, de un solo uso y de corta duración, y no contiene ningún dato subyacente de la sesión. Guarde session_id para correlacionarlo con sus propios registros de auditoría; no lo necesitará de nuevo para interactuar con el widget.
Su frontend muestra un iframe apuntado a la URL de sesión con el launch_token adjunto. El token en la URL es la única entrada que este flujo requiere de su frontend. Aplique codificación URL al valor, ya que puede contener caracteres que requieren codificación de porcentaje.
<iframe
src="https://production.deep-talk.ai/chat?launch_token=lt_7Kx0mQe2vB%2E…"
sandbox="allow-scripts allow-same-origin allow-forms"
allow="clipboard-write"
referrerpolicy="no-referrer"
></iframe>
| Valor del sandbox | Concede |
|---|---|
allow-scripts |
Ejecución de JavaScript dentro del iframe. Requerido: el widget no funciona sin él. |
allow-same-origin |
Trata el iframe como su origen real en lugar de un origen nulo aislado. Requerido para que use su propio almacenamiento y para que funcionen las comprobaciones de origen. |
allow-forms |
Envío de formularios. Requerido para la redacción de mensajes. |
No agregue permisos de más
Omita allow-popups, allow-top-navigation, allow-modals y allow-downloads salvo que una función específica los requiera. allow-top-navigation en particular permitiría que el iframe redirija toda su página.
Cuando el iframe se carga, Marco valida el token antes de mostrar cualquier contenido.
launch_token con relación a la sesión creada.session_id, estado de listo y configuración de visualización, generados a partir de los metadata ya consumidos en el servidor.READY al contenedor.Los metadata permanecen en el servidor
metadata se escribe una sola vez, de servidor a servidor, y se consume allí para configurar la sesión. Si su integración requiere que el frontend muestre ese contexto junto al widget, renderícelo desde su propio backend.
Además, READY solo indica que el widget es interactivo: los metadata de la sesión se procesan únicamente cuando el usuario realiza una acción explícita para comenzar.
Se aplican dos tiempos de vida independientes. El launch_token dura cinco minutos y es de un solo uso: existe únicamente para pasar el iframe de «tiene una referencia» a «sesión en ejecución». La sesión corre durante un tiempo de vida más largo, configurado por integración, se extiende con la actividad, y termina por cierre explícito, inactividad, o expiración.
Las sesiones no se pueden reanudar
No existe ningún mecanismo para reanudar una sesión una vez que su launch_token fue consumido o expiró. Es intencional: el uso único es lo que hace al token resistente a la reproducción. La única opción tras el fin de una sesión, por cualquier motivo, es crear una nueva con un nuevo launch_token.
Cerrar la pestaña, recargar la página o desmontar el iframe al navegar producen el mismo resultado: el iframe se descarta y la sesión termina con él. El token ya fue consumido en cuanto la carga anterior tuvo éxito, así que no puede reutilizarse. Su frontend debe solicitar una nueva sesión a su backend, que emite un nuevo token con el contexto actualizado. Si en cambio su contenedor mantiene el iframe montado durante la navegación, la sesión sigue activa hasta su tiempo de espera por inactividad o su cierre explícito.
metadata se fija al crear la sesión y no puede actualizarse en una sesión en ejecución. Para dirigir el widget a un paciente o registro diferente, solicite una nueva sesión con el nuevo contexto.
El widget publica el estado directamente a la ventana contenedora con postMessage. Su página lo escucha con un listener estándar. Verifique siempre event.origin antes de leer event.data, nunca lo compare contra '*', y nunca confíe en un mensaje que no incluya source: 'marco-widget'.
const MARCO_ORIGIN = 'https://production.deep-talk.ai';
window.addEventListener('message', (event) => {
if (event.origin !== MARCO_ORIGIN) return;
const msg = event.data;
if (msg?.source !== 'marco-widget') return;
switch (msg.state) {
case 'READY': /* el widget es interactivo */ break;
case 'STARTED': /* el usuario envió una consulta */ break;
case 'COMPLETED': /* sesión finalizada */ break;
case 'CANCELLED': /* cancelada por usuario o app */ break;
case 'ERROR': /* ver sección de errores */ break;
}
});
| Estado | Se publica cuando |
|---|---|
READY |
El widget validó el token, inició la sesión y es interactivo. |
STARTED |
El usuario envió una consulta y la interacción está en curso. |
COMPLETED |
Finalizó normalmente. Incluye created_at, finished_at y, opcionalmente, metadata. |
CANCELLED |
Finalizó antes de tiempo, por acción del usuario o de su aplicación. |
ERROR |
La sesión finalizó debido a una falla. |
Su contenedor puede enviar comandos de la misma forma, dirigidos al contentWindow del iframe. Pase siempre el origen de destino exacto como segundo argumento, nunca '*', para que el navegador se niegue a entregar el mensaje si el iframe navegó a otra ubicación de forma imprevista.
const iframeWindow = document.querySelector('iframe').contentWindow;
iframeWindow.postMessage({
source: 'marco-host',
version: 1,
type: 'COMMAND',
action: 'CLOSE', // única acción admitida actualmente
session_id: 'sess_9f2a1c'
}, MARCO_ORIGIN);
Al recibirlo, el widget verifica el origen contra los autorizados para su cuenta antes de ejecutarlo. Un comando que provenga de un origen no registrado se descarta en silencio: el widget no actúa ni emite ERROR, ya que un origen no reconocido podría no ser un emisor legítimo. Un CLOSE válido finaliza la sesión de inmediato y el widget emite CANCELLED de vuelta, para que su contenedor tenga una confirmación limpia y correlacionable.
{
"source": "marco-widget",
"version": 1,
"type": "STATE_CHANGE",
"state": "COMPLETED",
"session_id": "sess_9f2a1c",
"created_at": "2026-08-20T14:32:00Z",
"finished_at": "2026-08-20T14:36:12Z",
"metadata": {
"query_summary": "Asked about second-line options for HER2-positive…",
"final_synthesis": "Reviewed second-line options for HER2-positive…"
}
}
El objeto metadata es específico de cada integración
COMPLETED puede incluir un objeto metadata opcional sobre la estructura base. Las necesidades de salida estructurada varían según el cliente, así que su forma no está definida de forma fija en esta guía y debe acordarse con su contacto en Marco antes de implementar. El ejemplo de arriba ilustra el patrón, no un esquema. CANCELLED y ERROR siempre usan la estructura base.
Los errores ocurren en dos momentos y se presentan de forma distinta en cada uno. En la creación de la sesión son respuestas HTTP estándar a su llamada de servidor a servidor.
| Código | HTTP | Causa habitual |
|---|---|---|
invalid_client_secret |
401 | La credencial del encabezado Authorization falta o es incorrecta. |
unauthorized_origin |
403 | La configuración resuelta de la cuenta no permite este entorno. |
invalid_request |
400 | Un campo requerido no se incluyó o tenía formato incorrecto. |
rate_limited |
429 | Demasiadas llamadas de creación de sesión en un intervalo corto. |
Desde la validación del iframe en adelante, los errores llegan como un evento de estado ERROR con los campos code y message.
| Código | Causa habitual |
|---|---|
token_expired |
El iframe se cargó después de que expirara el TTL del launch_token. |
token_already_used |
El launch_token ya había sido consumido. |
invalid_origin |
El iframe se cargó desde un dominio fuera de su lista de permitidos. |
session_not_found |
Ninguna sesión coincide con el token, o la sesión ya fue cerrada. |
Tanto la creación de sesión como la incrustación del iframe se realizan únicamente por HTTPS. El contenido mixto es rechazado por el navegador antes de que Marco participe. Cada validación de token compara el origen solicitante con los dominios autorizados para su cuenta, y rechaza con unauthorized_origin aunque el token fuera válido.
Un launch_token válido solo confirma que el client_secret de su backend se presentó correctamente. No garantiza que ese user_id deba tener acceso al registro descrito en metadata: esa comprobación es responsabilidad exclusiva de su aplicación, al momento en que su backend llama al endpoint. Una vez creado el token, la decisión de acceso ya fue tomada; el token solo la transmite.
En este flujo su página nunca llama directamente a Marco: solo lo hacen su backend y el propio iframe. Por eso la CSP de su página contenedora únicamente necesita autorizar el origen de Marco en frame-src.
Content-Security-Policy:
frame-src https://production.deep-talk.ai;
El iframe mantiene su credencial de sesión en memoria después del inicio y la adjunta explícitamente a cada llamada posterior, evitando las restricciones de cookies de terceros.
Común a los tres flujos
Marco no utiliza el contenido de las conversaciones ni los metadata de las sesiones para entrenar, ajustar ni mejorar ningún modelo.
El contenido de la sesión y los registros de auditoría se conservan según plazos distintos, porque responden a propósitos diferentes.
| Datos | Propósito | Retención |
|---|---|---|
| Contenido de la sesión | metadata, transcripción y resumen. Sirve a la consulta misma. |
Eliminado dentro de las 48 horas posteriores al estado terminal de la sesión. |
| Registros de auditoría | Monitoreo de seguridad y evidencia de cumplimiento. session_id, external_id cuando aplica, marcas de tiempo y estado final, sin contenido clínico. |
6 años desde su creación, alineado con el estándar de la industria para registros de acceso en salud. |
session_id es el identificador de correlación en los tres flujos, y vincula los eventos de una sesión, las entradas de auditoría y la carga útil de COMPLETED. En el flujo de página privada, external_id lo acompaña cuando se usa. No existe un ID de correlación independiente.
Estabilidad de los códigos de error
En los tres flujos, code es un identificador estable y seguro para usar en lógica de negocio; pueden agregarse nuevos códigos con el tiempo, así que incluya siempre un caso por defecto. message es para registros y depuración: su texto exacto puede cambiar y no debe compararse por patrón.
Común a los tres flujos
Marco ofrece dos entornos, con las mismas rutas en ambos hosts.
| Entorno | Host |
|---|---|
| Pruebas (staging) | staging.deep-talk.ai |
| Producción | production.deep-talk.ai |
// Página pública y página privada
Script: https://<host>/widget/widget.js
API base URL: https://<host>/widget
// Servidor a servidor
Creación de sesión: POST https://<host>/launch_token
Iniciar chat: GET https://<host>/chat?launch_token=...
Cada entorno tiene sus propias credenciales y su propia lista de orígenes autorizados, y no existen rutas entre entornos para ninguna de ellas.
| Flujo | Credencial por entorno |
|---|---|
| Página pública | Un appId propio. Un appId de staging no es válido contra el host de producción. |
| Página privada | Un secreto compartido propio. Una firma calculada con el secreto de staging es rechazada en producción. |
| Servidor a servidor | Un client_secret propio. Un launch_token solo es válido en el entorno que lo emitió. |
Guarde y rote las credenciales de cada entorno por separado.
Las métricas de rendimiento y los umbrales fijos, incluido el tiempo hasta el primer token y cualquier otra garantía de latencia acordada, se definen en el acuerdo SLA, un documento independiente de esta guía.
Común a los tres flujos
Antes de activar cualquiera de los tres flujos, comparta lo siguiente con su contacto en Marco.
| Qué necesitamos | Para qué | Aplica a |
|---|---|---|
| Origen(es) donde se embebe el widget | Verificación de origen. Es la lista contra la que se compara cada carga o llamada. | Los tres flujos |
| Colores de marca primario y secundario | Personalizar el tema de la interfaz para adaptarlo a su sitio. | Los tres flujos |
| Duración de sesión | Tiempo de espera por inactividad y duración máxima, si los valores por defecto no le sirven. | Los tres flujos |
Forma del objeto metadata en COMPLETED |
Acordar la salida estructurada que su integración necesita recibir al cerrar la sesión. | Servidor a servidor |
Marco le entrega a cambio las credenciales de cada entorno: el appId en el flujo de página pública, el secreto compartido en página privada, y el client_secret junto con el app_id en servidor a servidor.
¿Dudas sobre cuál flujo le corresponde?
Escríbanos a sales@marco.care con una descripción de dónde vive su aplicación y qué datos de contexto necesita pasar. Le confirmamos el flujo y le entregamos las credenciales de staging para que pueda probar antes de comprometerse.
