InstaWebhook
October 14, 2026By InstaWebhook TeamDeveloper Guides

HTTP/3 and QUIC for Webhook Delivery: What It Fixes, What It Doesn’t, and How to Adopt It

Discover how upgrading to HTTP/3 and QUIC improves webhook delivery at scale, reduces handshake latency, eliminates head-of-line blocking, and prevents TCP socket exhaustion.

HTTP/3 and QUIC for Webhook Delivery: What It Fixes, What It Doesn’t, and How to Adopt It

HTTP/3 and QUIC for Webhook Delivery: What It Fixes, What It Doesn’t, and How to Adopt It

Webhooks are the standard way services push events to each other: payment notifications, repository events, order updates. A webhook sender has an unusual job. It makes outbound HTTPS requests to thousands of independent endpoints it doesn’t control. Many are slow, some are on lossy links, and most receive only occasional bursts of traffic.

HTTP/3, built on the QUIC transport, changes several things that matter for this workload. It is not a universal speed-up, though. This article covers where HTTP/3 helps webhook delivery, where it doesn’t, and what it takes to adopt it safely.


1. Where Webhook Delivery Hits TCP’s Limits

Handshake cost for sporadic endpoints

A new HTTPS connection over TCP with TLS 1.3 needs one round trip for the TCP handshake and one for the TLS handshake. The first request byte therefore goes out roughly two round trips after the dial begins. A QUIC connection combines the transport and TLS 1.3 handshakes (RFC 9001) and needs one round trip before the client can send application data.

For a sender that keeps long-lived, busy connections to a few endpoints, this saving is amortized away. For a sender that delivers one event every few minutes to each of thousands of subscribers, many deliveries pay the full handshake cost, so the saving is real.

Ephemeral port and TIME_WAIT pressure

Each outbound TCP connection is identified by its 4-tuple (source IP, source port, destination IP, destination port). A sender with a fixed set of egress IPs and a busy destination can run short of source ports. On Linux, the default ephemeral range (net.ipv4.ip_local_port_range) is 32768–60999, which gives about 28,000 ports per source IP and destination tuple. Connections that close actively also linger in TIME_WAIT, which is 60 seconds on Linux, so the usable number is lower in practice.

QUIC runs over UDP, and a single UDP socket can carry many QUIC connections, which are distinguished by connection IDs rather than by the 4-tuple alone. A QUIC client therefore doesn’t need a separate local port per connection. There is also no TIME_WAIT state in the TCP sense. This relieves port pressure, though it does not remove other limits such as file descriptors, CPU, or NAT table capacity.

Head-of-line blocking

Head-of-line (HoL) blocking has several distinct forms:

  1. HTTP/1.1. One request occupies a connection at a time. Pipelining exists in the spec but is rarely used in practice. A slow response ties up the connection, so senders open many parallel connections.
  2. HTTP/2 over TCP. Many streams share one TCP connection, which removes request-level blocking. TCP, however, delivers a single ordered byte stream. When one packet is lost, the kernel withholds all later data from the application until the retransmission arrives, so every stream on that connection stalls, including streams whose packets all arrived.
  3. HTTP/3 over QUIC. Streams are independent at the transport layer. Loss on one stream delays only that stream, and other streams can keep delivering data.

QUIC does not eliminate HoL blocking entirely. Data within a single stream is still delivered in order, and an overloaded receiving application can still queue work.


2. How QUIC Works

QUIC is specified in RFC 9000 (transport), RFC 9001 (TLS integration) and RFC 9002 (loss detection and congestion control). HTTP/3 is specified in RFC 9114 and its header compression, QPACK, in RFC 9204. All were published in 2021–2022. The protocol grew out of work Google began around 2012, with Jim Roskind as its original designer, and was then standardized by the IETF with significant changes.

