Skip to content

API in Capabilities

WebRTC API

Published Updated

Widely available since 2017-09W3C draft

WebRTC carries audio, video, and arbitrary data directly between two browsers (or a browser and a media server) over encrypted UDP: RTCPeerConnection negotiates codecs and transports through an SDP offer/answer exchange, ICE finds a network path, addTrack() attaches MediaStreamTracks captured with getUserMedia(), and RTCDataChannel carries application messages over SCTP. The exchange of offers, answers, and ICE candidates (signalling) is left to the application; any channel the page already has, usually a WebSocket, works.

The unprefixed RTCPeerConnection constructor has been available in Chrome 56, Edge 15, Firefox 44, Safari 11, Samsung Internet 6, and Android WebView 56, which is why Baseline lists it as widely available since 2017-09. getUserMedia() is [SecureContext], so capture needs https:// or localhost; the peer connection itself does not, but a page that cannot capture rarely has anything to send.

new RTCPeerConnection()
new RTCPeerConnection(configuration)
pc.createOffer()
pc.createOffer(options)
pc.createAnswer()
pc.setLocalDescription()
pc.setLocalDescription(description)
pc.setRemoteDescription(description)
pc.addIceCandidate(candidate)
pc.addTrack(track)
pc.addTrack(track, ...streams)
pc.createDataChannel(label)
pc.createDataChannel(label, options)
pc.close()
navigator.mediaDevices.getUserMedia(constraints)

createOffer() and createAnswer() resolve with an RTCSessionDescriptionInit ({ type, sdp }); the set-description methods and addIceCandidate() resolve with undefined and run on the connection’s operations chain, so they execute in call order. Calling setLocalDescription() with no argument creates the offer or answer the current signalingState calls for. addTrack() returns an RTCRtpSender synchronously, createDataChannel() returns an RTCDataChannel synchronously, and getUserMedia() resolves with a MediaStream.

The constructor’s RTCConfiguration, createDataChannel()’s RTCDataChannelInit, and getUserMedia()’s MediaStreamConstraints are the dictionaries a typical call touches (see RTCConfiguration members (w3.org)).

Member Type Required Description
configuration.iceServers sequence<RTCIceServer> No Default []. Each entry has urls (one or more stun:/turn:/turns: URLs) plus username and credential, which turn: and turns: entries must carry. Without a TURN server, peers behind symmetric NATs cannot connect.
configuration.iceTransportPolicy RTCIceTransportPolicy No "all" (default) or "relay"; "relay" hides the user’s IP by forcing TURN candidates only.
configuration.bundlePolicy RTCBundlePolicy No "balanced" (default), "max-compat", or "max-bundle"; controls how many transports are negotiated for multiple media sections.
configuration.rtcpMuxPolicy RTCRtcpMuxPolicy No Only "require" remains in the spec; a remote description that does not mux RTCP is rejected.
configuration.certificates sequence<RTCCertificate> No Default []. Certificates from RTCPeerConnection.generateCertificate() for a stable DTLS fingerprint across sessions.
configuration.iceCandidatePoolSize octet No Default 0. Candidates to pre-gather before setLocalDescription().
options.ordered (data channel) boolean No Default true. false delivers messages as they arrive.
options.maxPacketLifeTime / options.maxRetransmits unsigned short No Mutually exclusive; either turns the channel partially reliable.
options.negotiated / options.id boolean / unsigned short No negotiated: true with an explicit id (0 to 65534) skips in-band channel announcement; both sides must create the channel.
constraints.audio / constraints.video boolean or MediaTrackConstraints No At least one must be present and truthy. Object form accepts width, height, frameRate, facingMode, deviceId, each as a bare value (preference) or { exact } / { ideal }.

The names below are those webrtc-pc and mediacapture-main define; RTCPeerConnection methods reject their promise except where noted as thrown (see RTCError (w3.org) for the SDP and transport failures that carry an errorDetail).

