Skip to content

Voice & video calls

Who is this page for?

Gateway / DevOps — run the media server, set env vars, enable calls on your account.
App developersCalls SDK. Your app uses the url + token the gateway returns.

Complete the Quick start first. Calls need a media server on the gateway (LiveKit in self-hosted setups) and calls.enabled: true on your account (account settings). Chat-only accounts can skip this page.

Sendsar splits calls into signaling (gateway REST + Socket.IO) and WebRTC media (hosted on the gateway). Client apps connect with the credentials from startCall()url + token.


Who needs what

PlanGatewayClient packages
Chat onlyMessaging as usual; no media server env@sendsar/chat-sdk-javascript and, for React apps, @sendsar/chat-uikit-react
Chat + callsMedia server + calls.enabled: true@sendsar/chat-sdk-javascript, media kit (planned), media UI kit (planned) (+ @sendsar/chat-uikit-react for messaging UI)

When calls.enabled is false on your account, all POST /chat/rooms/:id/calls/* requests return 403.

Integrator guide: Calls SDK.


Media server (self-hosted)

Self-hosted Sendsar uses LiveKit for WebRTC. From the monorepo root:

bash
pnpm dev:infra   # Postgres, Redis, LiveKit (Docker)
pnpm dev:gateway

Environment variables

Docker (docker/.env):

VariablePurpose
LIVEKIT_NODE_IPHost IP the media server advertises for WebRTC (LAN testing)

Gateway (apps/gateway/.env):

VariablePurpose
LIVEKIT_URLInternal URL (e.g. ws://livekit:7880 in Docker)
LIVEKIT_PUBLIC_URLURL browsers use (e.g. ws://localhost:7880 or LAN IP)
LIVEKIT_API_KEY / LIVEKIT_API_SECRETMatch docker/livekit.yaml dev keys

The demo app never needs LIVEKIT_* — only the gateway mints tokens and returns livekit.url + livekit.token to clients.

LAN / second device

Set LIVEKIT_NODE_IP to your machine's LAN IP and LIVEKIT_PUBLIC_URL=ws://<that-ip>:7880 on the gateway so phones on the same Wi‑Fi can connect.


Account settings

Per-account flags live in tenant JSON settings. GET /v1/tenant/settings returns calls (session JWT or API key). PATCH /v1/tenant/settings merges calls (API key only):

FieldDefaultDescription
enabledtrue in devSet false for chat-only accounts
maxParticipants8Max participants per call
ringTimeoutSeconds451:1 ring timeout; server marks missed + call.missed webhook; returned on call REST
defaultType"video"Default when type omitted

Signaling API (session JWT)

MethodPathDescription
GET/chat/rooms/:roomId/calls/activeActive call in room
POST/chat/rooms/:roomId/callsStart or rejoin — body { "type": "audio" | "video" }
POST…/calls/:callId/acceptAccept incoming (1:1); returns { call, livekit }
POST…/calls/:callId/declineDecline (1:1 ends call; no-op in group rooms)
POST…/calls/:callId/endEnd for all (1:1 or group creator); body { reason?: "cancelled" | "no_answer" } for ringing 1:1
POST…/calls/:callId/leaveLeave group call without ending for others
POST…/calls/:callId/tokenRefresh LiveKit JWT (600s TTL)

Group rooms (>2 participants): start is active immediately; use leave instead of end unless you are the call creator. Full semantics: apps/gateway/CALLS.md in the repo (group vs 1:1 lifecycle).

Socket events: use client.callInvite(), callAccepted(), callDeclined(), callEnded() (see Client SDK — Calls).


Call history in messages

Completed, missed, and declined calls are stored as data-call message parts in the room timeline (Messenger-style). That is part of messaging, not the calls media package — see Calls SDK.


Next steps

Sendsar — headless chat for B2B platforms