![Jeff Posnick](https://web.dev/images/authors/jeffposnick.jpg) Jeff Posnick [X](https://twitter.com/jeffposnick) [GitHub](https://github.com/jeffposnick) [Homepage](https://jeffy.info/)

<br />


Published: June 8, 2020

<br />

[WebTransport](https://w3c.github.io/webtransport/) is a web API that uses the
[HTTP/3](https://www.rfc-editor.org/rfc/rfc9114) protocol as a bidirectional
transport. It's intended for two-way communications between a web client and an
HTTP/3 server. It supports sending data both unreliably with its
[datagram APIs](https://developer.chrome.com/docs/capabilities/web-apis/webtransport#datagram), and reliably with its [streams APIs](https://developer.chrome.com/docs/capabilities/web-apis/webtransport#stream).

[Datagrams](https://tools.ietf.org/html/draft-ietf-quic-datagram-00) are ideal
for sending and receiving data, that don't need strong delivery guarantees.
Individual packets of data are limited in size by the [maximum transmission unit (MTU)](https://wikipedia.org/wiki/Maximum_transmission_unit) of the underlying connection, and may or may not be transmitted successfully, and if they are transferred, they may arrive in an arbitrary order. These characteristics make the datagram APIs ideal for low-latency, best-effort data transmission. You can think of datagrams as [user datagram protocol (UDP)](https://wikipedia.org/wiki/User_Datagram_Protocol) messages, but encrypted and congestion-controlled.

The streams APIs, in contrast, provide [reliable](https://wikipedia.org/wiki/Reliability_(computer_networking)), ordered data transfer. They're [well-suited](https://quicwg.org/base-drafts/draft-ietf-quic-transport.html#name-streams) to scenarios where you need to send or receive one or more streams of ordered data. Using multiple WebTransport streams is analogous to establishing multiple [TCP](https://wikipedia.org/wiki/Transmission_Control_Protocol) connections, but since HTTP/3 uses the lighter-weight [QUIC](https://www.chromium.org/quic) protocol under the hood, they can be opened and closed without as much overhead.

### Use cases

This a small list of possible ways developers might use WebTransport.

- Sending game state at a regular interval with minimal latency to a server in a small, unreliable, out-of-order messages.
- Receiving media streams pushed from a server with minimal latency, independent of other data streams.
- Receiving notifications pushed from a server while a web page is open.

We're interested in [hearing more](https://developer.chrome.com/docs/capabilities/web-apis/webtransport#feedback) about how you plan to use WebTransport.

> [!NOTE]
> **Note:** Many of the concepts in this proposal were previously experimented with as part of the earlier QuicTransport origin trial, which did not end up being released as part of Chrome. WebTransport helps with similar use cases as QuicTransport, with the primary difference being that [HTTP/3](https://www.rfc-editor.org/rfc/rfc9114) instead of [QUIC](https://www.chromium.org/quic) is the underlying transport protocol.

## Browser support


Browser Support

- ![Chrome: 97.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='-10 -10 276 276'%3E%3ClinearGradient id='a' x1='145' x2='34' y1='253' y2='61' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%231e8e3e'/%3E%3Cstop offset='1' stop-color='%2334a853'/%3E%3C/linearGradient%3E%3ClinearGradient id='b' x1='111' x2='222' y1='254' y2='62' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fcc934'/%3E%3Cstop offset='1' stop-color='%23fbbc04'/%3E%3C/linearGradient%3E%3ClinearGradient id='c' x1='17' x2='239' y1='80' y2='80' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23d93025'/%3E%3Cstop offset='1' stop-color='%23ea4335'/%3E%3C/linearGradient%3E%3Ccircle cx='128' cy='128' r='64' fill='%23fff'/%3E%3Cpath fill='url(%23a)' d='M96 183a64 64 0 0 1-23-23L17 64a128 128 0 0 0 111 192l55-96a64 64 0 0 1-87 23Z'/%3E%3Cpath fill='url(%23b)' d='M192 128a64 64 0 0 1-9 32l-55 96A128 128 0 0 0 239 64H128a64 64 0 0 1 64 64Z'/%3E%3Ccircle cx='128' cy='128' r='52' fill='%231a73e8'/%3E%3Cpath fill='url(%23c)' d='M96 73a64 64 0 0 1 32-9h111a128 128 0 0 0-222 0l56 96a64 64 0 0 1 23-87Z'/%3E%3C/svg%3E)
- ![Edge: 97.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='0 0 27600 27600'%3E%3ClinearGradient id='A' gradientUnits='userSpaceOnUse'/%3E%3ClinearGradient id='B' x1='6870' x2='24704' y1='18705' y2='18705' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%230c59a4'/%3E%3Cstop offset='1' stop-color='%23114a8b'/%3E%3C/linearGradient%3E%3ClinearGradient id='C' x1='16272' x2='5133' y1='10968' y2='23102' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%231b9de2'/%3E%3Cstop offset='.16' stop-color='%231595df'/%3E%3Cstop offset='.67' stop-color='%230680d7'/%3E%3Cstop offset='1' stop-color='%230078d4'/%3E%3C/linearGradient%3E%3CradialGradient id='D' cx='16720' cy='18747' r='9538' xlink:href='%23A'%3E%3Cstop offset='.72' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.53'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='E' cx='7130' cy='19866' r='14324' gradientTransform='matrix(.14843 -.98892 .79688 .1196 -8759 25542)' xlink:href='%23A'%3E%3Cstop offset='.76' stop-opacity='0'/%3E%3Cstop offset='.95' stop-opacity='.5'/%3E%3Cstop offset='1'/%3E%3C/radialGradient%3E%3CradialGradient id='F' cx='2523' cy='4680' r='20243' gradientTransform='matrix(-.03715 .99931 -2.12836 -.07913 13579 3530)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2335c1f1'/%3E%3Cstop offset='.11' stop-color='%2334c1ed'/%3E%3Cstop offset='.23' stop-color='%232fc2df'/%3E%3Cstop offset='.31' stop-color='%232bc3d2'/%3E%3Cstop offset='.67' stop-color='%2336c752'/%3E%3C/radialGradient%3E%3CradialGradient id='G' cx='24247' cy='7758' r='9734' gradientTransform='matrix(.28109 .95968 -.78353 .22949 24510 -16292)' xlink:href='%23A'%3E%3Cstop offset='0' stop-color='%2366eb6e'/%3E%3Cstop offset='1' stop-color='%2366eb6e' stop-opacity='0'/%3E%3C/radialGradient%3E%3Cpath id='H' d='M24105 20053a9345 9345 0 01-1053 472 10202 10202 0 01-3590 646c-4732 0-8855-3255-8855-7432 0-1175 680-2193 1643-2729-4280 180-5380 4640-5380 7253 0 7387 6810 8137 8276 8137 791 0 1984-230 2704-456l130-44a12834 12834 0 006660-5282c220-350-168-757-535-565z'/%3E%3Cpath id='I' d='M11571 25141a7913 7913 0 01-2273-2137 8145 8145 0 01-1514-4740 8093 8093 0 013093-6395 8082 8082 0 011373-859c312-148 846-414 1554-404a3236 3236 0 012569 1297 3184 3184 0 01636 1866c0-21 2446-7960-8005-7960-4390 0-8004 4166-8004 7820 0 2319 538 4170 1212 5604a12833 12833 0 007684 6757 12795 12795 0 003908 610c1414 0 2774-233 4045-656a7575 7575 0 01-6278-803z'/%3E%3Cpath id='J' d='M16231 15886c-80 105-330 250-330 566 0 260 170 512 472 723 1438 1003 4149 868 4156 868a5954 5954 0 003027-839 6147 6147 0 001133-850 6180 6180 0 001910-4437c26-2242-796-3732-1133-4392-2120-4141-6694-6525-11668-6525-7011 0-12703 5635-12798 12620 47-3654 3679-6605 7996-6605 350 0 2346 34 4200 1007 1634 858 2490 1894 3086 2921 618 1067 728 2415 728 2952s-271 1333-780 1990z'/%3E%3Cuse fill='url(%23B)' xlink:href='%23H'/%3E%3Cuse fill='url(%23D)' opacity='.35' xlink:href='%23H'/%3E%3Cuse fill='url(%23C)' xlink:href='%23I'/%3E%3Cuse fill='url(%23E)' opacity='.4' xlink:href='%23I'/%3E%3Cuse fill='url(%23F)' xlink:href='%23J'/%3E%3Cuse fill='url(%23G)' xlink:href='%23J'/%3E%3C/svg%3E)
- ![Firefox: 114.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='24' viewBox='0 0 512 512'%3E%3Cdefs%3E%3CradialGradient id='ff-b' cx='428.5' cy='55.1' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23ffbd4f'/%3E%3Cstop offset='.2' stop-color='%23ffac31'/%3E%3Cstop offset='.3' stop-color='%23ff9d17'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff563b'/%3E%3Cstop offset='.5' stop-color='%23ff3750'/%3E%3Cstop offset='.7' stop-color='%23f5156c'/%3E%3Cstop offset='.8' stop-color='%23eb0878'/%3E%3Cstop offset='.9' stop-color='%23e50080'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-c' cx='245.4' cy='259.9' r='501' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%23960e18'/%3E%3Cstop offset='.3' stop-color='%23b11927' stop-opacity='.7'/%3E%3Cstop offset='.4' stop-color='%23db293d' stop-opacity='.3'/%3E%3Cstop offset='.5' stop-color='%23f5334b' stop-opacity='.1'/%3E%3Cstop offset='.5' stop-color='%23ff3750' stop-opacity='0'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-d' cx='305.8' cy='-58.6' r='363' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.3' stop-color='%23ffdc3e'/%3E%3Cstop offset='.5' stop-color='%23ff9d12'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-e' cx='190' cy='390.8' r='238.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.3' stop-color='%233a8ee6'/%3E%3Cstop offset='.5' stop-color='%235c79f0'/%3E%3Cstop offset='.7' stop-color='%239059ff'/%3E%3Cstop offset='1' stop-color='%23c139e6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-f' cx='252.2' cy='201.3' r='126.5' gradientTransform='matrix(1 0 0 1 -48 31)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%239059ff' stop-opacity='0'/%3E%3Cstop offset='.3' stop-color='%238c4ff3' stop-opacity='.1'/%3E%3Cstop offset='.8' stop-color='%237716a8' stop-opacity='.5'/%3E%3Cstop offset='1' stop-color='%236e008b' stop-opacity='.6'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-g' cx='239.1' cy='34.6' r='171.6' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23ffe226'/%3E%3Cstop offset='.1' stop-color='%23ffdb27'/%3E%3Cstop offset='.3' stop-color='%23ffc82a'/%3E%3Cstop offset='.5' stop-color='%23ffa930'/%3E%3Cstop offset='.7' stop-color='%23ff7e37'/%3E%3Cstop offset='.8' stop-color='%23ff7139'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-h' cx='374' cy='-74.3' r='732.2' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-i' cx='304.6' cy='7.1' r='536.4' gradientTransform='rotate(84 303 4)' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='0' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.3' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.6' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-j' cx='235' cy='98.1' r='457.1' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.5' stop-color='%23ff980e'/%3E%3Cstop offset='.6' stop-color='%23ff5634'/%3E%3Cstop offset='.7' stop-color='%23ff3647'/%3E%3Cstop offset='.9' stop-color='%23e31587'/%3E%3C/radialGradient%3E%3CradialGradient id='ff-k' cx='355.7' cy='124.9' r='500.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.2' stop-color='%23ffe141'/%3E%3Cstop offset='.5' stop-color='%23ffaf1e'/%3E%3Cstop offset='.6' stop-color='%23ff980e'/%3E%3C/radialGradient%3E%3ClinearGradient id='ff-a' x1='446.9' y1='76.8' x2='47.9' y2='461.8' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.1' stop-color='%23fff44f'/%3E%3Cstop offset='.1' stop-color='%23ffe847'/%3E%3Cstop offset='.2' stop-color='%23ffc830'/%3E%3Cstop offset='.4' stop-color='%23ff980e'/%3E%3Cstop offset='.4' stop-color='%23ff8b16'/%3E%3Cstop offset='.5' stop-color='%23ff672a'/%3E%3Cstop offset='.5' stop-color='%23ff3647'/%3E%3Cstop offset='.7' stop-color='%23e31587'/%3E%3C/linearGradient%3E%3ClinearGradient id='ff-l' x1='442.1' y1='74.8' x2='102.6' y2='414.3' gradientUnits='userSpaceOnUse'%3E%3Cstop offset='.2' stop-color='%23fff44f' stop-opacity='.8'/%3E%3Cstop offset='.3' stop-color='%23fff44f' stop-opacity='.6'/%3E%3Cstop offset='.5' stop-color='%23fff44f' stop-opacity='.2'/%3E%3Cstop offset='.6' stop-color='%23fff44f' stop-opacity='0'/%3E%3C/linearGradient%3E%3C/defs%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134a120 120 0 0 0-66 25 71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a229 229 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zM202 355l3 1-3-1zm55-145zm198-31z' fill='url(%23ff-a)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-b)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60 14 26 22 53 25 72v1a207 207 0 0 1-206 279c-113-3-212-87-231-197-3-17 0-26 2-40-2 11-3 14-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121z' fill='url(%23ff-c)'/%3E%3Cpath d='m362 195 1 1a130 130 0 0 0-22-29C266 92 322 5 331 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62z' fill='url(%23ff-d)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-e)'/%3E%3Cpath d='M257 210c-1 6-22 26-29 26-68 0-80 41-80 41 3 35 28 64 57 79l4 2 7 3a107 107 0 0 0 31 6c120 6 143-143 57-186 22-4 45 5 58 14-21-37-60-62-105-62l-9 1a120 120 0 0 0-66 25l17 16c16 16 58 33 58 35z' fill='url(%23ff-f)'/%3E%3Cpath d='m171 151 5 3a111 111 0 0 1-1-58c-25 11-44 29-58 44 1 0 36 0 54 11z' fill='url(%23ff-g)'/%3E%3Cpath d='M18 261a242 242 0 0 0 231 197 207 207 0 0 0 206-279c8 56-20 110-64 146-86 71-169 43-186 31l-3-1c-50-24-71-70-67-110-42 0-57-35-57-35s38-28 89-4c46 22 90 4 90 4 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5l-5-3c-18-11-52-11-54-11-9-12-9-51-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73c0 1-9 38-5 57z' fill='url(%23ff-h)'/%3E%3Cpath d='M341 167a130 130 0 0 1 22 29 46 46 0 0 1 4 3c55 50 26 121 24 126 44-36 72-90 64-146-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-9 5-65 92 10 167z' fill='url(%23ff-i)'/%3E%3Cpath d='M367 199a46 46 0 0 0-4-3l-1-1c-13-9-36-18-58-15 86 44 63 193-57 187a107 107 0 0 1-31-6 131 131 0 0 1-11-5c17 12 99 39 186-31 2-5 31-76-24-126z' fill='url(%23ff-j)'/%3E%3Cpath d='M148 277s12-41 80-41c7 0 28-20 29-26s-44 18-90-4c-51-24-89 4-89 4s15 35 57 35c-4 40 16 85 67 110l3 1c-29-15-54-44-57-79z' fill='url(%23ff-k)'/%3E%3Cpath d='M479 166c-11-25-32-52-49-60a249 249 0 0 1 25 73c-27-68-73-95-111-155a255 255 0 0 1-8-14 44 44 0 0 1-4-9 1 1 0 0 0 0-1 1 1 0 0 0-1 0c-60 35-81 101-83 134l9-1c45 0 84 25 105 62-13-9-36-18-58-14 86 43 63 192-57 186a107 107 0 0 1-31-6 131 131 0 0 1-11-5l-3-1 3 1c-29-15-54-44-57-79 0 0 12-41 80-41 7 0 28-20 29-26 0-2-42-19-58-35l-17-16a71 71 0 0 0-6-5 111 111 0 0 1-1-58c-25 11-44 29-58 44-9-12-9-52-8-60l-8 4a175 175 0 0 0-24 21 210 210 0 0 0-22 26 203 203 0 0 0-32 73l-1 2-2 15a279 279 0 0 0-4 34v1a240 240 0 0 0 477 40l1-9c5-41 0-84-15-121zm-24 13z' fill='url(%23ff-l)'/%3E%3C/svg%3E)
- ![Safari: 26.4.](data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='24' height='24' viewBox='195 190 135 135'%3E%3Cdefs%3E%3ClinearGradient id='s-a' x1='132.6' x2='134.4' y1='111.7' y2='-105.3' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23d2d2d2' /%3E%3Cstop offset='.5' stop-color='%23f2f2f2' /%3E%3Cstop offset='1' stop-color='%23fff' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-b' gradientUnits='userSpaceOnUse' /%3E%3ClinearGradient id='s-c' x1='65.4' x2='67.4' y1='115.7' y2='17.1' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23005ad5' /%3E%3Cstop offset='.2' stop-color='%230875f0' /%3E%3Cstop offset='.3' stop-color='%23218cee' /%3E%3Cstop offset='.6' stop-color='%2327a5f3' /%3E%3Cstop offset='.8' stop-color='%2325aaf2' /%3E%3Cstop offset='1' stop-color='%2321aaef' /%3E%3C/linearGradient%3E%3ClinearGradient id='s-d' x1='158.7' x2='176.3' y1='96.7' y2='79.5' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%23c72e24' /%3E%3Cstop offset='1' stop-color='%23fd3b2f' /%3E%3C/linearGradient%3E%3CradialGradient id='s-i' cx='-69.9' cy='69.3' r='54' gradientTransform='matrix(.9 -.01 .04 2.72 -9 -120)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-color='%2324a5f3' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%231e8ceb' /%3E%3C/radialGradient%3E%3CradialGradient id='s-j' cx='109.3' cy='13.8' r='93.1' gradientTransform='matrix(-.02 1.1 -1.04 -.02 137 -115)' xlink:href='%23s-b'%3E%3Cstop offset='0' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235488d6' stop-opacity='0' /%3E%3Cstop offset='1' stop-color='%235d96eb' /%3E%3C/radialGradient%3E%3C/defs%3E%3Crect width='220' height='220' x='22' y='-107' fill='url(%23s-a)' ry='49' transform='matrix(.57 0 0 .57 187 256)' /%3E%3Cg transform='translate(194 190)'%3E%3Ccircle cx='67.8' cy='67.7' fill='url(%23s-c)' paint-order='stroke fill markers' r='54' /%3E%3Ccircle cx='-69.9' cy='69.3' fill='url(%23s-i)' transform='translate(138 -2)' r='54' /%3E%3C/g%3E%3Cellipse cx='120' cy='14.2' fill='url(%23s-j)' rx='93.1' ry='93.7' transform='matrix(.58 0 0 .58 192 250)' /%3E%3Cg transform='matrix(.58 0 0 .57 197 182)'%3E%3Cpath fill='%23cac7c8' d='M46 192h1l72-48-7-9-66 57Z' /%3E%3Cpath fill='%23fbfffc' d='M46 191v1l66-57-7-9-59 65Z' /%3E%3Cpath fill='url(%23s-d)' d='m119 144-7-9 66-57-59 66Z' /%3E%3Cpath fill='%23fb645c' d='m105 126 7 9 66-57-1-1-72 49Z' /%3E%3C/g%3E%3Cpath stroke='%23fff' stroke-linecap='round' stroke-miterlimit='1' stroke-width='1.3' d='m287 278 3-2m-12-17 8-2m-8-3h4m-4-13 8 2m-8 3h4m-1-13 7 3m-4-11 7 4m-2-11 6 6m0-12 6 7m1-11 4 6m4-10 3 7m5-9 2 7m15-7-1 7m10-5-3 7m11-4-4 7m11-2-5 6m16 7-7 4m10 4-7 3m10 6-8 1m8 16-8-2m5 10-7-3m4 11-7-4m2 11-6-5m0 11-5-6m-2 11-4-7m-4 11-3-8m-6 10-1-8m-16 8 2-8m-10 5 3-7m-11 4 4-7m-11 2 5-6m-8 3 3-3m4 8 2-3m5 8 2-4m6 7 1-4m8 5v-4m8 4v-4m9 3-1-4m9 1-2-4m9 0-2-4m9-2-3-3m8-4-3-2m8-5-4-2m7-6-4-1m5-8h-4m4-8h-4m3-9-4 1m1-9-4 2m-1-9-3 2m-2-9-3 3m-4-8-2 3m-5-8-2 4m-6-6-1 3m-8-5v4m-8-4v4m-9-2 1 3m-9 0 2 3m-9 1 2 3m-9 2 3 3m-8 4 3 2m-8 5 4 2m-7 6 4 1m-4 25 4-1m-2 5 7-3m-6 7 4-2m-2 6 7-4m-13-21h8m41-41v-8m0 99v-8m49-42h-8' transform='translate(-65 8)' /%3E%3C/svg%3E)

[Source](https://developer.mozilla.org/docs/Web/API/WebTransport)

<br />

As with all features that lacking universal browser support, we recommend adding
[feature detection](https://developer.mozilla.org/docs/Learn/Tools_and_testing/Cross_browser_testing/Feature_detection).

## Relationship to other technologies

### Is WebTransport a replacement for WebSockets?

Maybe. There are use cases where either [WebSockets](https://developer.mozilla.org/docs/Web/API/WebSockets_API) or WebTransport might be valid communication protocols to use.

WebSockets communications are modeled around a single, reliable, ordered stream
of messages, which is fine for some types of communication needs. If you need
those characteristics, then WebTransport's streams APIs can provide them, too.
In comparison, WebTransport's datagram APIs provide low-latency delivery,
without guarantees about reliability or ordering, so they're not a direct
replacement for WebSockets.

When you use WebTransport, with the datagram APIs or multiple concurrent Streams
API instances, you don't have to worry about [head-of-line blocking](https://wikipedia.org/wiki/Head-of-line_blocking), which can be an issue with WebSockets. Additionally, there are performance benefits when establishing new connections, as the underlying [QUIC handshake](https://www.fastly.com/blog/quic-handshake-tls-compression-certificates-extension-study) is faster than starting up TCP over TLS.

WebTransport is part of a new draft specification, and as such the WebSocket ecosystem around client and server libraries is much more robust. If you need something that works "out of the box" with common server setups, and with broad web client support, WebSockets is a better choice today.

### Is WebTransport the same as a UDP Socket API?

No. WebTransport is not a [UDP Socket API](https://www.w3.org/TR/raw-sockets/). While WebTransport uses HTTP/3, which in turn uses UDP "under the hood," WebTransport has requirements around encryption and congestion control that make it more than a basic UDP Socket API.

### Is WebTransport an alternative to WebRTC data channels?

Yes, for client-server connections. WebTransport shares many of the same properties as [WebRTC data channels](https://developer.mozilla.org/docs/Web/API/RTCDataChannel), although the underlying protocols are different.

> [!NOTE]
> **Note:** WebRTC data channels support peer-to-peer communications, but WebTransport only supports client-server connection. If you have multiple clients that need to talk directly to each other, then WebTransport isn't a viable alternative.

Generally, running a HTTP/3-compatible server requires less setup and configuration than maintaining a WebRTC server, which involves understanding multiple protocols ([ICE](https://developer.mozilla.org/docs/Web/API/WebRTC_API/Connectivity#ICE_candidates), [DTLS](https://webrtc-security.github.io/#4.3.1.), and [SCTP](https://developer.mozilla.org/docs/Web/API/RTCSctpTransport)) in order to get a working transport. WebRTC entails many more moving pieces that could lead to failed client/server negotiations.

The WebTransport API was designed with the web developer use cases in mind, and should feel more like writing modern web platform code than using WebRTC's data channel interfaces. [Unlike WebRTC](https://bugs.chromium.org/p/chromium/issues/detail?id=302019), WebTransport is supported inside of [Web Workers](https://developer.mozilla.org/docs/Web/API/Web_Workers_API/Using_web_workers), which allows you to perform client-server communications independent of a given HTML page. Because WebTransport exposes a [Streams](https://streams.spec.whatwg.org/)-compliant interface, it supports optimizations around [backpressure](https://streams.spec.whatwg.org/#backpressure).

However, if you already have a working WebRTC client/server setup that you're happy with, switching to WebTransport may not offer many advantages.

## Experiment

The best way to experiment with WebTransport is to start up a compatible HTTP/3 server.
Use this page with a [basic JavaScript client](https://googlechrome.github.io/samples/webtransport/client.html) to try out client and server communications.

Additionally, a community-maintained echo server is available at
[webtransport.day](https://webtransport.day/).

## Use the API

WebTransport was designed on top of modern web platform primitives, like the [Streams API](https://developer.mozilla.org/docs/Web/API/Streams_API). It relies heavily on [promises](https://developer.mozilla.org/docs/Web/JavaScript/Guide/Using_promises), and works well with [`async` and `await`](https://developer.mozilla.org/docs/Learn/JavaScript/Asynchronous/Async_await).

The current WebTransport implementation in Chromium supports three distinct
types of traffic: datagrams, as well as both unidirectional and bidirectional
streams.

### Connect to a server

You can connect to a HTTP/3 server by creating a `WebTransport` instance. The scheme of the URL should be `https`. You need to explicitly specify the port number.

You should use the `ready` promise to wait for the connection to be established.
This promise remains unfulfilled until the setup is complete, and rejects if
the connection fails at the QUIC/TLS stage.

The `closed` promise fulfills when the connection closes normally, and rejects if the closure was unexpected.

If the server rejects the connection due to a
[client indication](https://tools.ietf.org/html/draft-vvv-webtransport-quic-01#section-3.2)
error (such as the path of the URL is invalid), then that causes `closed` to
reject, while `ready` remains unresolved.

    const url = 'https://example.com:4999/foo/bar';
    const transport = new WebTransport(url);

    // Optionally, set up functions to respond to
    // the connection closing:
    transport.closed.then(() => {
      console.log(`The HTTP/3 connection to ${url} closed gracefully.`);
    }).catch((error) => {
      console.error(`The HTTP/3 connection to ${url} closed due to ${error}.`);
    });

    // Once .ready fulfills, the connection can be used.
    await transport.ready;

### Datagram APIs

Once you have a WebTransport instance that's connected to a server, you can use it to send and receive discrete bits of data, known as [datagrams](https://wikipedia.org/wiki/Datagram).

The `writeable` getter returns a [`WritableStream`](https://developer.mozilla.org/docs/Web/API/WritableStream), which a web client can use to send data to the server. The `readable` getter returns a [`ReadableStream`](https://developer.mozilla.org/docs/Web/API/ReadableStream), allowing you to listen for data from the server. Both streams are inherently unreliable, so it's possible that the data you write won't be received by the server, and vice versa.

Both types of streams use [`Uint8Array`](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) instances for data transfer.

    // Send two datagrams to the server.
    const writer = transport.datagrams.writable.getWriter();
    const data1 = new Uint8Array([65, 66, 67]);
    const data2 = new Uint8Array([68, 69, 70]);
    writer.write(data1);
    writer.write(data2);

    // Read datagrams from the server.
    const reader = transport.datagrams.readable.getReader();
    while (true) {
      const {value, done} = await reader.read();
      if (done) {
        break;
      }
      // value is a Uint8Array.
      console.log(value);
    }

> [!NOTE]
> **Note:** Chrome doesn't [currently](https://bugs.chromium.org/p/chromium/issues/detail?id=929585) expose an [async iterator](https://github.com/whatwg/streams/issues/778) for a `ReadableStream`. For the time being, using the `getReader()` method combined with a `while()` loop is the best way to read from the stream.

### Streams APIs

Once you've connected to the server, you could also use WebTransport to send and receive data via its Streams APIs.

Each chunk of all streams is a `Uint8Array`. Unlike with the Datagram APIs, these streams are reliable. But each stream is independent, so data order across streams is not guaranteed.

#### WebTransportSendStream

A [`WebTransportSendStream`](https://w3c.github.io/webtransport/#send-stream) is created by the web client using the `createUnidirectionalStream()` method of a `WebTransport` instance, which returns a promise for the `WebTransportSendStream`.

Use the [`close()`](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/close) method of the [`WritableStreamDefaultWriter`](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) to close the associated HTTP/3 stream. The browser tries to send all pending data before actually closing the associated stream.

    // Send two Uint8Arrays to the server.
    const stream = await transport.createUnidirectionalStream();
    const writer = stream.writable.getWriter();
    const data1 = new Uint8Array([65, 66, 67]);
    const data2 = new Uint8Array([68, 69, 70]);
    writer.write(data1);
    writer.write(data2);
    try {
      await writer.close();
      console.log('All data has been sent.');
    } catch (error) {
      console.error(`An error occurred: ${error}`);
    }

Similarly, use the [`abort()`](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter/abort) method of the [`WritableStreamDefaultWriter`](https://developer.mozilla.org/docs/Web/API/WritableStreamDefaultWriter) to send a [`RESET_STREAM`](https://w3c.github.io/webtransport/#stream-reset) to the server. When using `abort()`, the browser may discard any pending data that hasn't yet been sent.

    const ws = await transport.createUnidirectionalStream();
    const writer = ws.getWriter();
    writer.write(...);
    writer.write(...);
    await writer.abort();
    // Not all the data may have been written.

#### WebTransportReceiveStream

The server initiates [`WebTransportReceiveStream`](https://w3c.github.io/webtransport/#receive-stream).
Obtaining a `WebTransportReceiveStream` is a two-step process for a web client. First, the client calls the `incomingUnidirectionalStreams` attribute of a `WebTransport` instance, which returns a `ReadableStream`. Each chunk of that `ReadableStream`, is, in turn, a `WebTransportReceiveStream` that can be used to read `Uint8Array` instances sent by the server.

    async function readFrom(receiveStream) {
      const reader = receiveStream.readable.getReader();
      while (true) {
        const {done, value} = await reader.read();
        if (done) {
          break;
        }
        // value is a Uint8Array
        console.log(value);
      }
    }

    const rs = transport.incomingUnidirectionalStreams;
    const reader = rs.getReader();
    while (true) {
      const {done, value} = await reader.read();
      if (done) {
        break;
      }
      // value is an instance of WebTransportReceiveStream
      await readFrom(value);
    }

You can detect stream closure using the [`closed`](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader/closed) promise of the [`ReadableStreamDefaultReader`](https://developer.mozilla.org/docs/Web/API/ReadableStreamDefaultReader). When the underlying HTTP/3 stream is [closed with the FIN bit](https://tools.ietf.org/html/draft-ietf-quic-transport-27#section-19.8), the `closed` promise is fulfilled after all the data is read. When the HTTP/3 stream is closed abruptly (for example, by [`RESET_STREAM`](https://w3c.github.io/webtransport/#stream-reset)), then the `closed` promise rejects.

    // Assume an active receiveStream
    const reader = receiveStream.readable.getReader();
    reader.closed.then(() => {
      console.log('The receiveStream closed gracefully.');
    }).catch(() => {
      console.error('The receiveStream closed abruptly.');
    });

#### WebTransportBidirectionalStream

A [`WebTransportBidirectionalStream`](https://w3c.github.io/webtransport/#bidirectional-stream)
might be created either by the server or the client.

Web clients can create one using the `createBidirectionalStream()` method of a
`WebTransport` instance, which returns a promise for a
`WebTransportBidirectionalStream`.

    const stream = await transport.createBidirectionalStream();
    // stream is a WebTransportBidirectionalStream
    // stream.readable is a ReadableStream
    // stream.writable is a WritableStream

You can listen for a `WebTransportBidirectionalStream` created by the server with the `incomingBidirectionalStreams` attribute of a `WebTransport` instance, which returns a `ReadableStream`. Each chunk of that `ReadableStream`, is, in turn, a `WebTransportBidirectionalStream`.

    const rs = transport.incomingBidirectionalStreams;
    const reader = rs.getReader();
    while (true) {
      const {done, value} = await reader.read();
      if (done) {
        break;
      }
      // value is a WebTransportBidirectionalStream
      // value.readable is a ReadableStream
      // value.writable is a WritableStream
    }

A `WebTransportBidirectionalStream` is just a combination of a `WebTransportSendStream` and `WebTransportReceiveStream`. The examples from the previous two sections explain how to use each of them.

## Polyfill

A polyfill (or rather ponyfill that provides functionality as a standalone module you can use) called
[`webtransport-ponyfill-websocket`](https://github.com/fails-components/webtransport-ponyfill-websocket)
that implements some of the features of WebTransport is available. Carefully read the constraints in
the project's `README` to determine if this solution can work for your use case.

## Privacy and security considerations

See the [corresponding section](https://w3c.github.io/webtransport/#privacy-security)
of the draft specification for authoritative guidance.

## Feedback

Is there something about the API that's awkward or doesn't work as expected? Or
are there missing pieces that you need to implement your idea?

- File an issue on the [Web Transport GitHub repo](https://github.com/WICG/web-transport/issues), or add your thoughts to an existing issue.
- File a bug about [Chromium's implementation](https://new.crbug.com). Include as much detail as you can, along with instructions for reproducing.

Your public support helps Chrome prioritize features, and shows other browser
vendors how critical it is to support them.

- Tweet [@ChromiumDev](https://twitter.com/chromiumdev) using the hashtag [`#WebTransport`](https://twitter.com/search?q=%23WebTransport&src=typed_query&f=live) and details on where and how you're using it.

### General discussion

You can use the [web-transport-dev Google Group](https://groups.google.com/a/chromium.org/g/web-transport-dev) for general questions or problems that don't fit into one of the other categories.

## Acknowledgements

We incorporated information from the
[WebTransport Explainer](https://github.com/w3c/webtransport/blob/main/explainer.md)
and [draft specification](https://w3c.github.io/webtransport/). Thank you to the
respective authors for providing that foundation.