Descripción general
Los Dashboard Apps nativos tienen tres contratos separados:
- El SDK V2 del navegador entrega contexto limitado por capacidades, eventos de solo lectura, comandos seguros e identidad firmada opcional para el backend propio de la app.
- La API REST permite a administradores o roles personalizados con
integration_managecrear, consultar, actualizar, reordenar y eliminar apps e instalaciones. - El servidor MCP expone el mismo contrato con cuatro herramientas opcionales en Canales e integraciones → Dashboard Apps.
El SDK autentica solo una identidad breve de la app incrustada; no ofrece un proxy de API genérico. Las escrituras deben pasar por tu backend, autenticado en REST con un token de privilegio mínimo.
Requisitos previos
dashboard_apps_native_surfaceshabilitada en la cuenta.- Dashboard App HTTPS en un origen diferente del dashboard de Conversa Labs para V2.
- Un administrador o rol personalizado con
integration_managey unapi_access_tokenpara gestión REST. - Para MCP, perfil cuyo usuario actuante tenga ese permiso y que incluya Dashboard Apps.
- CSP
frame-ancestorsque permita el origen exacto de Conversa Labs, sinX-Frame-Optionsincompatible.
Paso a paso
1. Conecta el SDK V2 en el navegador
Importa el SDK desde la ruta estable y versionada de tu despliegue de Conversa Labs. Redirige al build actual con fingerprint y expone exportaciones con nombre del módulo JavaScript.
import { connect } from '/dashboard-app-sdk/v2.js';
try {
// El host inyecta parámetros cl_*; origen e instalación se descubren automáticamente.
// Las opciones explícitas siguen disponibles para pruebas controladas.
const client = await connect();
const stop = await client.subscribe(
['context.initialized', 'conversation.changed', 'theme.changed'],
event => console.log(event.context_revision, event.data)
);
await client.setHeight(520);
document.querySelector('#documentacion').addEventListener('click', () => {
client.openLink('https://app.ejemplo.com/docs');
});
// Después: await stop(); client.disconnect();
} catch (error) {
console.error(error.code);
}
Usa las importaciones con nombre del módulo mostradas arriba. Son el contrato estable para aplicaciones
nuevas. Si tu aplicación no puede consumir exportaciones con nombre, el mismo archivo también expone
window.ConversaLabsDashboardAppSDK, con las mismas funciones.
Llama a connect() apenas cargue la página, no detrás de autenticación ni de una ida a tu backend.
El host inicia el handshake cuando el marco termina de cargar y reintenta hasta 10 segundos; una
aplicación que empieza a escuchar después de esa ventana recibe handshake_timeout. Un import
estático al inicio de un <script type="module"> es la forma soportada. Un import() dinámico
funciona si se espera de inmediato — no dejes la carga del SDK para después de renderizar la pantalla.
connect lee cl_dashboard_origin y cl_installation_id de la URL, valida el origen exacto, negocia
protocolo 2.0 y resuelve tras el handshake. El cliente
expone installationId, capabilities, connected, subscribe, unsubscribe, setHeight,
openLink, getIdentityAssertion y disconnect.
Los eventos incluyen contexto, conversación, estado/asignación/etiquetas, mensajes, contacto/usuario,
idioma/tema/permisos e instalación. La conversación es omnicanal y funciona para correo, WhatsApp,
SMS y otros inboxes; la barra lateral omite conversación y contacto. Programa
según capabilities, campos opcionales y context_revision creciente; resincroniza datos antiguos.
Identidad segura para n8n y otros backends
No envíes identidad ni credenciales en la query string del iframe. En context.initialized, usa:
account.idyaccount.namepara identificar la cuenta;current_user.id,current_user.name,current_user.roleycurrent_user.avatar_urlpara identificar a la persona que abrió la app;installation.id,surface,sidebar_categoryysidebar_iconen barra lateral para identificar instalación, lugar, categoría nativa e icono elegido;- en la superficie de conversación,
conversation,contact,permissionsy eventos de mensajes para el contexto operativo.
El bridge nunca expone tokens de usuario/sesión/CSRF, REST, MCP ni credenciales del proveedor del canal.
Cuando se conceden por instalación, current_user:email añade el correo del usuario actual;
contact:email y contact:phone añaden datos del contacto en conversación. Sin la capacidad, el campo
se omite del contexto y los eventos.
Para que un backend propio o webhook n8n verifique identidad sin confiar en IDs del navegador, pide una aserción breve:
const identity = await client.getIdentityAssertion();
await fetch('https://app.ejemplo.com/api/dashboard-session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(identity),
});
El backend envía assertion por POST a la introspection_url recibida, opcionalmente con
expected_origin: "https://app.ejemplo.com". Una respuesta activa contiene claims de cuenta, usuario,
instalación, superficie y capacidades. La aserción expira en dos minutos y cada introspección revalida
feature, instalación, usuario activo, membresía, audiencia y origen. El verificador público rechaza
aserciones mayores de 16 KiB antes de verificar la firma. No es un token REST/MCP y no debe almacenarse
ni usarse como bearer. Datos y escrituras adicionales usan la credencial REST de privilegio mínimo o
el perfil MCP restringido del backend.
2. Gestiona instalaciones por REST
Todas las rutas pertenecen a la cuenta. Con la feature desactivada responde 404 resource_not_found;
un miembro autenticado sin permiso de gestión recibe 403 forbidden en esas operaciones.
# Lista instalaciones visibles; administradores también reciben audience.
curl -sS "https://soporte.ejemplo.com/api/v1/accounts/1/dashboard_app_installations?surface=conversation" \
-H "api_access_token: API_TOKEN"
# Crea una instalación. dashboard_app_id queda inmutable.
curl -sS -X POST "https://soporte.ejemplo.com/api/v1/accounts/1/dashboard_app_installations" \
-H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \
--data '{"dashboard_app_installation":{"dashboard_app_id":42,"surface":"conversation","compatibility_mode":"v2","enabled":true,"position":0,"capabilities":["account:read","current_user:read","current_user:email","permissions:read","appearance:read","installation:read","conversation:read","contact:read","contact:email","contact:phone","messages:read","identity:assertion"],"audience":{"type":"any","roles":["administrator"],"team_ids":[7],"user_ids":[]}}}'
# Actualiza o reordena con el lock_version de la respuesta más reciente.
curl -sS -X PATCH "https://soporte.ejemplo.com/api/v1/accounts/1/dashboard_app_installations/9" \
-H "api_access_token: API_TOKEN" -H "Content-Type: application/json" \
--data '{"dashboard_app_installation":{"sidebar_category":"applications","sidebar_icon":"rocket","position":1,"lock_version":3}}'
# Elimina.
curl -sS -X DELETE "https://soporte.ejemplo.com/api/v1/accounts/1/dashboard_app_installations/9" \
-H "api_access_token: API_TOKEN"
La lista devuelve { "payload": [...], "meta": { "revision": N } }; crear, ver y actualizar
devuelven { "payload": { ... } }; eliminar devuelve 204. La instalación incluye app id/título/URL,
surface, compatibility_mode, enabled, visible operativo, position, sidebar_category y
sidebar_icon solo en barra lateral, capabilities, audience solo para administradores,
transport_security, warnings, lock_version y updated_at.
POST /api/v1/accounts/:account_id/dashboard_app_installations/:id/identity_assertion es la llamada
autenticada usada por getIdentityAssertion; aplica feature, instalación V2/dual activa, audiencia y
capacidades. Las apps deben preferir el SDK. La validación server-to-server usa el endpoint público
POST /dashboard-apps/v2/assertions/introspect descrito arriba.
También puedes crear app e instalaciones iniciales atómicamente con installations en
POST /dashboard_apps. Con la feature habilitada, omitir la propiedad crea la instalación por defecto
conversation + legacy + all; un array vacío crea solo la app. Con la feature deshabilitada, enviar
installations se rechaza y omitirla conserva la creación heredada. Cada app puede tener una
instalación por superficie.
3. Usa las herramientas MCP de la cuenta
Habilita Dashboard Apps en el perfil MCP. El módulo permanece en channels_integrations, conserva
las herramientas existentes de definición de app y añade:
| Herramienta | Efecto | Perfil de solo lectura |
|---|---|---|
list_dashboard_app_installations |
Lista instalaciones visibles al usuario actuante | Disponible |
create_dashboard_app_installation |
Crea una instalación | Oculta |
update_dashboard_app_installation |
Actualiza categoría, icono, posición, audiencia o bridge | Oculta |
delete_dashboard_app_installation |
Elimina una instalación | Oculta |
MCP usa el mismo ámbito, política, audiencia, feature y errores que REST. El detalle REST y el alias PUT no son herramientas separadas: list es la lectura canónica y PATCH la actualización/reordenación canónica. La emisión de identidad no es una herramienta MCP: pertenece a la sesión humana incrustada, mientras MCP ya posee su propio principal autenticado.
Configuración y opciones
- Superficies:
conversationosidebar. - Modos:
legacy,v2odual. - Capacidades: núcleo obligatorio; conversación/contacto/mensajes solo para
conversation; datos personales opcionalescurrent_user:email,contact:email,contact:phone; identidad opcionalidentity:assertion. Omitirlas aplica el valor completo y útil de la superficie; un array vacío explícito se rechaza coninvalid_capabilities. Elimina solo concesiones opcionales y conserva todas las capacidades obligatorias para la superficie seleccionada. - Audiencia:
{ "type": "all" }o{ "type": "any", "roles": [...], "team_ids": [...], "user_ids": [...] }. Roles válidos:agentyadministrator;anyexige un selector no vacío y concede acceso si cualquiera coincide. - Categoría lateral:
support,contacts_crm,applications,commercial,productivity,automation,growthoanalytics_config; solo se acepta ensidebary el valor predeterminado esproductivity. El grupoapplicationsqueda justo debajo de Contactos y CRM y se omite vacío. - Icono lateral:
panels_top_left,layout_dashboard,app_window,boxes,briefcase,bot,calendar_days,chart_no_axes_combined,circle_dollar_sign,clipboard_list,database,folder,globe_2,headphones,life_buoy,messages_square,package,rocket,shopping_bag,sparkles,workflowowrench; solo se acepta ensidebar, conpanels_top_leftcomo valor predeterminado. - Posición: índice desde cero normalizado por superficie y, en barra lateral, por categoría.
- Concurrencia: envía
lock_version;409 stale_installationexige recargar y reintentar. - Errores estables:
invalid_audience,invalid_capabilities,invalid_sidebar_category,invalid_sidebar_icon,installation_already_exists,invalid_dashboard_app_url,dashboard_app_migration_required,unsafe_same_origin_v2yresource_not_found. El error de migración exige un marco HTTP(S); query string y fragmento son válidos. - Límites SDK: 64 KiB por mensaje, 32 suscripciones, 30 comandos por 10 segundos y altura 160–2.000 px.
- Los plazos del handshake son dos, independientes. El panel abre la ventana cuando el marco carga
y reenvía la invitación hasta 10 segundos. El
connect()del SDK tiene su propio límite de 10 segundos desde la llamada, ajustable conconnect({ timeoutMs }). Subir el del SDK no ayuda: el panel deja de reintentar antes. - Ciclo de vida: el SDK emite eventos de ventana
conversalabs.dashboard-apps:bridge.connected,:bridge.paused,:bridge.resumedy:context.resynced. Escúchalos para pausar trabajo cuando la pestaña pierde el foco y para resincronizar. El códigodisconnectedsignifica que todo comando posterior será rechazado; reconecta en lugar de reintentar el comando.
Códigos de error y lo primero que revisar
| Código | Significa | Empieza por |
|---|---|---|
handshake_timeout |
El panel reintentó 10 s y la aplicación no respondió | ¿Llama a connect() al cargar? ¿El bundle cargado expone connect? |
invalid_origin |
Origen configurado o del bridge inválido | URL HTTP(S) sin credenciales; en V2 el origen debe diferir del panel |
frame_blocked |
El navegador rechazó la incrustación | frame-ancestors y X-Frame-Options en el servidor de la aplicación |
insecure_transport_blocked |
Aplicación HTTP dentro de un panel HTTPS | Publica la aplicación por HTTPS |
unsupported_version |
Aplicación y panel no comparten versión del bridge | Usa el SDK del mismo despliegue |
unsupported_command |
Comando fuera de la allowlist | Usa solo los comandos anunciados |
invalid_payload |
Mensaje fuera del contrato | Versión, secuencia, campos y tamaño |
invalid_sequence |
Mensaje fuera de orden | No reimplementes el protocolo a mano; usa el SDK |
rate_limited |
Más de 30 comandos en 10 s | Baja la frecuencia y agrupa suscripciones |
disconnected |
El bridge se cerró | Reconecta; los comandos pendientes no vuelven |
user_denied |
La persona rechazó la confirmación | Esperado en openLink; no insistas |
capability_denied |
Capacidad no concedida | Actívala en la instalación; los datos de contacto solo existen en conversación |
identity_unavailable |
No se pudo emitir la aserción | Feature, modo V2/dual, instalación activa y audiencia |
Casos de uso
- Mostrar contexto de conversación manteniendo cambios del CRM en un backend auditable.
- Provisionar instalaciones desde un servicio administrativo interno.
- Permitir que un perfil IA de solo lectura inventaríe apps sin escritura.
- Usar MCP de escritura en un perfil administrador restringido para automatización controlada.
Consejos, límites y buenas prácticas
- Fija
dashboardOriginal origen HTTPS exacto. No uses*, no aceptes orígenespostMessageno confiables ni reimplementes el protocolo si existe el SDK. - Query string y fragmento estáticos se conservan. En V2/dual el host añade y sobrescribe solo
cl_dashboard_origin,cl_installation_id,cl_account_id,cl_surface,cl_protocolycl_locale; trata todo nombrecl_*como reservado. El host elimina los valorescl_*estáticos antes de escribir esos seis parámetros. Nunca pongas tokens, aserciones, secretos ni datos personales en la URL o bundle. - Mantén credenciales REST/MCP en el servidor, rótalas y separa perfiles de inventario y administración.
- Autoriza audiencia en el servidor. Ocultar una pestaña no es autorización.
openLinksiempre muestra una confirmación nativa del host. El enlace solo se abre después de que la persona pulse Abrir enlace; nunca se confía en un timestamp del iframe como prueba de gesto.- Maneja códigos de
DashboardAppSDKError, desconexión, límites y resincronización. insecure_httpes una señal de migración, no aprobación para producción.
Solución de problemas
handshake_timeout: el panel reenvió la invitación durante 10 segundos y la aplicación no respondió. Empieza por la aplicación, no por la red: ¿llama aconnect()apenas carga la página? ¿El bundle que cargó exponeconnect? Solo después revisa el origen y el modo V2/dual. Un marco en blanco es otro problema — consultaframe_blocked.- El modo Dual oculta el fallo de V2: con Dual, la atención sigue por el lane heredado y la superficie aparece lista aunque el bridge V2 esté muerto. El diálogo Probar muestra el resultado de cada bridge por separado; para ver el error crudo, cambia la instalación a V2 temporalmente.
invalid_origin: usa una URL HTTP(S) sin credenciales incrustadas y mantén V2 en un origen distinto al del panel.unsupported_versionounsupported_command: usa el SDK del mismo despliegue y capacidades anunciadas.capability_denied: habilita la capacidad solicitada; correo/teléfono del contacto existe solo en la superficie de conversación.identity_unavailableo introspección inactiva: pide una aserción nueva y verifica feature, modo V2/dual, instalación activa, audiencia, membresía y origen exacto.rate_limitedoinvalid_payload: reduce frecuencia, tamaño y suscripciones.- REST/MCP devuelve 404: revisa cuenta, instalación y feature; la audiencia también puede ocultarla.
- REST/MCP devuelve 403: el usuario autenticado o actuante no es administrador ni posee un rol
personalizado con
integration_manage. - 409 al actualizar: obtiene el estado actual y repite con su
lock_version. - 422 al crear/actualizar: revisa
error, URL, par app/superficie, categoría lateral, audiencia y origen V2.