WHIP
Publish video from a browser or a WebRTC encoder with WHIP, the HTTP way to start a WebRTC publish.
WHIP (RFC 9725) is how a WebRTC publisher hands a stream to a server: one HTTP request with the publisher's offer, one answer back. The ingest takes the video and repackages it for the relays, without re-encoding. A browser can publish this way with no software to install, and OBS has a WHIP output.
Video only for now. The ingest accepts the offer's audio and drops it: a stream sent over WHIP plays without sound. For sound, use SRT or RTMP. WHIP's audio is still to be built.
What you need
-
A stream that lists
whipin itsingest:curl -s $API/v1/streams -H "Authorization: Bearer $SK" -H "Content-Type: application/json" \ -d '{"name":"cam","admission":"public","ingest":["whip"]}'The answer's
publish.stream_key(stk_..., shown once) is the WHIP bearer token. Lost it?POST /v1/streams/{id}/stream-keymakes a new one. -
The WHIP endpoint's address. The API's
publishobject does not carry it yet, only the key.
How a publish goes
- The publisher sends
POST <endpoint>withContent-Type: application/sdp,Authorization: Bearer stk_...and its SDP offer. - The ingest answers
201 Createdwith its SDP answer and aLocationheader, a path of the form/whip/session/<id>. A wrong key, or a key whose stream does not listwhip, gets401; an offer it cannot use gets406; a body that is not SDP gets400. - Media flows over WebRTC (UDP, DTLS-SRTP). The ingest offers H.264 and Opus. It does not need a STUN server of its own: it is reachable at one address it announces.
- To stop, the publisher sends
DELETEto theLocationresource with the same bearer key. The answer is200, and the key is free for the next publisher. With no packet for 10 s the ingest ends the session itself.
OPTIONS is answered with the CORS headers a page needs, so a browser page
can publish straight to the endpoint. The stream key is in the request
header, so the endpoint is HTTPS (plain HTTP is only for a gateway on
loopback).
From OBS
Settings, Stream: Service "WHIP", Server: the endpoint, Bearer Token: the stream key. Then the output settings of OBS and SRT: keyframe interval 1 s, no B-frames. Choose H.264 as the encoder.
From a browser
const pc = new RTCPeerConnection();
const media = await navigator.mediaDevices.getUserMedia({ video: true });
media.getTracks().forEach(t => pc.addTrack(t, media));
await pc.setLocalDescription(await pc.createOffer());
const res = await fetch(ENDPOINT, {
method: "POST",
headers: { "Content-Type": "application/sdp", Authorization: "Bearer " + STREAM_KEY },
body: pc.localDescription.sdp,
});
if (res.status !== 201) throw new Error("WHIP " + res.status);
const resource = new URL(res.headers.get("Location"), ENDPOINT); // DELETE this to stop
await pc.setRemoteDescription({ type: "answer", sdp: await res.text() });Put a stream key in a page only for yourself: anyone who can read the page can publish to your stream. For a page that other people use, have your backend hold the key and start the publish.
Two things to know about browsers:
- Browsers pick H.264 only if you ask. Order the offer's video codecs with
RTCRtpTransceiver.setCodecPreferencesso H.264 comes first. - A browser lowers its resolution when the network is poor. The stream's track list keeps the size of the first frame; players follow the new size at the next keyframe, but anything that reads the size from the track list will see the first one.
ffmpeg
ffmpeg's WHIP output is not supported: its DTLS certificate uses a key type the ingest does not accept, and the handshake fails. From ffmpeg, publish over SRT or RTMP.
Keyframes and loss
The ingest does not buffer. It hands each frame on when its last packet arrives. A lost packet waits for one retransmission request; a frame that cannot be completed is dropped, and the ingest asks the publisher for a new keyframe at once (a picture loss indication, at most about three a second). A publisher that never sends keyframes of its own therefore recovers, which is why WHIP tolerates loss well. Keep the keyframe interval at 1 s anyway: a viewer joining starts at a keyframe.
When it does not work
| Symptom | Likely cause |
|---|---|
401 on the POST | the bearer key is wrong, or the stream does not list whip in its ingest |
406 on the POST | the ingest could not accept the offer (the answer's body says why; check that it offers H.264) |
400 on the POST | the body is not an SDP offer |
201, but the picture never starts | the publisher cannot reach the ingest's UDP address, or the browser chose VP8 or VP9 instead of H.264 |
| Plays, but no sound | expected: WHIP carries video only for now |