Exception Condition
InvalidStateError createOffer(), createAnswer(), both set-description methods, addIceCandidate(), addTrack() (thrown), and createDataChannel() (thrown) once signalingState is "closed"; addIceCandidate() while remoteDescription is null; createAnswer() when the state is not "have-remote-offer" or "have-local-pranswer".
InvalidModificationError setLocalDescription() is given an offer or answer whose sdp differs from the one createOffer()/createAnswer() last produced.
InvalidAccessError The constructor is given an expired or cross-origin certificate, or a turn: server without username and credential (thrown); a description fails RFC 9429 semantics validation, does not mux RTCP under rtcpMuxPolicy: "require", or renegotiates RIDs; addTrack() is called twice with the same track (thrown).
SyntaxError An iceServers URL does not parse, has a path, or has a query other than transport=udp or transport=tcp (thrown by the constructor and setConfiguration()).
NotSupportedError An iceServers URL uses a scheme the browser does not implement (thrown).
TypeError addIceCandidate() receives a non-empty candidate with both sdpMid and sdpMLineIndex null; createDataChannel() sets both maxPacketLifeTime and maxRetransmits, uses negotiated: true without an id, or uses id: 65535 (thrown).
OperationError addIceCandidate()’s sdpMid matches no media section, sdpMLineIndex is out of range, or usernameFragment matches no applied remote description; createDataChannel() finds the id in use or exhausted (thrown); applying a description fails for a reason not covered above; createOffer() cannot inspect the system’s codecs and resources.
RTCError A DOMException subclass whose errorDetail names the failure: "sdp-syntax-error" (with sdpLineNumber) from the set-description methods, "dtls-failure", "fingerprint-failure", "sctp-failure", or "data-channel-failure" from the transport, delivered through the error event on an RTCDataChannel.
NotAllowedError (getUserMedia) The user denied the permission, the page is not allowed by the camera/microphone Permissions Policy, or the call lacks the activation the browser requires.
NotFoundError (getUserMedia) No device of the requested kind exists.
NotReadableError (getUserMedia) The OS or another application holds the device.
OverconstrainedError (getUserMedia) An exact constraint is not satisfiable; error.constraint names it.
TypeError / SecurityError (getUserMedia) constraints has neither audio nor video set; the document is not fully active or the context is insecure.

The three examples form one call: capture, negotiate, and exchange data, each with the branch taken when support or permission is missing.

Capturing local media with a constraint fallback

Section titled “Capturing local media with a constraint fallback”

Feature-detect RTCPeerConnection and navigator.mediaDevices together; a page served over http:// has navigator.mediaDevices undefined. Ask for the ideal resolution first and retry with the bare video: true request when the camera cannot meet an exact constraint.

async function capture() {
if (!("RTCPeerConnection" in window) || !navigator.mediaDevices?.getUserMedia) {
return null; // show an upload form instead of a live call
}
try {
return await navigator.mediaDevices.getUserMedia({
audio: true,
video: { width: { ideal: 1280 }, height: { ideal: 720 }, facingMode: { exact: "user" } },
});
} catch (err) {
if (err.name === "OverconstrainedError") {
console.warn(`constraint ${err.constraint} not satisfiable; retrying without it`);
return navigator.mediaDevices.getUserMedia({ audio: true, video: true });
}
if (err.name === "NotAllowedError") return null; // permission denied: audio-free fallback
throw err;
}
}

facingMode: { exact: "user" } is the usual trigger on desktops, where webcams report no facing mode at all.

Driving offer, answer, and ICE from browser events

Section titled “Driving offer, answer, and ICE from browser events”

Let negotiationneeded create the offer so that tracks added later renegotiate automatically, forward each icecandidate over the signalling channel, and feed the remote side’s candidates to addIceCandidate() only after setRemoteDescription() has run.

