§ 7.5  Module 7 — From Two to Two Thousand: Data, Topologies, and Trust

Broadcast Convergence: WHIP, WHEP, and Cascading SFUs

March 2020: every mesh-topology product melted at four participants, and the architecture that won can read your media.Course notes

By the end of this lesson

  • Implement a WHIP client: POST application/sdp offer, handle 201 + Location, DELETE teardown
  • Position WebRTC's sub-500ms latency against LL-HLS and HLS on the latency spectrum
  • Design SFU cascade routing under per-link RTT/loss/capacity constraints
  • Explain the irony: the protocol that standardized no signaling grew a standard signaling profile

The failure that forced the design

RTMP playback died with Flash in 2020; RTMP ingest — the broadcast world's contribution workhorse since 2002 — lingers as the legacy default in OBS and every streaming service, but it caps out at multi-second latency, an ossified H.264/AAC codec set, and no encryption by default. WebRTC had the latency but, by Module 2's deliberate design, no standard signaling; every service invented its own POST-an-SDP endpoint. WHIP became RFC 9725 in March 2025. WHEP mirrors the single-POST pattern for egress, but draft-ietf-wish-whep-03 expired on February 19, 2026 and is not an RFC as of July 2026. OBS 30 shipped native WHIP output. War story: a two-region SFU cascade doubled latency because the inter-SFU link ran untuned over public internet — cascade links need their own congestion control and regional keyframe caching.

