WebTransport Echo

WebTransport reached Baseline in March 2026, but the implementation is uneven. Engines disagree about what a server must advertise, which certificates count, and which half of the API exists. The following list shows the most common gotchas when implementing WebTransport.

Your Browser

These settings were pulled from your browser. Use the probe button to fill in session capabilities.

CapabilityHereNotes

Safari and iOS

WebTransport on Safari is the holy grail, because datagrams are most useful where connections are lossy. Here are the common solutions to getting WebTransport working on iOS devices like iPads and iPhones.

What a server must advertise

RequirementIf absent
WT_MAX_SESSIONS ready never resolves and never rejects. Keep a client-side timeout or the page hangs forever.
WT_INITIAL_MAX_DATA, WT_INITIAL_MAX_STREAMS_UNI, WT_INITIAL_MAX_STREAMS_BIDI, whenever WT_MAX_SESSIONS exceeds 1 Session refused before CONNECT on Safari. The draft does not require these, but advertising WT_MAX_SESSIONS above 1 switches flow control on, and all three default to 0 — so the session opens with no credit at all. Other engines tolerate it; Safari does not.

Matching every advertised value is not a guarantee of matching behavior. Two endpoints here send the same h3 SETTINGS and QUIC transport parameters from different generations of one Go library, and Safari accepts one while refusing the other. Details on the quic-go exhibit.

Breaking Behaviors in Safari

BehaviorConsequence
WebTransportError carries an empty message Invalid settings, TLS rejection and wire-level refusal are indistinguishable from the client, all arriving about 50 ms after construction. Put your diagnostics server-side or you will not have any.
Flow-control credit is never released on FIN or RESET (319818) The connection deadlocks at 16 MB or 7,600 streams, whichever comes first. Long-lived sessions moving real volume will hit this.
User-installed root CAs are ignored for WebTransport TLS rejects with certificate_unknown even at full trust, so a local CA that works for page loads fails here. Develop against hash pinning or real WebPKI, nothing between. Measured on iOS 27.0 beta rather than read from a source, and notably WebKit's own trust path calls SecTrustEvaluateAsyncWithError, which would honor a user root — placing the rejection below WebKit, inside Network.framework.
The wire implementation lives in Network.framework, not WebKit An OS point release can change SETTINGS handling with nothing appearing in Safari release notes. Re-test on every iOS and macOS update, not every Safari version.
The floor is iOS and macOS 26.4, not Safari 26.4 The operating system version decides this, not the browser version. An older OS running a newer Safari has no WebTransport at all. Support in WKWebView and in-app browsers is unconfirmed either way.

Using Certificates

serverCertificateHashes does not extend certificate chain validation as you would expect, but instead replaces it.

Here are the rules that can cause certificates to be silently rejected.

RuleDetail
Hash the leaf DERSHA-256 over the leaf certificate only. Hashing the SPKI instead is the other convention in circulation, and it fails with no diagnostic.
Total validity ≤ 14 daysNot remaining validity, but the whole notBefore to notAfter span. A 90-day certificate cannot be pinned on any engine.
ECDSA P-256The one curve every engine must accept, so the safe choice. Chrome also takes P-384 and Ed25519. RSA is excluded by the specification and rejected outright by Chrome and Firefox.
Algorithm string is lowercase "sha-256"The specification calls for a case-insensitive match, but WebKit and Firefox both compare literally, so "SHA-256" is spec-legal and silently never matches.
Incompatible with allowPoolingThe specification requires a NotSupportedError at construction when both are set. Safari throws it, being the only engine that implements allowPooling at all.
Certificate names are irrelevantNothing checks SANs on this path. A localhost-SAN certificate serves a public origin fine.
Chrome needs a known root, not merely a trusted oneIt reads the system trust store and then requires the root be a public one, so mkcert and every private CA fail with ERR_QUIC_CERT_ROOT_NOT_KNOWN. Pin instead, or run Chrome with --webtransport-developer-mode.

These endpoints show how small variations in certificates can cause browsers to behave differently:

Current State of Chrome and Firefox

EngineGap
ChromeallowPooling and requireUnreliable do not exist, and headers is behind an experimental flag. WebIDL discards dictionary members it does not know, so passing any of them does nothing and reports nothing.
ChromegetStats() and congestionControl are not enabled in stable. maxDatagramSize is hardcoded to 1024 and never updated from the negotiated path MTU.
FirefoxgetStats() throws. protocol unimplemented. serverCertificateHashes works from 125 onward; earlier versions layered it on top of PKI instead of replacing it.
SafariToday the only engine shipping createSendGroup and draining by default, though Firefox 155 adds both. anticipatedConcurrentIncoming* stays Safari-only.

Additional Gotchas

The HTTP/2 fallback is Safari-only, and silentChrome and Firefox do not implement the binding, so a client on a UDP-blocked path simply fails. Safari does fall back to HTTP/2 over TCP behind the same API, which means streams keep working while datagrams vanish. Read reliability after connecting, or pass requireUnreliable: true to refuse the downgrade.
UDP does not always get throughRFC 9308 cites measurements of 3 to 5 percent of networks blocking UDP entirely, and Google measured 4.4 percent of its video clients unable to use QUIC in 2016, concentrated in enterprise networks. Both numbers are old and neither has a modern replacement. Ship a second transport at the application layer.
The datagram write API movedThe specification replaced datagrams.writable with createWritable() in 2025. Safari ships the current shape, Chrome still ships the old attribute, and Firefox carries both. readable is an accessor everywhere: createReadable() does not exist. Feature-detect the writer, not the reader.
No request headers on the CONNECTThe headers option is unimplemented in Safari, absent from Firefox, and behind an experimental flag in Chrome. Carry what you need in the URL or the first message.
The draft is still movingChrome and Safari both still send the legacy :protocol = "webtransport" rather than draft-15's webtransport-h3. A server enforcing the newer token strictly rejects every current browser.
Baseline "newly available", not "widely available"Interoperable since 2026-03-24. The 30-month bar lands around September 2028.

Additional information about bugs in WebKit and quic-go and webtransport-go.