Embed SDKDisponible@callspace/embed-sdk

Embed SDK de CallSpace

Integra el workspace de llamadas, el teléfono flotante o la superficie de llamada entrante directamente en tu aplicación con la entrada core para JavaScript/TypeScript o los componentes React y un protocolo postMessage tipado.

Instalación rápida

Paquete con entrada core para JavaScript/TypeScript y entrada react con componentes listos y funciones de comando tipadas.

pnpm add @callspace/embed-sdk

Paquete

@callspace/embed-sdk

Runtime

JavaScript / React 19+

Protocolo

postMessage

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

bash
# pnpm
pnpm add @callspace/embed-sdk

# npm
npm install @callspace/embed-sdk

Entrypoints recomendados

TypeScript
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

PropTipoObligatorioDefaultDescripción
appUrlstringNohttps://app.callspace.ioURL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io.
iframeIdstringNoID que identifica este iframe en mensajes multi-iframe.
parentOriginstringNowindow.location.originOrigen del host. Se detecta automáticamente si se omite.
debugbooleanNofalseActiva logs verbose del protocolo postMessage en consola.
autoInitbooleanNotrueEnvía INIT y CONFIGURE automáticamente al cargar el iframe.
configurePayloadCallspaceConfigurePayloadNoTema y contexto enviados en el comando CONFIGURE inicial.
auth{ loginUrl: string; title?: string; description?: string; buttonLabel?: string }NoSi la sesión no está activa, muestra una pantalla de login integrada.
onCallspaceEvent(event: CallspaceEventEnvelope) => voidNoCallback 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

TSX
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

PropTipoObligatorioDefaultDescripción
appUrlstringNohttps://app.callspace.ioURL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io.
iframeIdstringNoID que identifica este iframe en mensajes multi-iframe.
parentOriginstringNowindow.location.originOrigen del host. Se detecta automáticamente si se omite.
debugbooleanNofalseActiva logs verbose del protocolo postMessage en consola.
autoInitbooleanNotrueEnvía INIT y CONFIGURE automáticamente al cargar el iframe.
configurePayloadCallspaceConfigurePayloadNoTema y contexto enviados en el comando CONFIGURE inicial.
auth{ loginUrl: string; title?: string; description?: string; buttonLabel?: string }NoSi la sesión no está activa, muestra una pantalla de login integrada.
onCallspaceEvent(event: CallspaceEventEnvelope) => voidNoCallback 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

PropTipoObligatorioDefaultDescripción
appUrlstringNohttps://app.callspace.ioURL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io.
iframeIdstringNoID que identifica este iframe en mensajes multi-iframe.
parentOriginstringNowindow.location.originOrigen del host. Se detecta automáticamente si se omite.
debugbooleanNofalseActiva logs verbose del protocolo postMessage en consola.
autoInitbooleanNotrueEnvía INIT y CONFIGURE automáticamente al cargar el iframe.
configurePayloadCallspaceConfigurePayloadNoTema y contexto enviados en el comando CONFIGURE inicial.
auth{ loginUrl: string; title?: string; description?: string; buttonLabel?: string }NoSi la sesión no está activa, muestra una pantalla de login integrada.
onCallspaceEvent(event: CallspaceEventEnvelope) => voidNoCallback que recibe todos los eventos emitidos por el iframe.
panelWidthnumberNo380Ancho del panel flotante en px.
panelHeightnumberNo620Alto del panel flotante en px.
placement'bottom-right' | 'bottom-left'No'bottom-right'Esquina donde se ancla el panel y el FAB por defecto.
openbooleanNoEstado controlado del panel. Combinar con onOpenChange.
onOpenChange(open: boolean) => voidNoCallback cuando el widget solicita abrir o cerrar.
defaultOpenbooleanNofalseEstado inicial para uso no controlado.
renderTrigger({ open, toggle }) => ReactNodeNoRender 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

TSX
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

PropTipoObligatorioDefaultDescripción
appUrlstringNohttps://app.callspace.ioURL de la instancia CallSpace. Por defecto apunta a https://app.callspace.io.
iframeIdstringNoID que identifica este iframe en mensajes multi-iframe.
parentOriginstringNowindow.location.originOrigen del host. Se detecta automáticamente si se omite.
debugbooleanNofalseActiva logs verbose del protocolo postMessage en consola.
autoInitbooleanNotrueEnvía INIT y CONFIGURE automáticamente al cargar el iframe.
configurePayloadCallspaceConfigurePayloadNoTema y contexto enviados en el comando CONFIGURE inicial.
auth{ loginUrl: string; title?: string; description?: string; buttonLabel?: string }NoSi la sesión no está activa, muestra una pantalla de login integrada.
onCallspaceEvent(event: CallspaceEventEnvelope) => voidNoCallback que recibe todos los eventos emitidos por el iframe.
autoHidebooleanNofalseOculta 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

TSX
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ámetroTipoDescripción
targetWindowWindowcontentWindow del iframe de destino.
targetOriginstringOrigen del iframe. Usar getCallspaceAppOrigin(appUrl).
payload.phoneNumberstringNúmero de teléfono destino con prefijo internacional.
payload.callerIdstring | nullNúmero a mostrar como caller ID (opcional).
TypeScript
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ámetroTipoDescripción
targetWindowWindowcontentWindow del iframe de destino.
targetOriginstringOrigen del iframe. Usar getCallspaceAppOrigin(appUrl).
payload.phoneNumberstringAbre el contexto completo de ese número en el workspace. Si no existe todavía, abre Nueva conversación con el número precargado.
TypeScript
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.

TypeScript
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.

EventoPayloadDescripción
callspace:readyCallspaceReadyPayloadEl iframe está listo y el Voice Device inicializado.
callspace:ackCallspaceAckPayloadConfirmación de que se recibió un comando.
callspace:errorCallspaceErrorPayloadError interno del iframe. El campo recoverable indica si el agente puede continuar.
callspace:auth-requiredCallspaceAuthRequiredPayloadLa sesión del agente no está activa. Motivos: no-token, exchange-failed, session-invalid.
callspace:incoming.ringingCallspaceIncomingCallPayloadEstá sonando una llamada entrante.
callspace:incoming.acceptedCallspaceIncomingCallPayloadEl agente aceptó la llamada entrante.
callspace:incoming.rejectedCallspaceIncomingCallPayloadEl agente rechazó la llamada entrante.
callspace:call.startedCallspaceCallPayloadUna llamada (entrante o saliente) pasó a estado en-progreso.
callspace:call.updatedCallspaceCallPayloadEstado de la llamada actualizado: mute, hold, etc.
callspace:call.endedCallspaceCallPayloadLa llamada terminó.
callspace:agent.presence.changedCallspacePresencePayloadEl 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.

TSX
'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.

TSX
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.

JavaScript
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);