Paneles embebidos
El panel se embebe en un iframe, pero nunca recibe credenciales por URL. Tu servidor guarda un secreto de larga duración; el navegador solo recibe un código de un solo uso.
La trampa que rompe la mayoría de las integraciones
Todos los mensajes del host llevan source: 'mati-wordpress', incluso fuera de WordPress. Es un nombre heredado que quedó como constante del protocolo. Si mandás otro valor, el panel descarta los mensajes en silencio y se queda para siempre en «Conectando…».
1. Vincular el origen (una sola vez por dominio)
PUT https://app.mati-ia.com/api/integrations/wordpress/panel/origin
Authorization: MatiPair <codigo-de-vinculacion>
Content-Type: application/json
{ "version": 1, "workspaceSlug": "mi-negocio", "origin": "https://misitio.com" }
→ 201 { "hostCredential": "..." }2. Bootstrap (en cada carga de página, servidor a servidor)
POST https://app.mati-ia.com/api/integrations/wordpress/panel/bootstrap
Authorization: MatiHost <hostCredential>
Content-Type: application/json
{
"version": 1,
"workspaceSlug": "mi-negocio",
"origin": "https://misitio.com",
"subject": "usuario-42",
"role": "ADMIN",
"capabilities": ["mati_manage_operations", "mati_manage_settings"]
}
→ 201 { "code": "mpb_...", "frameContext": "...", "expiresAt": "..." }Hoy solo role: "ADMIN" emite sesión. Provider y customer devuelven 401 por diseño fail-closed.
La autorización del usuario final es tuya: Mati verifica que el host sea legítimo, no que quien mira la página deba ver el panel.
3. Construir la URL del iframe
https://app.mati-ia.com/widget/panel/admin/{slug}
?channelId={channelId}
&frameContext={frameContext}
// encodeURIComponent en ambos: un '#' sin escapar corta el frameContext.4. Handshake por postMessage
Panel → Host : mati:panel:ready
Host → Panel: mati:panel:bootstrap { code, origin }
// Validar SIEMPRE, en cada mensaje recibido:
if (event.origin !== panelOrigin) return;
if (data.source !== 'mati-panel') return;
if (data.version !== 1) return;
if (data.channelId !== channelId) return;
// Y al enviar: origen exacto, nunca '*'
frame.contentWindow.postMessage(message, panelOrigin);Panel → Host
mati:panel:ready—mati:panel:resize{ height }mati:panel:changed—mati:panel:session-expiring—mati:panel:open-admin—mati:panel:focus-mode{ enabled }mati:panel:select-logo—
Host → Panel
mati:panel:bootstrap{ code, origin }mati:panel:logo-selected{ url }mati:panel:logo-cancelled—
Cliente listo para usar
mati-panel-host.js implementa todo el protocolo y es agnóstico de plataforma: sirve para Shopify, Wix, Squarespace, Laravel o HTML plano.
<div id="mati-panel-host"></div>
<script src="https://app.mati-ia.com/mati-panel-host.js"></script>
<script>
MatiPanelHost.mount({
container: document.getElementById('mati-panel-host'),
panelOrigin: 'https://app.mati-ia.com',
bootstrapUrl: '/mi-backend/mati/bootstrap',
section: 'dashboard',
});
</script>Diagnóstico
"Conectando…"— elsourceno esmati-wordpress, o elchannelIdno coincide.401— eloriginno es el vinculado, orole≠ADMIN.429— 10/min en vinculación, 60/min en bootstrap.frameContexttruncado — faltaencodeURIComponent.