← Back to docs

Calls SDK

Build WhatsApp-like in-app call signaling.

Use DomVia Realtime calls patterns for private audio and video call setup: ringing, accept, reject, end, WebRTC offer, answer, ICE candidates, and media state updates.

Package

@domvia/realtime-calls

install

npm install @domvia/realtime @domvia/realtime-calls @domvia/realtime-server

@domvia/realtime-calls

Built for focused realtime use cases.

Keep the SDK modular, documented, and safe to extend as new products are added.

Architecture

How this guide should be used.

Step 01

Use realtime for signaling only

DomVia Realtime should coordinate call state and WebRTC negotiation. The actual microphone, camera, and screen media should flow through WebRTC, not through normal realtime events.

Step 02

Keep call rooms protected

Use private or presence channels for calls. Your trusted server should verify the signed-in user belongs to the chat room, trip, support room, or delivery session before signing access.

Step 03

Start one-to-one before group calls

One-to-one WebRTC calls can start with peer-to-peer plus TURN fallback. Group calls should later use an SFU so every user does not have to stream to every other user.

Step 04

Separate call state from chat messages

Chat messages stay in @domvia/realtime-chat. Call sessions stay in @domvia/realtime-calls. A chat room can start a call, but the signaling layer should remain separate.

Examples

Focused implementation notes.

Client call session

Join a protected call signaling channel, listen for WebRTC negotiation events, and send call state updates when client events are enabled.

Client call session

import { DomViaRealtime } from "@domvia/realtime";
import {
  buildCallChannelName,
  createCallSession,
} from "@domvia/realtime-calls";

const realtime = DomViaRealtime.create({
  key: "pk_live_your_public_key",
  host: "ws.domvia.net",
  authEndpoint: "/realtime/auth",
  clientEvents: true,
});

const channelName = buildCallChannelName({
  channelKind: "private",
  scope: "room",
  scopeId: "chat-room-42",
  sessionId: "call-9001",
});

const call = createCallSession(realtime, {
  callId: "call-9001",
  channelName,
});

await call.waitUntilReady();

call.onOffer(async (event) => {
  await peerConnection.setRemoteDescription({
    type: "offer",
    sdp: event.sdp,
  });
});

call.onAnswer(async (event) => {
  await peerConnection.setRemoteDescription({
    type: "answer",
    sdp: event.sdp,
  });
});

call.onIceCandidate(async (event) => {
  await peerConnection.addIceCandidate({
    candidate: event.candidate,
    sdpMid: event.sdp_mid,
    sdpMLineIndex: event.sdp_mline_index,
  });
});

call.sendRinging({
  userId: "user-2",
});

Trusted server authorization

A private call channel should be signed only after your server confirms the user can access the room or call session.

Trusted server authorization

import { authorizePrivateChannel } from "@domvia/realtime-server";

app.post("/realtime/auth", async (request, response) => {
  const user = await requireSignedInUser(request);
  const channelName = request.body.channel_name;
  const socketId = request.body.socket_id;

  if (!channelName.startsWith("private-call.room.")) {
    return response.status(403).json({ message: "Forbidden" });
  }

  const roomId = extractRoomIdFromCallChannel(channelName);

  if (!await userCanAccessRoom(user.id, roomId)) {
    return response.status(403).json({ message: "Forbidden" });
  }

  return response.json(
    await authorizePrivateChannel({
      key: process.env.DOMVIA_REALTIME_KEY,
      secret: process.env.DOMVIA_REALTIME_SECRET,
      socketId,
      channelName,
    }),
  );
});

Where WebRTC fits

Realtime sends signaling messages. WebRTC uses those messages to establish the media path.

Where WebRTC fits

// DomVia Realtime signaling:
call.started
call.ringing
call.accepted
call.offer
call.answer
call.ice_candidate
call.ended

// WebRTC media:
microphone audio
camera video
screen sharing

// Production media reliability:
STUN for discovery
TURN for fallback
SFU later for group calls

Safety

Rules before production traffic.

Do not send raw audio or video through realtime events.

Use WebRTC for media and DomVia Realtime for signaling.

Protect every call channel with trusted server authorization.

Use TURN fallback for production one-to-one calls.

Use an SFU later for serious group calls.

Rate-limit noisy call signaling events like ICE candidates.

Keep call logs and missed-call records in your trusted server database.

Continue

Keep reading without crowding the main docs page.