Configuration

View Source

Server Options

webtransport:start_listener(Name, Opts).
OptionRequiredDefaultDescription
transportyes--h2 (HTTP/2) or h3 (HTTP/3)
portyes--TCP/UDP port to listen on
certfileyes--Path to TLS certificate (PEM)
keyfileyes--Path to TLS private key (PEM)
handleryes--Module implementing webtransport_handler
handler_optsno#{}Map passed to handler:init/3
max_datano1048576 (1 MB)Session-level flow-control window (bytes)
max_streams_bidino100Max concurrent bidirectional streams
max_streams_unino100Max concurrent unidirectional streams
compat_modenoautoHTTP/3 draft selection (see below)

Client Options

webtransport:connect(Host, Port, Path, Opts).
OptionDefaultDescription
transporth3h2 or h3
verifyverify_peerverify_peer or verify_none
cacertfile--Path to CA certificate bundle
certfile--Client certificate (mutual TLS)
keyfile--Client private key (mutual TLS)
headers[]Extra headers on the CONNECT request
timeout30000Connection timeout (ms)
handler_opts#{}Map passed to handler's init/3
compat_modelatestHTTP/3 draft selection (see below)

Compatibility Mode

The HTTP/3 WebTransport spec has evolved through multiple drafts. As of April 2026, Safari and the IETF are on draft-15 while Chrome and Firefox still use draft-02. This library keeps the two paths separate:

Mode:protocolSETTINGSUse when
latestwebtransport-h3wt_enabled=1 + initial flow-controlDraft-15 peers (Safari, spec-conformant servers)
legacy_browser_compatwebtransportSETTINGS_ENABLE_WEBTRANSPORT_DRAFT02=1Draft-02 peers (Chrome, Firefox, quic-go v0.9)
auto (server only)accepts bothadvertises bothAccept either draft per request

Server detection

In auto mode, the server inspects each CONNECT request:

  • :protocol = webtransport-h3 with no draft-02 header -> latest
  • :protocol = webtransport -> legacy_browser_compat
  • Conflicting signals (e.g. webtransport-h3 plus the draft-02 header) -> 400

The decision is frozen at session init. Pin to latest or legacy_browser_compat to refuse the other:

%% Accept only draft-15 clients
webtransport:start_listener(strict, #{
    transport => h3,
    port => 4433,
    certfile => "cert.pem",
    keyfile => "key.pem",
    handler => my_handler,
    compat_mode => latest
}).

Client selection

Clients must choose explicitly. Default is latest:

{ok, Session} = webtransport:connect("example.com", 443, <<"/wt">>, #{
    transport => h3,
    compat_mode => legacy_browser_compat
}).

HTTP/2 has no draft-02 variant; compat_mode applies only to HTTP/3.

Flow Control

WebTransport provides session-level and per-stream flow control:

ParameterDefaultDescription
max_data1048576 (1 MB)Session-level byte limit
max_streams_bidi100Max concurrent bidirectional streams
max_streams_uni100Max concurrent unidirectional streams

Override at listener or connect time:

webtransport:start_listener(my_server, #{
    transport => h3,
    port => 4433,
    certfile => "cert.pem",
    keyfile => "key.pem",
    handler => my_handler,
    max_data => 4194304,        %% 4 MB
    max_streams_bidi => 200,
    max_streams_uni => 50
}).

Enforcement

The library enforces these rules from the spec:

  • Monotonicity -- a peer sending a decreased WT_MAX_DATA or WT_MAX_STREAMS closes the session with WT_FLOW_CONTROL_ERROR.
  • Peer stream count -- streams opened beyond the advertised limit are rejected with WT_BUFFERED_STREAM_REJECTED.
  • HTTP/3 prohibition -- WT_MAX_STREAM_DATA and WT_STREAM_DATA_BLOCKED capsules are session errors on HTTP/3 (per-stream flow control uses native QUIC).
  • HTTP/2 WebTransport-Init -- the WebTransport-Init structured-field header (draft-14 section 4.3.2) carries initial flow-control windows. When both SETTINGS and the header are present, the greater value is used.

Datagram Limits

Datagrams are bounded by the transport:

TransportMax payloadReason
HTTP/365527 bytesmax_datagram_frame_size (65535) minus session-id varint (up to 8 bytes)
HTTP/265471 bytesHTTP/2 initial stream window (65535) minus capsule framing overhead (64 bytes)

Sending a datagram larger than the limit returns {error, datagram_too_large}.

Error Codes

The library uses the error codes defined in the spec:

ConstantValueMeaning
WT_BUFFERED_STREAM_REJECTED0x3994bd84Peer exceeded buffered stream limit
WT_SESSION_GONE0x170d7b68Session terminated
WT_FLOW_CONTROL_ERROR0x045d4487Flow-control violation
WT_REQUIREMENTS_NOT_MET0x212c0d48Protocol requirements not satisfied

Application-level error codes are mapped to/from QUIC error codes per draft-15 section 3.3.

Session Termination

When a session closes (locally or by the peer):

  1. All live streams are reset with WT_SESSION_GONE.
  2. A CLOSE_SESSION capsule is sent (or received) with an error code and reason (max 1024 bytes).
  3. The CONNECT stream is half-closed (FIN sent).
  4. The handler's terminate/2 receives {closed, ErrorCode, Reason}.

If the peer FINs the CONNECT stream without sending CLOSE_SESSION, the session terminates with {closed, 0, <<"peer closed CONNECT">>}.