loading
Warning:
You need an HTTP/3 server This cannot talk to an ordinary HTTPS endpoint and there is no fallback to one - the server has to speak WebTransport over HTTP/3. That is also why the connect button below fails against anything on this site: the demo is served over HTTP/1.1 and HTTP/2. Point it at a WebTransport echo server (Chromium's webtransport over http3 sample server, or your own) to watch it work.

Check for support

IsSupported

Chromium and Firefox implement WebTransport, and Safari from 26.4 on - so it comes down to the browser and its version, and IsSupported() is the answer that is actually current.

Razor
@inject Bit.Butil.WebTransport webTransport

if (await webTransport.IsSupported())
{
    // safe to connect
}
Live sample
support output
Results will appear here when you interact with the samples.

Connect

Connect

Connect resolves once the session is usable, and hands back either the session or the reason there is not one. Incoming data arrives through the callbacks rather than being read from the handle: the browser's reader is a pull loop, so it runs on the JS side and dispatches what it reads.

C#
var result = await webTransport.Connect("https://example.com:4433/echo",
    onDatagram: data => Log($"datagram: {data.Length} bytes"),
    onStreamData: chunk => Log(chunk.Ended
        ? $"stream {chunk.StreamId} ended"
        : $"stream {chunk.StreamId}: {chunk.Data.Length} bytes"),
    onStreamOpened: stream => Log($"the server opened stream {stream.Id}"),
    onClosed: info => Log($"closed: {info.CloseCode} {info.Reason} {info.Error}"),
    congestionControl: WebTransportCongestionControl.LowLatency);

if (result.Session is null)
{
    Log(result.Error);
    return;
}

await using var session = result.Session;
Live sample
session output
Results will appear here when you interact with the samples.

Datagrams

SendDatagram

Unreliable and unordered, like a UDP packet: nothing is retransmitted, nothing is acknowledged, and one larger than the path MTU (about 1200 bytes) is dropped rather than fragmented. Use it for data that is worthless late - position updates, telemetry, media frames.

Razor
@code {
    private WebTransportHandle? session;   // from webTransport.Connect

    private async Task Ping()
    {
        // Unreliable and unordered by design: true means it was handed to the network, not that it
        // arrived. Anything that has to arrive belongs on a stream.
        var sent = await session!.SendDatagram(Encoding.UTF8.GetBytes("ping"));
    }
}
Live sample
Session not connected
datagram output
Results will appear here when you interact with the samples.

Streams

OpenStream / WebTransportStream.Write

A stream is ordered and reliable. Streams are independent of each other, so a stall on one does not hold up the rest - which is the reason to open several rather than multiplex your own framing over one. Disposing a stream closes its writable half, which is what tells the peer the message is complete.

Razor
@code {
    private WebTransportHandle? session;   // from webTransport.Connect

    private async Task Send()
    {
        await using var stream = await session!.OpenStream(bidirectional: true);

        if (stream is not null)
        {
            await stream.Write(Encoding.UTF8.GetBytes("hello"));
            // what comes back arrives through the session's onStreamData callback
        }
    }
}
Live sample
Session not connected
stream output
Results will appear here when you interact with the samples.
Note:
Reaching a development server A WebTransport server usually runs behind a certificate no public CA signed. Rather than turning validation off, pass the certificate's SHA-256 hash through certificateHashes - the browser then accepts that exact certificate, and only if it is short-lived (14 days in Chromium) and uses an ECDSA P-256 key.

API reference

Member
Signature
Description
IsSupported
ValueTask<bool> IsSupported()
True when the runtime exposes WebTransport.
Connect
ValueTask<WebTransportConnectResult> Connect(string url, Action<byte[]>? onDatagram = null, Action<WebTransportStreamData>? onStreamData = null, Action<WebTransportStream>? onStreamOpened = null, Action<WebTransportCloseInfo>? onClosed = null, bool allowPooling = false, WebTransportCongestionControl congestionControl = Default, WebTransportCertificateHash[]? certificateHashes = null)
Connects to an HTTP/3 endpoint and waits until the session is usable.
WebTransportHandle.GetState
ValueTask<WebTransportState> GetState()
Whether the session is still open.
WebTransportHandle.SendDatagram
ValueTask<bool> SendDatagram(byte[] data)
Sends one unreliable, unordered datagram.
WebTransportHandle.OpenStream
ValueTask<WebTransportStream?> OpenStream(bool bidirectional = false)
Opens a stream on the session. Null when the session is closed.
WebTransportHandle.Close
ValueTask Close(int closeCode = 0, string reason = "")
Closes the session, telling the peer why. Disposing closes it with no code or reason.
WebTransportStream.Write
ValueTask<bool> Write(byte[] data)
Writes bytes to the stream - ordered and reliable.
WebTransportStream.Id
string Id
The stream's id within its session, as it appears in WebTransportStreamData.StreamId.
WebTransportConnectResult
record WebTransportConnectResult(WebTransportHandle? Session, string Error)
The session, or the reason there is not one.
WebTransportStreamData
record WebTransportStreamData(string StreamId, byte[] Data, bool Ended)
A chunk read off a stream, or - with Ended - the notification that the stream is finished.
WebTransportCloseInfo
record WebTransportCloseInfo(int CloseCode, string Reason, string Error)
How the session ended. Error is set when it died rather than closed.
WebTransportCertificateHash
class WebTransportCertificateHash { string Algorithm; byte[] Value; }
A hash of the exact server certificate to accept - how to reach a development server.
WebTransportCongestionControl
enum WebTransportCongestionControl { Default, Throughput, LowLatency }
What to tune the connection for.
WebTransportState
enum WebTransportState { Open, Closed }
Whether a session is still usable.
An unhandled error has occurred. Reload 🗙