Code example
+-------------------------------------------------------+
| HTTP/3 (Semantics: Headers, Methods, QPACK)           |
+-------------------------------------------------------+
| QUIC (Streams, Loss Recovery, Congestion Control,     |
|       TLS 1.3 handshake and encryption)               |
+-------------------------------------------------------+
| UDP (Datagrams)                                       |
+-------------------------------------------------------+
| IP                                                    |
+-------------------------------------------------------+

QUIC implementations live in user space, which makes them easier to update than kernel TCP stacks. They also have to build features such as pacing, batching, and socket buffer management themselves.

0-RTT: powerful, but dangerous for webhooks

QUIC supports 0-RTT resumption: a client that has previously connected to a server can send application data in its first flight. That sounds ideal for webhooks, but 0-RTT data is not protected against replay. An attacker who captures the first flight can resend it, and the server may process it twice.

Because of this, HTTP/3 clients and libraries restrict 0-RTT to idempotent requests. The Go library quic-go, for example, exposes 0-RTT only for GET and HEAD requests and documents that 0-RTT offers no replay protection. A webhook is a POST that triggers side effects, so a dispatcher should not send it in 0-RTT unless the receiver has made its handler idempotent and explicitly accepts early data. Treat the 1-RTT handshake as the realistic baseline.

Connection migration: narrower than it sounds

QUIC connections are identified by connection IDs rather than by the IP and port 4-tuple. A client can therefore continue a connection after its address changes, for example after a NAT rebinding or a switch between network interfaces. Two limits apply:

  • Migration is initiated by the client. A server cannot migrate an established connection to a new address on its own.
  • A dispatcher pod that is evicted and rescheduled with a new IP does not carry its connections with it. The process is gone, and so is its connection state. New connections have to be established, as with TCP.

For webhook dispatchers, migration mainly helps with NAT rebinding and path changes. It does not help with pod churn, so don’t plan around it for that.


3. Designing a Dispatcher for HTTP/3

From connection pools to multiplexed sessions

HTTP/2 and HTTP/3 both let one connection carry many concurrent requests. A dispatcher can therefore keep one or a few long-lived connections per subscriber host rather than a deep pool. The peer limits the number of concurrent streams through the SETTINGS/transport parameters it advertises, and dispatchers should respect that limit.

Prioritization

Prioritization in HTTP/3 uses the Extensible Prioritization scheme from RFC 9218, which signals urgency and incrementality hints through the Priority header field. Servers treat these signals as hints and are not required to honor them. Don’t rely on them to guarantee that a critical event overtakes a routine one. Handle that ordering in your own queues before events reach the transport.

Loss recovery and congestion control

QUIC’s loss recovery works per packet and retransmits the affected stream data while other streams continue. RFC 9002 describes a NewReno-style baseline, and implementations commonly offer Cubic and, in some cases, BBR variants. The available algorithms depend on the library, so check the one you use.

Discovery and fallback

A dispatcher needs to know whether an endpoint supports HTTP/3. There are two standard mechanisms:

  • Alt-Svc response header (RFC 7838): an HTTP/1.1 or HTTP/2 response advertises an HTTP/3 alternative on a UDP port, usually 443.
  • DNS HTTPS records (RFC 9460), which can advertise h3 support before the first connection.

Whichever you use, UDP can be blocked or rate-limited by a firewall or network path, so you need a fallback to TCP. A practical design:

  • Race HTTP/3 and TCP when no preference is cached, or try HTTP/3 with a short timeout.
  • Cache the outcome per endpoint with a TTL, and retry HTTP/3 after the TTL expires.
  • Treat the fallback as a normal path, not an error.

4. Reference Architecture

Code example
[Event Source / Queue] ---> [Dispatcher Workers]
                                      |
                              [Protocol Selector + Cache]
                                      |
       +------------------------------+------------------------------+
       | HTTP/3 (QUIC/UDP)            | HTTP/3 (QUIC/UDP)            | HTTP/2 or 1.1 (TCP)
       v                              v                              v
