diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 574fe38aa0e3c..1e7b7fe08ee5f 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -967,6 +967,7 @@ _BANNED_CPP_FUNCTIONS: Sequence[BanRule] = (
             r'android_webview/browser/ip_protection/.*',
             r'chrome/browser/ip_protection/.*',
             r'components/ip_protection/.*',
+            r'net/quic/dedicated_web_transport_http3_client\.cc',
 
             # Needed to use MediaPipe API.
             r'components/media_effects/.*\.cc',
diff --git a/net/quic/dedicated_web_transport_http3_client.cc b/net/quic/dedicated_web_transport_http3_client.cc
index b1c857e8a8880..9879fd4203058 100644
--- a/net/quic/dedicated_web_transport_http3_client.cc
+++ b/net/quic/dedicated_web_transport_http3_client.cc
@@ -28,6 +28,7 @@
 #include "net/third_party/quiche/src/quiche/quic/core/quic_connection.h"
 #include "net/third_party/quiche/src/quiche/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quiche/quic/core/quic_utils.h"
+#include "net/third_party/quiche/src/quiche/web_transport/web_transport_headers.h"
 #include "net/url_request/url_request_context.h"
 #include "url/scheme_host_port.h"
 
@@ -365,6 +366,7 @@ DedicatedWebTransportHttp3Client::DedicatedWebTransportHttp3Client(
     : url_(url),
       origin_(origin),
       anonymization_key_(anonymization_key),
+      application_protocols_(parameters.application_protocols),
       context_(context),
       visitor_(visitor),
       quic_context_(context->quic_context()),
@@ -748,6 +750,14 @@ int DedicatedWebTransportHttp3Client::DoSendRequest() {
   headers[":protocol"] = "webtransport";
   headers["sec-webtransport-http3-draft02"] = "1";
   headers["origin"] = origin_.Serialize();
+  if (!application_protocols_.empty()) {
+    absl::StatusOr<std::string> protocols_header =
+        webtransport::SerializeSubprotocolRequestHeader(application_protocols_);
+    if (protocols_header.ok()) {
+      headers[webtransport::kSubprotocolRequestHeader] =
+          *std::move(protocols_header);
+    }
+  }
   stream->WriteHeaders(std::move(headers), /*fin=*/false, nullptr);
 
   web_transport_session_ = stream->web_transport();
diff --git a/net/quic/dedicated_web_transport_http3_client.h b/net/quic/dedicated_web_transport_http3_client.h
index 30d000ce4bc8a..516bef6c81bdc 100644
--- a/net/quic/dedicated_web_transport_http3_client.h
+++ b/net/quic/dedicated_web_transport_http3_client.h
@@ -150,6 +150,7 @@ class NET_EXPORT DedicatedWebTransportHttp3Client
   const GURL url_;
   const url::Origin origin_;
   const NetworkAnonymizationKey anonymization_key_;
+  const std::vector<std::string> application_protocols_;
   const raw_ptr<URLRequestContext> context_;          // Unowned.
   const raw_ptr<WebTransportClientVisitor> visitor_;  // Unowned.
 
diff --git a/net/quic/dedicated_web_transport_http3_client_test.cc b/net/quic/dedicated_web_transport_http3_client_test.cc
index de283198da4f3..26adffd17befa 100644
--- a/net/quic/dedicated_web_transport_http3_client_test.cc
+++ b/net/quic/dedicated_web_transport_http3_client_test.cc
@@ -210,7 +210,7 @@ TEST_F(DedicatedWebTransportHttp3Test, Connect) {
       GetURL("/echo"), origin_, &visitor_, anonymization_key_, context_.get(),
       WebTransportParameters());
 
-  EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning());
+  EXPECT_CALL(visitor_, OnConnected).WillOnce(StopRunning());
   client_->Connect();
   Run();
   ASSERT_TRUE(client_->session() != nullptr);
@@ -251,7 +251,7 @@ TEST_F(DedicatedWebTransportHttp3Test, MAYBE_CloseTimeout) {
       GetURL("/echo"), origin_, &visitor_, anonymization_key_, context_.get(),
       WebTransportParameters());
 
-  EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning());
+  EXPECT_CALL(visitor_, OnConnected).WillOnce(StopRunning());
   client_->Connect();
   Run();
   ASSERT_TRUE(client_->session() != nullptr);
@@ -277,7 +277,7 @@ TEST_F(DedicatedWebTransportHttp3Test, CloseReason) {
       GetURL("/session-close"), origin_, &visitor_, anonymization_key_,
       context_.get(), WebTransportParameters());
 
-  EXPECT_CALL(visitor_, OnConnected(_)).WillOnce(StopRunning());
+  EXPECT_CALL(visitor_, OnConnected).WillOnce(StopRunning());
   client_->Connect();
   Run();
   ASSERT_TRUE(client_->session() != nullptr);
@@ -296,5 +296,42 @@ TEST_F(DedicatedWebTransportHttp3Test, CloseReason) {
   EXPECT_THAT(received_close_info, Optional(close_info));
 }
 
+// Test negotiation of the application protocol via
+// https://www.ietf.org/archive/id/draft-ietf-webtrans-http3-12.html#name-application-protocol-negoti
+TEST_F(DedicatedWebTransportHttp3Test, SubprotocolHeader) {
+  StartServer();
+  WebTransportParameters parameters;
+  parameters.application_protocols = {"first", "second", "third"};
+  // The selected-subprotocol endpoint selects the first of the offered
+  // protocols by default, and echoes it on a unidirectional stream.
+  client_ = std::make_unique<DedicatedWebTransportHttp3Client>(
+      GetURL("/selected-subprotocol"), origin_, &visitor_, anonymization_key_,
+      context_.get(), parameters);
+
+  bool stream_received = false;
+  EXPECT_CALL(visitor_, OnConnected).WillOnce(StopRunning());
+  EXPECT_CALL(visitor_, OnIncomingUnidirectionalStreamAvailable).WillOnce([&] {
+    stream_received = true;
+    StopRunning();
+  });
+  client_->Connect();
+  Run();
+  ASSERT_TRUE(client_->session() != nullptr);
+
+  EXPECT_EQ(client_->session()->GetNegotiatedSubprotocol(), "first");
+
+  if (!stream_received) {
+    Run();
+  }
+
+  quic::WebTransportStream* stream =
+      client_->session()->AcceptIncomingUnidirectionalStream();
+  ASSERT_TRUE(stream != nullptr);
+  std::string read_buffer;
+  webtransport::Stream::ReadResult read_result = stream->Read(&read_buffer);
+  ASSERT_TRUE(read_result.fin);
+  EXPECT_EQ(read_buffer, "first");
+}
+
 }  // namespace
 }  // namespace net::test
diff --git a/net/quic/web_transport_client.h b/net/quic/web_transport_client.h
index f597a4dc902d2..7b7e187983a0f 100644
--- a/net/quic/web_transport_client.h
+++ b/net/quic/web_transport_client.h
@@ -106,9 +106,13 @@ struct NET_EXPORT WebTransportParameters {
   bool enable_web_transport_http3 = false;
 
   // A vector of fingerprints for expected server certificates, as described in
-  // https://wicg.github.io/web-transport/#dom-quictransportconfiguration-server_certificate_fingerprints
+  // https://w3c.github.io/webtransport/#dom-webtransportoptions-servercertificatehashes
   // When empty, Web PKI is used.
   std::vector<quic::CertificateFingerprint> server_certificate_fingerprints;
+
+  // A vector of strings offered by client as a list of potential subprotocols.
+  // https://w3c.github.io/webtransport/#dom-webtransportoptions-protocols
+  std::vector<std::string> application_protocols;
 };
 
 // An abstract base for a WebTransport client.  Most of the useful operations