function connect(stream, signaling, iceServers) {
const pc = new RTCPeerConnection({ iceServers });
const controller = new AbortController();
const { signal } = controller;
for (const track of stream.getTracks()) pc.addTrack(track, stream);
pc.addEventListener("negotiationneeded", async () => {
await pc.setLocalDescription(); // creates the offer for the current state
signaling.send({ description: pc.localDescription });
}, { signal });
pc.addEventListener("icecandidate", ({ candidate }) => {
if (candidate) signaling.send({ candidate });
}, { signal });
pc.addEventListener("track", ({ streams }) => {
document.querySelector("#remote").srcObject = streams[0];
}, { signal });
signaling.onmessage = async ({ description, candidate }) => {
if (description) {
await pc.setRemoteDescription(description);
if (description.type === "offer") {
await pc.setLocalDescription();
signaling.send({ description: pc.localDescription });
}
} else if (candidate && pc.remoteDescription) {
await pc.addIceCandidate(candidate);
}
};
return () => {
controller.abort();
for (const track of stream.getTracks()) track.stop();
pc.close();
};
}

The returned function is the hang-up: aborting the signal removes every listener in one call, stopping tracks releases the camera light, and close() ends ICE. The signal option on addEventListener() needs Chrome 90, Firefox 86, or Safari 15; below those, keep named handlers and call removeEventListener().

Sending messages over a data channel with a WebSocket fallback

Section titled “Sending messages over a data channel with a WebSocket fallback”

Data channels do not need any media. Create the channel before the first offer so it is announced in-band, and read readyState before each send(); a channel is usable only while it is "open".

function openChat(pc, signalingSocket, onMessage) {
if (!pc || typeof pc.createDataChannel !== "function") {
signalingSocket.addEventListener("message", (e) => onMessage(e.data));
return (text) => signalingSocket.send(text); // relayed through the server
}
const channel = pc.createDataChannel("chat", { ordered: false, maxRetransmits: 0 });
channel.binaryType = "arraybuffer";
channel.addEventListener("message", (e) => onMessage(e.data));
channel.addEventListener("error", (e) => console.error(e.error.errorDetail, e.error.message));
pc.addEventListener("datachannel", ({ channel: incoming }) => {
incoming.addEventListener("message", (e) => onMessage(e.data));
});
return (text) => {
if (channel.readyState === "open") channel.send(text);
else signalingSocket.send(text);
};
}

ordered: false with maxRetransmits: 0 gives UDP-like delivery for presence or cursor updates; omit both options for chat text that must arrive complete and in order.

Specifications

SpecificationStatus
WebRTC (RTCPeerConnection)W3C draft
WebRTC: RTCPeerConnection constructorW3C draft
WebRTC: setLocalDescription()W3C draft
WebRTC: addIceCandidate()W3C draft
WebRTC: createDataChannel()W3C draft
Media Capture and Streams: getUserMedia()W3C draft
  • Legend
  • Yes
  • Partial
  • Flag
  • No
  • Unknown
Browser / PlatformSupportVersionsConfidenceSourceNotes
Chrome (Desktop)Yes56highsource—
Chrome (Android)Yes56highsource1
Edge (Desktop)Yes15highsource—
Firefox (Desktop)Yes44highsource—
Firefox (Android)Yes44highsource—
Safari (macOS)Yes11highsource—
Safari (iOS)Yes11highsource2
Samsung InternetYes6.0highsource3
WebView (Android)Yes56highsource4
  1. Derived by browser-compat-data mirroring from Chrome.
  2. Derived by browser-compat-data mirroring from Safari.
  3. Derived by browser-compat-data mirroring from Chrome Android.
  4. Derived by browser-compat-data mirroring from Chrome Android.

Source data: /compatibility/web-rtc.json · Global usage: 93 % (StatCounter 2026-05)

Source: spec · MDN · Last verified 2026-10-03 · Confidence: high (computed from sources)