Edge WebRTC SFU for browser-first, bidirectional interactive media — rooms, calls, and voice agents. Layer 1 of the WAVE Protocol Plane.
The status claims below trace to the shipped
wrangler.tomlfeature flags andsrc/route-dispatch.tsroute table at the pinned revision — flip a flag and the status here is wrong. When in doubt, the code is the receipt.
npm installnpx wrangler dev # local dev (full route surface)
npm run typecheck
npm run test # contract tests (vitest)
npm run deploy # wrangler deployLive. The substrate decision is made and shipped: a custom SFU on Cloudflare Realtime (Calls) + a per-room Durable Object — SfuClient (src/sfu.ts) is the media plane, RoomDO (src/room.ts) is the control plane (/v1/realtime/rooms/{room}/{intent}). Not LiveKit.
| Surface | Path | Status |
|---|---|---|
| Interactive rooms (join/publish/subscribe/renegotiate/leave) | /v1/realtime/rooms/{room}/{intent} |
Live — RoomDO substrate |
| WHIP v1 ingest (publish WebRTC) | /v1/whip/publish · /v1/whip/resource/{id} |
Live (armed 2026-06-24) |
| WHEP v1 egress (subscribe WebRTC) | /v1/whep/subscribe · /v1/whep/resource/{id} |
Live (armed 2026-07-01) |
| CF Stream → SFU bridge | /v1/stream/bridge/webhook |
Live (armed 2026-06-26) |
| Voice agents | /v1/realtime/agents/* |
Live (VOICE_AGENT_PROVIDER=wave, armed 2026-06-25) |
| Recording (managed PULL) | /rtk/recording-webhook |
Live (RT_RECORD=1, RT_ENCODER=managed) |
| Routed ingest router | /v1/realtime/ingress/{protocol}/{intent} |
Live — whip only; rtmp/srt/url return 501 (need a VM listener) |
| Routed egress router (wave-render/RunPod/Stream) | EGRESS_ROUTER_ENABLED |
Inert — backends built, not armed |
| Room presence WebSocket | /v1/realtime/rooms/{room}/presence |
Inert — PRESENCE_ENABLED off |
| Channel pub/sub (connect/publish/presence/history) | /v1/connect · /v1/channels/{channel}/{publish,presence,history} |
Live — ChannelDO substrate |
| Multi-region cascade | RT_CASCADE |
Inert — single-region today |
An OpenAPI 3.1 spec (docs/api/openapi.yaml) and a 200+ file contract test suite run on every PR.
Like Stripe is for payments and Resend is for email — WAVE is for live streaming and video. This spoke is the interactive media layer: 2-way N-N calls and voice agents, the complement to wave-moq-edge (1-to-many broadcast).
| Use case | Spoke |
|---|---|
| 1-1 or N-N call (rooms, voice agents) | wave-realtime-edge |
| 1-to-many livestream | wave-moq-edge |
| Studio-grade broadcast (NDI/Dante/SRT) | wave-bridge-edge |
| Browser playback of recorded content | the clip service |
browser ──WebRTC──▶ wave-realtime-edge (this spoke)
│ edge SFU · Cloudflare Realtime (Calls) + per-room Durable Object
│ /api/* → api.wave.online (gateway-enforced)
▼
the WAVE API gateway → auth · scope · meter
docs/api/openapi.yaml — OpenAPI 3.1 contract (passing contract tests) docs/REALTIME.md — realtime API surface and substrate notes threat-model.md, SECURITY.md, CONTRIBUTING.md
| Method | Path | Does |
|---|---|---|
GET |
/health |
Liveness check, no auth |
GET |
/ |
Branded landing page |
POST |
/rtk/join |
RealtimeKit meeting create + participant token mint |
POST |
/rtk/turn |
TURN/ICE credential mint (WebRTC NAT traversal) |
POST |
/rtk/recording-webhook |
Recording status update (managed PULL puller) — public by design; self-authenticates via the rtk-signature header (RSA-SHA256), no bearer |
POST |
/v1/realtime/rooms/{room}/{intent} |
Room signaling: join/publish/subscribe/renegotiate/leave (RoomDO) |
POST |
/v1/whip/publish |
WHIP ingest offer handshake -> 201 + SDP answer |
PATCH |
/v1/whip/resource/{id} |
WHIP trickle-ICE candidate update |
DELETE |
/v1/whip/resource/{id} |
WHIP teardown, stops the ingest meter |
POST |
/v1/whep/subscribe |
WHEP egress offer handshake -> 201 + SDP answer |
PATCH |
/v1/whep/resource/{id} |
WHEP trickle-ICE candidate update |
DELETE |
/v1/whep/resource/{id} |
WHEP teardown, stops the egress meter |
POST |
/v1/stream/bridge/webhook |
CF Stream → SFU bridge receiver (HMAC-verified) |
POST |
/v1/realtime/agents/{intent} |
Voice agent bind/info (VOICE_AGENT_PROVIDER=wave) |
POST |
/v1/realtime/ingress/{protocol}/{intent} |
Routed ingest create/delete (whip live; rtmp/srt/url return 501) |
GET |
/v1/realtime/rooms/{room}/presence |
WebSocket upgrade for room presence/state-sync (PRESENCE_ENABLED gated, inert) |
GET |
/v1/connect |
Subscribe to a channel — WebSocket upgrade (?channel=<id>&as=<member>); sends welcome then live message/join/leave/presence frames |
POST |
/v1/channels/{channel}/publish |
Publish one event to every channel subscriber |
GET |
/v1/channels/{channel}/presence |
Current channel member list |
GET |
/v1/channels/{channel}/history |
Recent channel events (bounded ring buffer, ≤50) |
| Transport | Direction | Status |
|---|---|---|
| WHIP | in | |
| WHEP | out | |
| CF Realtime (Calls) SFU rooms | bidir | |
| CF Stream → SFU bridge | in | |
| presence-websocket | bidir |
edge · webrtc · sfu · x402-metered · protocol-plane-layer-1
Built by WAVE Online, LLC · wave.online · Docs · LinkedIn