Appearance
Voice & video calls
Who is this page for?
Gateway / DevOps — run the media server, set env vars, enable calls on your account.
App developers → Calls 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
| Plan | Gateway | Client packages |
|---|---|---|
| Chat only | Messaging as usual; no media server env | @sendsar/chat-sdk-javascript and, for React apps, @sendsar/chat-uikit-react |
| Chat + calls | Media 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:gatewayEnvironment variables
Docker (docker/.env):
| Variable | Purpose |
|---|---|
LIVEKIT_NODE_IP | Host IP the media server advertises for WebRTC (LAN testing) |
Gateway (apps/gateway/.env):
| Variable | Purpose |
|---|---|
LIVEKIT_URL | Internal URL (e.g. ws://livekit:7880 in Docker) |
LIVEKIT_PUBLIC_URL | URL browsers use (e.g. ws://localhost:7880 or LAN IP) |
LIVEKIT_API_KEY / LIVEKIT_API_SECRET | Match 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):
| Field | Default | Description |
|---|---|---|
enabled | true in dev | Set false for chat-only accounts |
maxParticipants | 8 | Max participants per call |
ringTimeoutSeconds | 45 | 1:1 ring timeout; server marks missed + call.missed webhook; returned on call REST |
defaultType | "video" | Default when type omitted |
Signaling API (session JWT)
| Method | Path | Description |
|---|---|---|
GET | /chat/rooms/:roomId/calls/active | Active call in room |
POST | /chat/rooms/:roomId/calls | Start or rejoin — body { "type": "audio" | "video" } |
POST | …/calls/:callId/accept | Accept incoming (1:1); returns { call, livekit } |
POST | …/calls/:callId/decline | Decline (1:1 ends call; no-op in group rooms) |
POST | …/calls/:callId/end | End for all (1:1 or group creator); body { reason?: "cancelled" | "no_answer" } for ringing 1:1 |
POST | …/calls/:callId/leave | Leave group call without ending for others |
POST | …/calls/:callId/token | Refresh 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
- Calls SDK — signaling + media for app developers
- API Reference — Calls tag in OpenAPI