Qué puedes integrar
El Embed SDK ofrece tres superficies distintas según el nivel de integración que necesites.
Workspace completo
CallspaceFrame incrusta el workspace de agente completo en un iframe. Ideal para portales de operaciones o paneles internos.
Teléfono flotante
PhoneWidget y PhoneFrame exponen solo el marcador y los controles de llamada. El iframe permanece montado aunque el panel esté cerrado.
Llamada entrante
IncomingCallFrame es invisible en reposo y aparece automáticamente cuando llega una llamada. Perfecto para añadir notificaciones de llamada a cualquier app.
Instalación
Agrega el paquete e importa @callspace/embed-sdk/core para JavaScript/TypeScript o @callspace/embed-sdk/react para los componentes React.
Instalar el paquete
# pnpm
pnpm add @callspace/embed-sdk
# npm
npm install @callspace/embed-sdkEntrypoints recomendados
import {
CallspaceFrame,
PhoneFrame,
PhoneWidget,
IncomingCallFrame,
} from '@callspace/embed-sdk/react';
import {
sendCallspaceStartCall,
sendCallspaceOpenConversation,
sendCallspaceConfigure,
CALLSPACE_EMBED_EVENTS,
addCallspaceEventListener,
buildCallspaceEmbedUrl,
getCallspaceAppOrigin,
} from '@callspace/embed-sdk/core';Requisitos
@callspace/embed-sdk/core funciona sin React. @callspace/embed-sdk/react requiere React 19 o superior porque sus componentes usan useEffectEvent.
Componentes
Cuatro componentes React para incrustar el workspace de llamadas con distintos niveles de integración.
Todos los componentes heredan las props de iframe estándar excepto src y children.
CallspaceFrame
Incrusta el workspace completo de agente en un iframe. Gestiona la inicialización, configuración y sesión de autenticación de forma automática.
Props
| Prop | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
| appUrl | string | No | https://app.callspace.io | URL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io. |
| iframeId | string | No | — | ID que identifica este iframe en mensajes multi-iframe. |
| parentOrigin | string | No | window.location.origin | Origen del host. Se detecta automáticamente si se omite. |
| debug | boolean | No | false | Activa logs verbose del protocolo postMessage en consola. |
| autoInit | boolean | No | true | Envía INIT y CONFIGURE automáticamente al cargar el iframe. |
| configurePayload | CallspaceConfigurePayload | No | — | Tema y contexto enviados en el comando CONFIGURE inicial. |
| auth | { loginUrl: string; title?: string; description?: string; buttonLabel?: string } | No | — | Si la sesión no está activa, muestra una pantalla de login integrada. |
| onCallspaceEvent | (event: CallspaceEventEnvelope) => void | No | — | Callback que recibe todos los eventos emitidos por el iframe. |
Autenticación integrada
Pasa la prop auth con un loginUrl para mostrar automáticamente una pantalla de login cuando la sesión del agente no está activa.
Ejemplo
import { useRef } from 'react';
import { CallspaceFrame } from '@callspace/embed-sdk/react';
export function WorkspaceEmbed() {
const iframeRef = useRef<HTMLIFrameElement>(null);
return (
<CallspaceFrame
ref={iframeRef}
iframeId="workspace-1"
auth={{ loginUrl: '/login' }}
onCallspaceEvent={(event) => {
if (event.event === 'callspace:ready') {
console.log('Workspace listo', event.payload);
}
}}
style={{ width: '100%', height: '100vh', border: 'none' }}
/>
);
}PhoneFrame
Igual que CallspaceFrame pero en modo phone: solo expone el marcador y los controles de llamada. Úsalo cuando necesitas controlar tú mismo el layout del iframe.
Props
| Prop | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
| appUrl | string | No | https://app.callspace.io | URL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io. |
| iframeId | string | No | — | ID que identifica este iframe en mensajes multi-iframe. |
| parentOrigin | string | No | window.location.origin | Origen del host. Se detecta automáticamente si se omite. |
| debug | boolean | No | false | Activa logs verbose del protocolo postMessage en consola. |
| autoInit | boolean | No | true | Envía INIT y CONFIGURE automáticamente al cargar el iframe. |
| configurePayload | CallspaceConfigurePayload | No | — | Tema y contexto enviados en el comando CONFIGURE inicial. |
| auth | { loginUrl: string; title?: string; description?: string; buttonLabel?: string } | No | — | Si la sesión no está activa, muestra una pantalla de login integrada. |
| onCallspaceEvent | (event: CallspaceEventEnvelope) => void | No | — | Callback que recibe todos los eventos emitidos por el iframe. |
PhoneWidget
Panel flotante con el teléfono integrado. Incluye un FAB por defecto o acepta un trigger personalizado vía renderTrigger. El iframe permanece montado aunque el panel esté cerrado.
Props
| Prop | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
| appUrl | string | No | https://app.callspace.io | URL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io. |
| iframeId | string | No | — | ID que identifica este iframe en mensajes multi-iframe. |
| parentOrigin | string | No | window.location.origin | Origen del host. Se detecta automáticamente si se omite. |
| debug | boolean | No | false | Activa logs verbose del protocolo postMessage en consola. |
| autoInit | boolean | No | true | Envía INIT y CONFIGURE automáticamente al cargar el iframe. |
| configurePayload | CallspaceConfigurePayload | No | — | Tema y contexto enviados en el comando CONFIGURE inicial. |
| auth | { loginUrl: string; title?: string; description?: string; buttonLabel?: string } | No | — | Si la sesión no está activa, muestra una pantalla de login integrada. |
| onCallspaceEvent | (event: CallspaceEventEnvelope) => void | No | — | Callback que recibe todos los eventos emitidos por el iframe. |
| panelWidth | number | No | 380 | Ancho del panel flotante en px. |
| panelHeight | number | No | 620 | Alto del panel flotante en px. |
| placement | 'bottom-right' | 'bottom-left' | No | 'bottom-right' | Esquina donde se ancla el panel y el FAB por defecto. |
| open | boolean | No | — | Estado controlado del panel. Combinar con onOpenChange. |
| onOpenChange | (open: boolean) => void | No | — | Callback cuando el widget solicita abrir o cerrar. |
| defaultOpen | boolean | No | false | Estado inicial para uso no controlado. |
| renderTrigger | ({ open, toggle }) => ReactNode | No | — | Render prop para usar un trigger personalizado en lugar del FAB. |
Iframe siempre montado
El iframe no se desmonta al cerrar el panel para mantener vivo el Voice Device y recibir llamadas entrantes.
Ejemplo
import { PhoneWidget } from '@callspace/embed-sdk/react';
export function Layout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<PhoneWidget
placement="bottom-right"
auth={{ loginUrl: '/login' }}
/>
</>
);
}IncomingCallFrame
Superficie dedicada a llamadas entrantes. Con autoHide={true} el iframe es invisible en reposo y aparece automáticamente al detectar actividad de llamada.
Props
| Prop | Tipo | Obligatorio | Default | Descripción |
|---|---|---|---|---|
| appUrl | string | No | https://app.callspace.io | URL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io. |
| iframeId | string | No | — | ID que identifica este iframe en mensajes multi-iframe. |
| parentOrigin | string | No | window.location.origin | Origen del host. Se detecta automáticamente si se omite. |
| debug | boolean | No | false | Activa logs verbose del protocolo postMessage en consola. |
| autoInit | boolean | No | true | Envía INIT y CONFIGURE automáticamente al cargar el iframe. |
| configurePayload | CallspaceConfigurePayload | No | — | Tema y contexto enviados en el comando CONFIGURE inicial. |
| auth | { loginUrl: string; title?: string; description?: string; buttonLabel?: string } | No | — | Si la sesión no está activa, muestra una pantalla de login integrada. |
| onCallspaceEvent | (event: CallspaceEventEnvelope) => void | No | — | Callback que recibe todos los eventos emitidos por el iframe. |
| autoHide | boolean | No | false | Oculta el iframe en reposo y lo muestra solo cuando hay actividad de llamada. |
autoHide
El componente se vuelve visible en INCOMING_RINGING, CALL_STARTED y CALL_UPDATED, y se oculta en CALL_ENDED e INCOMING_REJECTED.
Ejemplo
import { IncomingCallFrame } from '@callspace/embed-sdk/react';
// Monta este componente una vez en el layout raíz.
// El iframe es invisible hasta que llega una llamada.
export function IncomingCallOverlay() {
return (
<IncomingCallFrame
autoHide
auth={{ loginUrl: '/login' }}
style={{ width: 360, height: 200, borderRadius: 16 }}
/>
);
}Comandos del host
Funciones para enviar comandos al iframe embebido desde la página host. Usan postMessage con envoltura tipada.
Cómo obtener el contentWindow
Pasa un ref al componente frame para acceder al iframe DOM. El targetOrigin debe coincidir con el origen de appUrl.
sendCallspaceStartCall
Inicia una llamada saliente en el iframe embebido. El agente debe estar disponible para que el comando se ejecute.
Props
| Parámetro | Tipo | Descripción |
|---|---|---|
| targetWindow | Window | contentWindow del iframe de destino. |
| targetOrigin | string | Origen del iframe. Usar getCallspaceAppOrigin(appUrl). |
| payload.phoneNumber | string | Número de teléfono destino con prefijo internacional. |
| payload.callerId | string | null | Número a mostrar como caller ID (opcional). |
import {
getCallspaceAppOrigin,
sendCallspaceStartCall,
} from '@callspace/embed-sdk/core';
const iframeRef = useRef<HTMLIFrameElement>(null);
const appOrigin = getCallspaceAppOrigin();
function callCustomer() {
const win = iframeRef.current?.contentWindow;
if (!win) return;
sendCallspaceStartCall(win, appOrigin, {
phoneNumber: '+34600000000',
callerId: '+34900000000', // opcional
});
}Comportamiento en workspace
En modo workspace, esta acción sigue lanzando la llamada y además enfoca automáticamente el contexto de ese número en el inbox embebido.
sendCallspaceOpenConversation
Abre el contexto completo de un número dentro del workspace embebido sin enviar mensajes. Si todavía no existe contexto previo, abre Nueva conversación con el número precargado.
Props
| Parámetro | Tipo | Descripción |
|---|---|---|
| targetWindow | Window | contentWindow del iframe de destino. |
| targetOrigin | string | Origen del iframe. Usar getCallspaceAppOrigin(appUrl). |
| payload.phoneNumber | string | Abre el contexto completo de ese número en el workspace. Si no existe todavía, abre Nueva conversación con el número precargado. |
import {
getCallspaceAppOrigin,
sendCallspaceOpenConversation,
} from '@callspace/embed-sdk/core';
const iframeRef = useRef<HTMLIFrameElement>(null);
const appOrigin = getCallspaceAppOrigin();
function openConversation() {
const win = iframeRef.current?.contentWindow;
if (!win) return;
sendCallspaceOpenConversation(win, appOrigin, {
phoneNumber: '+34600000000',
});
}sendCallspaceConfigure
Envía configuración de tema o contexto al iframe después de que esté montado.
import { sendCallspaceConfigure } from '@callspace/embed-sdk/core';
sendCallspaceConfigure(iframe.contentWindow, appOrigin, {
theme: {
backgroundColor: '#0d1117',
accentColor: '#2563eb',
answerColor: '#16a34a',
hangupColor: '#dc2626',
},
});Eventos del iframe
Eventos que el iframe envía al host a través de onCallspaceEvent o del hook useCallspaceEvents.
Union discriminada
El parámetro event es un union discriminado por event.event. TypeScript infiere el tipo correcto del payload en cada rama del switch.
| Evento | Payload | Descripción |
|---|---|---|
| callspace:ready | CallspaceReadyPayload | El iframe está listo y el Voice Device inicializado. |
| callspace:ack | CallspaceAckPayload | Confirmación de que se recibió un comando. |
| callspace:error | CallspaceErrorPayload | Error interno del iframe. El campo recoverable indica si el agente puede continuar. |
| callspace:auth-required | CallspaceAuthRequiredPayload | La sesión del agente no está activa. Motivos: no-token, exchange-failed, session-invalid. |
| callspace:incoming.ringing | CallspaceIncomingCallPayload | Está sonando una llamada entrante. |
| callspace:incoming.accepted | CallspaceIncomingCallPayload | El agente aceptó la llamada entrante. |
| callspace:incoming.rejected | CallspaceIncomingCallPayload | El agente rechazó la llamada entrante. |
| callspace:call.started | CallspaceCallPayload | Una llamada (entrante o saliente) pasó a estado en-progreso. |
| callspace:call.updated | CallspaceCallPayload | Estado de la llamada actualizado: mute, hold, etc. |
| callspace:call.ended | CallspaceCallPayload | La llamada terminó. |
| callspace:agent.presence.changed | CallspacePresencePayload | El estado de presencia del agente cambió (deviceStatus, routingStatus). |
Ejemplo completo
Ejemplos de integración con React y con JavaScript usando postMessage.
Workspace completo con llamada desde host
Combina CallspaceFrame, ref, escucha de eventos y sendCallspaceStartCall para controlar el iframe desde React.
'use client';
import { useRef, useState, useCallback } from 'react';
import { CallspaceFrame } from '@callspace/embed-sdk/react';
import {
CALLSPACE_EMBED_EVENTS,
getCallspaceAppOrigin,
sendCallspaceOpenConversation,
sendCallspaceStartCall,
type CallspaceEventEnvelope,
} from '@callspace/embed-sdk/core';
const APP_ORIGIN = getCallspaceAppOrigin();
export function WorkspacePage() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [ready, setReady] = useState(false);
const handleEvent = useCallback((event: CallspaceEventEnvelope) => {
switch (event.event) {
case CALLSPACE_EMBED_EVENTS.READY:
setReady(true);
break;
case CALLSPACE_EMBED_EVENTS.CALL_ENDED:
console.log('Llamada finalizada', event.payload.callSid);
break;
}
}, []);
function startCall() {
const win = iframeRef.current?.contentWindow;
if (!win || !ready) return;
sendCallspaceStartCall(win, APP_ORIGIN, { phoneNumber: '+34600000000' });
}
function openConversation() {
const win = iframeRef.current?.contentWindow;
if (!win || !ready) return;
sendCallspaceOpenConversation(win, APP_ORIGIN, {
phoneNumber: '+34600000000',
});
}
return (
<div style={{ display: 'flex', flexDirection: 'column', height: '100vh' }}>
<button onClick={openConversation} disabled={!ready}>
Abrir conversacion
</button>
<button onClick={startCall} disabled={!ready}>
Llamar
</button>
<CallspaceFrame
ref={iframeRef}
iframeId="workspace"
auth={{ loginUrl: '/login' }}
onCallspaceEvent={handleEvent}
style={{ flex: 1, border: 'none' }}
/>
</div>
);
}Widget flotante mínimo
Agrega un teléfono flotante a cualquier app React con tres líneas.
import { PhoneWidget } from '@callspace/embed-sdk/react';
export function Layout({ children }: { children: React.ReactNode }) {
return (
<>
{children}
<PhoneWidget
placement="bottom-right"
auth={{ loginUrl: '/login' }}
/>
</>
);
}Iframe host con JavaScript
Monta el iframe manualmente y controla eventos y comandos con @callspace/embed-sdk/core.
import {
CALLSPACE_EMBED_EVENTS,
addCallspaceEventListener,
buildCallspaceEmbedUrl,
getCallspaceAppOrigin,
sendCallspaceOpenConversation,
sendCallspaceStartCall,
} from '@callspace/embed-sdk/core';
const iframe = document.querySelector('#callspace-frame');
const callButton = document.querySelector('#start-call');
const appOrigin = getCallspaceAppOrigin();
iframe.src = buildCallspaceEmbedUrl({
iframeId: 'workspace',
parentOrigin: window.location.origin,
});
const removeListener = addCallspaceEventListener({
iframe,
appOrigin,
onEvent(event) {
if (event.event === CALLSPACE_EMBED_EVENTS.READY) {
console.log('Iframe listo', event.payload);
}
},
});
callButton?.addEventListener('click', () => {
const targetWindow = iframe?.contentWindow;
if (!targetWindow) return;
sendCallspaceStartCall(targetWindow, appOrigin, {
phoneNumber: '+34600000000',
});
});
document
.querySelector('#open-conversation')
?.addEventListener('click', () => {
const targetWindow = iframe?.contentWindow;
if (!targetWindow) return;
sendCallspaceOpenConversation(targetWindow, appOrigin, {
phoneNumber: '+34600000000',
});
});
window.addEventListener('beforeunload', removeListener);