RTMP's decline and streaming's latency hunger; why WebRTC ingest needed a standard signaling profile after all (irony noted, closing the loop on Module 2's signaling void); WHIP/WHEP's single-POST design; and geo-distributed SFU cascading — implemented against a mock in-page HTTP endpoint.

Cross-references: WHIP & WHEP.

7.5.0Learning objectives

  • Implement a WHIP client: POST application/sdp offer, handle 201 + Location, DELETE teardown
  • Position WebRTC's sub-500ms latency against LL-HLS and HLS on the latency spectrum
  • Design SFU cascade routing under per-link RTT/loss/capacity constraints
  • Explain the irony: the protocol that standardized no signaling grew a standard signaling profile
observestates · countersdecidebudget · invariantactthen verify
Figure 7.5 — The operational loop used throughout the advanced modules.

7.5.1RTMP's decline; the latency spectrum

The operational claim is precise: RTMP's decline; the latency spectrum: WebRTC (<500 ms) vs LL-HLS (~3s) vs HLS (10-30s). Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.

Engineering consequence. RTMP's decline; the latency spectrum: WebRTC (<500 ms) vs LL-HLS (~3s) vs HLS (10-30s). Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.

WHIP depends on CORS headers most servers forget to exposedeployment

WHIP (RFC 9725) is one HTTP POST with Content-Type: application/sdp; the 201 response carries the answer in the body, the session resource in Location, and ICE server configuration in Link headers. Cross-origin, the browser will not let JavaScript read Location or Link unless the server sends Access-Control-Expose-Headers naming them. The POST succeeds, the answer applies, media may even flow — and you cannot DELETE the session or read the TURN configuration, with nothing in the console beyond a null header.

What to do

Require Access-Control-Expose-Headers: Location, Link on the WHIP endpoint and assert that response.headers.get('Location') is non-null before proceeding. Test cross-origin from the start; same-origin testing hides this entirely.

7.5.2WHIP mechanics

WHIP reduces ingest signaling to an HTTP resource lifecycle: POST an SDP offer as application/sdp, receive a 201 response and answer, PATCH trickle candidates when used, and DELETE the resource at teardown. WHEP applies the corresponding pattern to egress.

Engineering consequence. WHIP mechanics: POST application/sdp, 201+Location, PATCH/trickle, DELETE; WHEP for egress (draft-ietf-wish-whep-03 expired in February 2026 and is not an RFC as of July 2026). Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.

Interactive 3D instrument

Broadcast convergence — one POST in, a cascade out

A 3D instrument you drive yourself, one variable at a time. It needs JavaScript and WebGL, so it is not shown in this static copy of the page.

7.5.3OBS integration; where sub-second broadcast matters (auctions, sports betting, interactive)

The operational claim is precise: OBS integration; where sub-second broadcast matters (auctions, sports betting, interactive). Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.

Engineering consequence. OBS integration; where sub-second broadcast matters (auctions, sports betting, interactive). Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.

Most WHIP servers do not implement PATCH, so you are back to non-trickledeployment

Trickle and ICE restart in WHIP are optional, done with HTTP PATCH carrying application/trickle-ice-sdpfrag (RFC 8840). A large share of deployed servers return 405 or 501. Without PATCH you must gather fully before the POST, which reintroduces the gathering-hang problem — a single unhealthy TURN entry adds ten seconds to every publish attempt, and the user experiences it as a slow, unreliable "Go Live" button.

What to do

Probe for PATCH support and fall back gracefully. In the non-trickle path, race iceGatheringState === 'complete' against a 2–3 s timeout and POST whatever candidates you have; a partial candidate list usually connects fine.

7.5.4Cascading SFUs

The operational claim is precise: Cascading SFUs: geo-distribution, inter-SFU links, regional keyframe caching. Treat it as an observable contract. Record the state transition or counter that proves it, test the failure path as well as the happy path, and keep units and clock domains explicit. That discipline is what separates a plausible WebRTC explanation from a production diagnosis.

Engineering consequence. Cascading SFUs: geo-distribution, inter-SFU links, regional keyframe caching. Verify the behavior with a controlled trace before interpreting a live call: establish the expected state, change one variable, and compare deltas rather than isolated values. In production, attach the observation to a timestamp and media direction so the same evidence cannot be mistaken for a different failure.

7.5.AA worked reasoning trace

Start with the claim “RTMP's decline; the latency spectrum: WebRTC (<500 ms) vs LL-HLS (~3s) vs HLS (10-30s)” and turn it into a falsifiable trace. Write down the input condition, the expected state transition, the counter or packet field that should change, and the deadline by which it must change. This prevents a familiar mistake: observing a symptom after several mechanisms have already reacted and assigning it to whichever mechanism has the most recognizable name. The causal order matters. A queue grows before loss; an ICE path fails before a restart selects a new pair; a decoder loses reference state before frames stop rendering. Preserve that order in the evidence.

Now hold every unrelated variable still. Use canned timestamps, SDP, stats-shaped records, or a permission-free loopback source so the same input can be replayed. Record both media directions separately. For cumulative counters, retain two samples and divide the difference by their timestamp interval. For state machines, retain the ordered event sequence rather than the last state alone. For negotiated features, compare the offer, answer, and live stats result. These three views answer different questions: what was proposed, what was accepted, and what the browser actually used.

The first lab, The WHIP Conformance Test, applies this discipline to a bounded implementation problem. Implement a WHIP client against a mock in-page HTTP endpoint (harness provides mockFetch wrapping the second loopback peer): POST the offer with Content-Type: application/sdp, read the answer from the 201 body, honor Location for the resource, DELETE on teardown. Graded like a protocol conformance suite; hints catch JSON-wrapping the SDP and POSTing the answer instead of reading it. Before coding, identify the invariant hidden in that brief and the tempting shortcut the tests should reject. Then run the reference trace, perturb one boundary value, and explain why the result changes. If the code passes but the explanation cannot predict the perturbed result, the implementation is memorized rather than understood.

WHEP needs recvonly transceivers before createOffer, or you offer nothingapi

A subscriber has no local tracks, so a peer connection with nothing added produces an offer with no m-lines, and the server has nothing to answer. You must call pc.addTransceiver('video', { direction: 'recvonly' }) and the same for audio before createOffer(). Because the resulting SDP looks superficially valid, the failure presents as "the server never sends media" rather than as a malformed offer.

What to do

Create the recvonly transceivers explicitly and in the order the server expects (audio then video is the common convention). Also DELETE the WHEP/WHIP resource on unload — use fetch(url, { method: 'DELETE', keepalive: true }) in a pagehide handler, since sendBeacon cannot issue DELETE and orphaned sessions consume server slots indefinitely.

7.5.BVerification protocol

  1. Name the budget. State whether the scarce quantity is latency, bitrate, CPU, memory, reliability, or negotiation time, and attach a unit.
  2. Choose the observation point. Decide whether proof lives in SDP, an RTP/RTCP field, an API state, an event sequence, decoded output, or a joined stats record.
  3. Build the clean control. Establish one trace in which the mechanism succeeds before injecting loss, delay, glare, a codec mismatch, or a network change.
  4. Change one variable. Keep clocks, direction, source cadence, and topology fixed so the observed difference has a defensible cause.
  5. Test both sides of the threshold. A deadline or bitrate rule is not demonstrated by one passing value; include a value just inside and just outside the boundary.
  6. Close the loop. After acting, prove that the intended media outcome changed. Calling an API successfully is not evidence that frames flowed or quality recovered.

7.5.CProduction boundaries

A browser loopback is deliberately cleaner than the internet. It removes signaling latency, hostile NAT behavior, relay geography, wireless contention, device heat, and competing encoders. That makes it ideal for proving protocol and API invariants, but a dangerous basis for capacity claims. Carry the invariant into a second test with a canned hostile trace: reordered packets, a delayed candidate, a missing remote stats object, a bitrate step, or a state transition that recovers before the debounce timer. The implementation should remain correct even when optional fields are absent and events arrive more than once.

Feature detection is also part of correctness. Browser support for a codec, scalability mode, encoded transform, or transport API does not imply that the negotiated peer supports it or that hardware acceleration is active. Prefer capability checks, preserve a standards-based fallback, and label any simulation honestly. The graded result should measure the learner's algorithm; an optional live coda may demonstrate the real API without making a camera, operating-system codec, or network service a hidden test dependency.

Finally, connect the mechanism to the last topic in the lesson: “Cascading SFUs: geo-distribution, inter-SFU links, regional keyframe caching”. Advanced WebRTC failures cross boundaries. Congestion control changes encoder output; layer selection changes keyframe demand; end-to-end encryption constrains what an SFU can inspect; a watchdog can collide with negotiation; observability can mislabel a path if its joins are wrong. At every boundary, define ownership and an idempotent action. A retry must not create duplicate work, a stale event must not overwrite newer state, and an escalation must have a terminal cleanup path.

Use the problem set as an executable notebook. Keep a short record beside each answer: the invariant, the decisive evidence, the rejected alternative, and the production caveat. That record is more valuable than a green checkmark because it can be reused when a real call presents the same mechanism through noisier evidence.

A common error

Symmetric NAT always kills P2P — direct traversal fails roughly when both sides have address/port-dependent mapping; one-sided symmetric usually connects.

Problems for § 7.5Executable understanding

Implement the decision rules, run every assertion, then use the explanations to distinguish a wrong result from a wrong model.

The WHIP Conformance Test. Implement a WHIP client against a mock in-page HTTP endpoint (harness provides mockFetch wrapping the second loopback peer): POST the offer with Content-Type: application/sdp, read the answer from the 201 body, honor Location for the resource, DELETE on teardown. Graded like a protocol conformance suite; hints catch JSON-wrapping the SDP and POSTing the answer instead of reading it.

Lab 1

The WHIP Conformance Test

Graded in the browser against 1 assertion; the editor and harness require JavaScript.

Route the Cascade. Given a 3-region SFU graph with per-link RTT/loss/capacity, implement routeSubscriber(region, publisherRegion) minimizing hop latency under capacity constraints. Assertions match the reference optimum on 4 canned graphs; hints catch forgetting the publisher's home SFU as the mandatory first hop.

Lab 2

Route the Cascade

Graded in the browser against 1 assertion; the editor and harness require JavaScript.

Check your understanding

Auto-graded check

4 auto-graded questions with an explanation for every wrong answer. Requires JavaScript. (m7-l5-quiz)

Field quirks

What the specifications say, and what the browsers actually do, are two different documents. These are the divergences that cost production teams their weekends.

Cascaded SFUs put two congestion controllers in seriesperf

Each forwarding hop adds latency but, more importantly, each hop has its own bandwidth estimate and the downstream hop cannot slow the upstream one. When the last mile congests, the second SFU has more traffic arriving than it can deliver and its only options are to drop or to select a lower layer — it cannot ask the publisher to send less without a signalling path that most cascades do not have. So a congested subscriber degrades by loss rather than by adaptation, which is far more damaging.

What to do

Cascade only with simulcast or SVC so each hop has discrete layers to choose from, and make layer selection per-subscriber at the edge. Do not propagate REMB or TWCC-derived bitrates across hops as if they were a single path; propagate layer decisions instead.

WebRTC: From First Principles · Module 7, Lesson 5