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