[Subscriber A]                [Subscriber B]                [Subscriber C]
  1. Durable queue. Events are buffered in a system such as Kafka, SQS, or RabbitMQ.
  2. Workers. They serialize the payload and sign it. Providers differ here: GitHub sends an HMAC-SHA256 signature in X-Hub-Signature-256, Stripe uses a Stripe-Signature header, and Shopify uses X-Shopify-Hmac-Sha256. Subscribers need to know which scheme you use.
  3. Protocol selector. It holds the per-endpoint cache described above.
  4. Retries. Use exponential backoff with jitter, and give every event a stable ID so receivers can deduplicate.

Go sketch

This example uses quic-go’s HTTP/3 transport and sends a real request body. The transport is created once and reused, because creating one per request throws away connection reuse. (In recent quic-go releases http3.RoundTripper was renamed to http3.Transport; check the version you depend on.)

Code example
package main

import (
	"bytes"
	"context"
	"errors"
	"fmt"
	"io"
	"net/http"
	"time"

	"github.com/quic-go/quic-go/http3"
)

var h3Transport = &http3.Transport{}

var h3Client = &http.Client{
	Transport: h3Transport,
	Timeout:   10 * time.Second,
}

// Standard TCP client (HTTP/2 or HTTP/1.1) used as a fallback.
var tcpClient = &http.Client{Timeout: 10 * time.Second}

func newRequest(ctx context.Context, url string, payload []byte, sig string) (*http.Request, error) {
	req, err := http.NewRequestWithContext(ctx, http.MethodPost, url, bytes.NewReader(payload))
	if err != nil {
		return nil, err
	}
	req.Header.Set("Content-Type", "application/json")
	req.Header.Set("X-Webhook-Signature", sig)
	req.Header.Set("User-Agent", "ExampleWebhookDispatcher/1.0")
	return req, nil
}

func send(client *http.Client, req *http.Request) error {
	resp, err := client.Do(req)
	if err != nil {
		return err
	}
	defer resp.Body.Close()
	_, _ = io.Copy(io.Discard, resp.Body)

	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
		return fmt.Errorf("subscriber returned status %d", resp.StatusCode)
	}
	return nil
}

// dispatch tries HTTP/3 first (if the endpoint is known to support it)
// and falls back to TCP on transport-level failure.
func dispatch(ctx context.Context, url string, payload []byte, sig string, supportsH3 bool) error {
	if supportsH3 {
		h3Ctx, cancel := context.WithTimeout(ctx, 3*time.Second)
		defer cancel()
		req, err := newRequest(h3Ctx, url, payload, sig)
		if err != nil {
			return err
		}
		if err := send(h3Client, req); err == nil {
			return nil
		} else if !isTransportError(err) {
			return err // e.g. a non-2xx response: do not resend over TCP blindly
		}
		// transport failure: record it in the protocol cache, then fall through
	}

	req, err := newRequest(ctx, url, payload, sig)
	if err != nil {
		return err
	}
	return send(tcpClient, req)
}

func isTransportError(err error) bool {
	var ne interface{ Timeout() bool }
	return errors.As(err, &ne) || errors.Is(err, context.DeadlineExceeded)
}

Note the retry caveat: a timeout can mean the receiver processed the request but the response was lost. Falling back to another transport can therefore deliver the event twice. Include an event ID in the payload and require receivers to be idempotent.


5. Operational Challenges

UDP blocking and throttling

Some enterprise firewalls and cloud security groups block or rate-limit UDP port 443. Make sure your egress rules allow it, and make the TCP fallback automatic and cached.

CPU cost and tuning

QUIC does packet processing and encryption in user space, and implementations are generally more CPU-intensive per byte than kernel TCP with offloads, though the gap varies by library and hardware. Practical steps:

  • Load-test your actual library and payload sizes rather than assuming parity with TCP.
  • Raise the UDP socket buffer limits (net.core.rmem_max and net.core.wmem_max on Linux). The quic-go documentation recommends this and logs a warning when the buffers are small.
  • Watch for kernel and NIC features, such as UDP segmentation offload, that your library can exploit.

