Embed: inbox dentro de tu app

Empotrá el inbox de IxiChat en tu propia UI vía iframe firmado. Tus usuarios ven y responden mensajes sin salir de tu aplicación. Patrón Stripe Connect Embedded — el iframe no expone tu api_key, usa un session token opaco de corta vida que vos minteás desde el backend.

Pre-requisito

Necesitás un api_key del workspace que querés mostrar. Ver flujo OAuth →

Cómo funciona

  1. Tu backend llama a POST /api/embed/session usando el api_key del install. Recibe un session token (TTL 10 min) y una URL ya armada.
  2. Tu frontend pone esa URL en <iframe src=...>.
  3. El iframe carga /embed/inbox con UI nativa (lista de conversaciones, thread, composer, botones para resolver y pausar IA).
  4. Para sesiones de más de 10 min, tu frontend escucha el evento postMessage ixichat:session-expired y mintea una nueva sesión sin recargar la página.

SDK npm: @ixichat/embed

Para no escribir el wire a mano, publicamos un SDK oficial con helper de backend (Node ≥18) + componente React.

bash
npm install @ixichat/embed
# o pnpm / yarn

Backend — mintear sesiones

ts
import { IxichatEmbed } from "@ixichat/embed/server";

const ixc = new IxichatEmbed({
  apiKey: process.env.IXICHAT_API_KEY!, // el api_key del install OAuth
});

// En tu handler (Express, Next route, etc.)
const { iframeUrl, expiresAt } = await ixc.createSession({
  view: "inbox",
  channelId: req.user.assignedChannelId, // opcional
  actorEmail: req.user.email,
  actorName: req.user.name,
  theme: {
    primaryColor: "#10B981",
    logoUrl: "https://miclinic.com/logo.png",
    mode: "light",
  },
  ttlMinutes: 30, // 1-60, default 10
});

res.json({ iframeUrl, expiresAt });

Frontend (React)

tsx
import { IxichatInbox } from "@ixichat/embed/react";

export function ClinicaInbox() {
  const [url, setUrl] = useState<string | null>(null);

  async function fetchSession() {
    const r = await fetch("/api/my-erp/ixichat-session", { method: "POST" });
    const { iframeUrl } = await r.json();
    setUrl(iframeUrl);
  }

  useEffect(() => { fetchSession(); }, []);

  if (!url) return <p>Cargando inbox…</p>;
  return (
    <div style={{ height: 600 }}>
      <IxichatInbox
        url={url}
        onRefreshNeeded={async () => {
          const r = await fetch("/api/my-erp/ixichat-session", { method: "POST" });
          return (await r.json()).iframeUrl;
        }}
        onConversationOpened={(id) => console.log("opened", id)}
        onMessageSent={(id) => console.log("sent", id)}
      />
    </div>
  );
}

Endpoint del session mint

POST/api/embed/session

Auth: Authorization: Bearer ixc_live_*. Scope: read.

json
{
  "view": "inbox",
  "channel_id": "uuid-opcional",
  "actor_email": "agente@miapp.com",
  "actor_name": "Carlos del equipo",
  "theme": {
    "primaryColor": "#10B981",
    "logoUrl": "https://miapp.com/logo.png",
    "mode": "light"
  },
  "ttl_minutes": 30
}

Respuesta:

json
{
  "session_token": "ems_<48-chars>",
  "iframe_url": "https://ixichat.com/embed/inbox?session=ems_...",
  "expires_at": "2026-05-27T14:30:00Z"
}

Vistas disponibles

viewQué muestra
inboxLista de conversaciones + thread + composer. Acciones: responder, pausar IA, resolver.
conversationSolo la vista detalle de UNA conversación (reservado para Fase 4.E).

Theme override

Pasá un objeto theme al mintear la sesión para que el iframe use tu identidad visual:

CampoTipoDescripción
logoUrlstring URLLogo que aparece en el header del iframe (square, ~32px).
primaryColorstring hex/cssColor de los bubbles outbound, el botón send y los CTAs.
mode"light" | "dark"Modo claro u oscuro. Default: light.

postMessage protocol

El iframe envía estos mensajes al parent. El SDK los re-expone como callbacks; si no usás el SDK, podés escuchar directamente:

ts
window.addEventListener("message", (ev) => {
  if (ev.origin !== "https://ixichat.com") return;
  switch (ev.data?.type) {
    case "ixichat:session-expired":   // mintear nueva sesión
    case "ixichat:conversation-opened": // ev.data.conversationId
    case "ixichat:message-sent":        // ev.data.conversationId
    case "ixichat:resize":              // ev.data.height
      // ...
  }
});

Origin matching

Siempre verificá ev.origin antes de confiar en el mensaje. El SDK lo hace por defecto. Si no lo verificás, cualquier ventana puede spoofear estos eventos.

CSP del iframe

IxiChat permite que /embed/* sea iframeable desde cualquier origen (frame-ancestors *). El resto del sitio mantiene política estricta — la landing y /app rechazan iframes.

Errores

StatuserrorCausa
400invalid_viewEl view no está en la whitelist.
401invalid_sessionSession token expirado, revocado o nunca existió.
401unauthorizedAuth bearer faltante o inválido al mintear.
403channel_forbiddenLa conv que se está accediendo no pertenece al channel_id de la sesión.
404conversation_not_foundLa conv no existe en este tenant.