Architecture
How this guide should be used.
Step 01
Start with protected channels
Private and presence channels still come first. Your trusted server must verify who can join before any encrypted payload is delivered.Step 02
Encrypt only the sensitive payload
Strong privacy mode protects the JSON content inside the event. Channel names, event names, timing, connection details, and payload size are still routing metadata.Step 03
Use the right privacy target
Good targets are private chat bodies, call signaling offer/answer/ICE payloads, sensitive live location, sensitive delivery notes, and custom private events.Step 04
Plan key lifecycle before launch
Key generation, exchange, backup, rotation, recovery, and lost-device behavior must be clear before production rollout.Examples
Focused implementation notes.
Encrypt private chat JSON
Use the chat helper when message content should only be readable by room participants.
import {
decryptChatJson,
encryptChatJson,
generateAesGcmKey,
} from "@domvia/realtime-e2ee";
const roomKey = await generateAesGcmKey();
const encrypted = await encryptChatJson(
{
body: "Private message",
sender_id: "user-1",
},
roomKey,
{
roomId: "room-42",
messageId: "msg-1",
keyId: "room-42-v1",
keyVersion: 1,
},
);
const message = await decryptChatJson(encrypted, roomKey, {
roomId: "room-42",
messageId: "msg-1",
});Encrypt call signaling JSON
Use this when offer, answer, ICE, or media state payloads need stronger privacy. WebRTC still carries the actual audio and video media.
import {
decryptCallSignalJson,
encryptCallSignalJson,
} from "@domvia/realtime-e2ee";
const encryptedOffer = await encryptCallSignalJson(
{
sdp_type: "offer",
sdp: localDescription.sdp,
},
callKey,
{
callId: "call-9001",
eventName: "call.offer",
keyVersion: 1,
},
);
const offer = await decryptCallSignalJson(encryptedOffer, callKey, {
callId: "call-9001",
eventName: "call.offer",
});Encrypt custom realtime JSON
Use the universal helper for sensitive location, delivery, account, or custom private events.
import {
decryptRealtimeJson,
encryptRealtimeJson,
} from "@domvia/realtime-e2ee";
const encrypted = await encryptRealtimeJson(
{
note: "Sensitive delivery instruction",
},
eventKey,
{
scope: "delivery",
scopeId: "order-1001",
eventName: "delivery.private_note",
keyVersion: 1,
},
);
const payload = await decryptRealtimeJson(encrypted, eventKey, {
scope: "delivery",
scopeId: "order-1001",
eventName: "delivery.private_note",
});Safety
Rules before production traffic.
Use private or presence channels before encrypted events.
Use strong privacy for sensitive payload fields, not every routine event.
Do not store encryption keys in public client configuration.
Do not treat E2EE as a replacement for authorization, trusted origins, WSS, usage limits, or abuse controls.
Make recovery and key-loss behavior clear before production rollout.
Continue