Amplification and abuse

QUIC’s address validation and its anti-amplification limit (a server may send at most three times the data it has received from an unvalidated address) mainly protect servers. A dispatcher acting as a client is less exposed, but the usual outbound controls still apply: egress rate limits, SSRF protections on subscriber-supplied URLs, and sensible timeouts.

Observability

QUIC encrypts most transport metadata that TCP exposes, so passive tools such as tcpdump with TCP flag analysis show much less. Instrument the application instead:

  • handshake duration and failure reasons
  • fraction of deliveries by protocol (HTTP/3 vs. fallback)
  • per-endpoint latency and error rates
  • stream resets and connection closures

Many QUIC libraries can emit qlog traces for deep debugging.


6. What the Evidence Says

Public measurements show the general pattern rather than webhook-specific numbers, so treat any exact figure with caution, including the ones below.

  • Adoption on the receiving side is substantial but uneven. W3Techs reported HTTP/3 in use on about 39.7% of websites in July 2026. Much of that is concentrated behind CDNs and reverse proxies; for sites using Cloudflare, W3Techs reported 88.8% in March 2026.
  • Actual request share is lower. One analysis of Q1 2026 Cloudflare Radar data put HTTP/3 at about 21% of requests, compared with about 51% for HTTP/2 and about 28% for HTTP/1.x, with bot and server-to-server traffic a large contributor to the gap.
  • Benefits are largest on high-latency and lossy paths. Field measurements consistently find larger gains over long distances and on mobile networks than on clean, low-latency datacenter links.

For a dispatcher, this means two things. First, only some of your subscribers can receive HTTP/3 today, so fallback is a day-one requirement. Second, the gain for any individual endpoint depends on its network path, so measure your own traffic: compare latency percentiles, failure rates, and CPU use between the two protocols for the same endpoints before committing.


7. Should You Adopt It?

HTTP/3 is a good fit for a webhook dispatcher when:

  • many endpoints are contacted infrequently, so handshake cost matters,
  • you deliver across long or lossy internet paths,
  • you are hitting ephemeral port limits or connection churn on TCP, and
  • your subscribers sit behind CDNs or proxies that already speak HTTP/3.

It offers little when your traffic goes to a handful of well-connected endpoints over warm, long-lived HTTP/2 connections.

A sensible rollout:

  1. Add HTTP/3 as an opt-in transport behind a feature flag, with cached fallback to TCP.
  2. Start with endpoints that advertise Alt-Svc or DNS h3 support.
  3. Don’t use 0-RTT for POST deliveries.
  4. Compare latency, success rate, and CPU against the TCP baseline per endpoint.
  5. Expand gradually as the data supports it.

References and Further Reading

  1. RFC 9000: QUIC: A UDP-Based Multiplexed and Secure Transport, IETF.
  2. RFC 9001: Using TLS to Secure QUIC, IETF.
  3. RFC 9002: QUIC Loss Detection and Congestion Control, IETF.
  4. RFC 9114: HTTP/3, IETF.
  5. RFC 9204: QPACK: Field Compression for HTTP/3, IETF.
  6. RFC 9218: Extensible Prioritization Scheme for HTTP, IETF.
  7. RFC 7838: HTTP Alternative Services, IETF.
  8. RFC 9460: Service Binding and Parameter Specification via the DNS (SVCB and HTTPS Resource Records), IETF.
  9. W3Techs, Usage statistics of HTTP/3 (w3techs.com).
  10. Cloudflare Radar, HTTP protocol version trends (radar.cloudflare.com).
  11. quic-go documentation (quic-go.net), including its notes on 0-RTT and UDP buffer sizes.
  12. Ilya Grigorik, High Performance Browser Networking (O’Reilly, 2013). Pre-dates HTTP/3, but remains a good foundation for TCP and TLS performance.