WebRTC Signaling
bext handles WebRTC signaling only — the exchange of SDP offers/answers and ICE candidates that peers need to establish a direct connection. bext does not relay media or act as a STUN/TURN server.
WebRTC signaling is part of bext-realtime. No additional feature flag is
required beyond the base realtime module.
How It Works
- Peers connect to bext via WebSocket (or SSE).
- They join a signaling room by name.
- bext relays SDP and ICE messages between peers in the room.
- Once peers exchange enough information, they connect directly (P2P).
- bext is no longer in the data path.
Peer A ──ws──▶ bext ◀──ws── Peer B
│ │
└──── direct P2P ─────────┘
Signaling Rooms
Rooms isolate signaling traffic. A room is created when the first peer joins and destroyed when the last peer leaves.
const ws = new WebSocket("wss://example.com/__bext/rtc/room-42");
ws.onmessage = (e) => {
const msg = JSON.parse(e.data);
switch (msg.type) {
case "PeerJoined":
startOffer(msg.peerId);
break;
case "Offer":
handleOffer(msg.peerId, msg.sdp);
break;
case "Answer":
handleAnswer(msg.peerId, msg.sdp);
break;
case "IceCandidate":
addCandidate(msg.peerId, msg.candidate);
break;
case "PeerLeft":
removePeer(msg.peerId);
break;
}
};
Room names are arbitrary strings. Use them to scope signaling to a call, a game lobby, or a file transfer session.
Message Types
All messages are JSON with a type field:
PeerJoined
Sent to existing peers when a new peer enters the room.
{
"type": "PeerJoined",
"peerId": "abc-123",
"metadata": { "name": "Alice" }
}
Offer
An SDP offer forwarded from one peer to another.
{
"type": "Offer",
"peerId": "abc-123",
"targetPeerId": "def-456",
"sdp": "v=0\r\no=- ..."
}
Answer
An SDP answer in response to an offer.
{
"type": "Answer",
"peerId": "def-456",
"targetPeerId": "abc-123",
"sdp": "v=0\r\no=- ..."
}
IceCandidate
A trickle ICE candidate relayed between peers.
{
"type": "IceCandidate",
"peerId": "abc-123",
"targetPeerId": "def-456",
"candidate": {
"candidate": "candidate:1 1 UDP ...",
"sdpMLineIndex": 0,
"sdpMid": "0"
}
}
PeerLeft
Sent to remaining peers when someone disconnects.
{
"type": "PeerLeft",
"peerId": "abc-123"
}
Configuration
[realtime.signaling]
enabled = true
max_room_size = 50
room_idle_timeout_secs = 300
| Field | Default | Description |
|---|---|---|
max_room_size |
50 | Max peers per room. |
room_idle_timeout_secs |
300 | Destroy room after this idle duration. |
No STUN/TURN
bext only routes signaling messages. It does not implement STUN or TURN
servers. For peers behind symmetric NATs, you need an external TURN
server. Point clients at it in the RTCPeerConnection config:
const pc = new RTCPeerConnection({
iceServers: [
{ urls: "stun:stun.example.com:3478" },
{ urls: "turn:turn.example.com:3478", username: "u", credential: "p" },
],
});
Use Cases
- Voice and video calls — exchange SDP for media tracks, then stream directly between browsers.
- File transfer — negotiate a DataChannel, transfer files P2P with no server bandwidth cost.
- Multiplayer games — DataChannels for low-latency game state sync between players.
- Screen sharing — add a screen capture track to an existing peer connection.
Authentication
Signaling WebSocket connections go through bext's standard auth middleware. Require authentication on the signaling endpoint to prevent unauthorized room access:
[[routes]]
path = "/__bext/rtc/*"
auth = "required"
Peer metadata (like display name) is attached on join and forwarded in
PeerJoined messages so other peers can identify who is in the room.
Metrics
| Metric | Description |
|---|---|
rtc_signaling_rooms_active |
Currently active rooms. |
rtc_signaling_peers_connected |
Total connected peers. |
rtc_signaling_messages_relayed |
Messages relayed between peers. |
bext handles signaling only — SDP and ICE exchange. It does not relay media. Peers behind symmetric NATs that cannot form a direct P2P path need an external TURN server; without one, those calls will silently fail to connect after the signaling phase completes successfully.
Protect signaling rooms with authentication (auth = "required" on the
/__bext/rtc/* route). Unauthenticated signaling endpoints let any
client join any room by guessing its name.
Related
- WebSockets — the transport used for signaling connections
- Server-Sent Events — alternative transport for one-way signaling messages
- WebTransport — HTTP/3-based low-latency transport for media-adjacent use cases
- Auth capability — securing signaling endpoints with JWT or API keys
- Includes: Realtime — higher-level realtime primitives in bext
Links
- webrtc.org — WebRTC project site and developer resources
- RFC 8829 — JavaScript Session Establishment Protocol (JSEP) — defines SDP offer/answer exchange used in WebRTC signaling