<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: InstaWebhook</title>
    <description>The latest articles on DEV Community by InstaWebhook (@instawebhook).</description>
    <link>https://dev.to/instawebhook</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4015117%2F816ab357-711b-419d-94c7-13745e03c38c.png</url>
      <title>DEV Community: InstaWebhook</title>
      <link>https://dev.to/instawebhook</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/instawebhook"/>
    <language>en</language>
    <item>
      <title>Memory-Mapped Webhook Buffers: Using `mmap` and Ring Buffers to Cut Kernel-User Crossings at 500k RPS</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Mon, 02 Nov 2026 02:21:37 +0000</pubDate>
      <link>https://dev.to/instawebhook/memory-mapped-webhook-buffers-using-mmap-and-ring-buffers-to-cut-kernel-user-crossings-at-500k-2img</link>
      <guid>https://dev.to/instawebhook/memory-mapped-webhook-buffers-using-mmap-and-ring-buffers-to-cut-kernel-user-crossings-at-500k-2img</guid>
      <description>&lt;p&gt;Webhooks connect payment gateways, CI/CD pipelines and SaaS platforms. At scale, the service that receives them has to accept a request, verify it, acknowledge it quickly and hand it off for processing, hundreds of thousands of times per second.&lt;/p&gt;

&lt;p&gt;This article looks at what actually costs CPU time at &lt;strong&gt;500,000 requests per second&lt;/strong&gt;. It shows where a shared-memory ring buffer built on &lt;code&gt;mmap&lt;/code&gt; helps, how to write one correctly, and how modern &lt;code&gt;io_uring&lt;/code&gt; features fit around it. It also covers where the approach does not help, because those limits matter as much as the gains.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where the time goes at 500k RPS
&lt;/h2&gt;

&lt;p&gt;500,000 RPS leaves a very small time budget per request. A single core handling all of that traffic would have 2 µs per request. Spread across 32 cores, each request gets about 64 µs of core time, and that has to cover TCP, TLS (if you terminate it), HTTP parsing, signature verification and the handoff to workers. A 1 KB payload at this rate is about 512 MB/s, or roughly 4 Gbit/s of body data before protocol overhead, so a 25 GbE NIC is not the limit. CPU time per request is.&lt;/p&gt;

&lt;h3&gt;
  
  
  The traditional receive path
&lt;/h3&gt;

&lt;p&gt;A typical blocking or &lt;code&gt;epoll&lt;/code&gt;-based server handles an incoming webhook like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The NIC writes the packet into a kernel-owned receive ring by DMA.&lt;/li&gt;
&lt;li&gt;An interrupt (or a polled NAPI cycle) triggers softirq processing, and the kernel runs the packet through IP and TCP.&lt;/li&gt;
&lt;li&gt;The payload lands in the socket’s receive queue (&lt;code&gt;sk_buff&lt;/code&gt; structures in kernel memory).&lt;/li&gt;
&lt;li&gt;The application calls &lt;code&gt;recv&lt;/code&gt;/&lt;code&gt;read&lt;/code&gt;, which switches into the kernel.&lt;/li&gt;
&lt;li&gt;The kernel copies the data into the user-space buffer (&lt;code&gt;copy_to_user&lt;/code&gt;) and returns to user mode.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------+
| USER SPACE                                                  |
|   Application buffer                                        |
|        ^                                                    |
|        | copy_to_user()  (copy + syscall entry/exit)        |
+--------|----------------------------------------------------+
| KERNEL SPACE                                                |
|   Socket receive queue (sk_buff)                            |
|        ^                                                    |
|        | TCP/IP stack processing                            |
+--------|----------------------------------------------------+
| HARDWARE                                                    |
|   NIC receive ring (DMA)                                    |
+-------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A system call is not a context switch
&lt;/h3&gt;

&lt;p&gt;The terminology matters here. A system call is a privilege-mode transition within the same thread. A &lt;strong&gt;context switch&lt;/strong&gt; is the scheduler replacing one thread or process with another, which usually means saving and restoring state and possibly changing address spaces.&lt;/p&gt;

&lt;p&gt;Both cost something, but they are different amounts. In one widely cited 2010 measurement on older Intel hardware, the cheapest system call (&lt;code&gt;gettid&lt;/code&gt;) cost roughly 52 to 105 ns depending on the CPU. The same benchmark measured process context switches at about 1.3 to 1.9 µs, and about 3 µs when &lt;code&gt;futex&lt;/code&gt; calls were included. A University of Rochester study put the direct cost of a context switch at about 3.4 µs on 2000s-era hardware. It also found that the indirect cost of refilling caches ranged from several microseconds to hundreds of microseconds, depending on working-set size. More recent measurements report a direct kernel-level context switch of around 0.62 µs. These numbers vary with CPU, kernel, mitigations and measurement method, so treat them as orders of magnitude rather than constants. Always measure your own hardware.&lt;/p&gt;

&lt;p&gt;The practical conclusion is that per-request system calls, copies, and scheduler wake-ups add up. At 500k RPS they can take a meaningful share of your CPU budget. The size of that share depends on your workload, so profile with &lt;code&gt;perf&lt;/code&gt; before assuming a number.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What &lt;code&gt;mmap&lt;/code&gt; and ring buffers actually buy you
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;mmap()&lt;/code&gt; maps a file or shared-memory object into a process’s address space. If two processes map the same object with &lt;code&gt;MAP_SHARED&lt;/code&gt;, they can exchange data through plain loads and stores. The kernel is only involved when the mapping is created and when pages fault in, not on every message.&lt;/p&gt;

&lt;p&gt;That makes it a good fit for &lt;strong&gt;handing parsed webhooks from an ingestion process to worker processes&lt;/strong&gt;. It does not remove the kernel from the network receive path, because TCP still runs in the kernel unless you bypass the stack entirely (see section 4).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------+
| SHARED MEMORY REGION (mmap of a shm object)                           |
|                                                                       |
|   +------------+------------+------------+------------+               |
|   | Slot 0     | Slot 1     | Slot 2     | Slot 3     | ...           |
|   | (Payload)  | (Payload)  | (Payload)  | (Empty)    |               |
|   +------------+------------+------------+------------+               |
|         ^                                     ^                       |
|    [Head: consumer reads]             [Tail: producer writes]         |
+-----------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A ring buffer is a good structure for this handoff because:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No allocation on the hot path.&lt;/strong&gt; Slots are preallocated, so enqueue and dequeue are a few arithmetic and atomic operations in constant time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cache-friendly layout.&lt;/strong&gt; Slots are contiguous and sequentially accessed, which suits hardware prefetchers. Aligning the head and tail counters to separate cache lines also avoids false sharing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No locks for the single-producer, single-consumer (SPSC) case.&lt;/strong&gt; One side only writes the tail, the other only writes the head, so acquire/release atomics are enough. Multiple producers or consumers on one ring need additional machinery (section 3).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If ingestion and workers are threads in one process, an ordinary in-process ring buffer gives you the same benefits without &lt;code&gt;mmap&lt;/code&gt;. Use shared memory when you need process isolation (a worker crash must not take down the listener), independent deploys, or the buffer to survive an ingestion restart.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. A correct SPSC shared-memory ring
&lt;/h2&gt;

&lt;p&gt;Here is a compact C++ implementation for one producer process and one consumer process. It differs from naive versions in a few ways that matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It uses a power-of-two capacity, so the index is a bitmask rather than a modulo.&lt;/li&gt;
&lt;li&gt;It uses monotonically increasing 64-bit counters, so full and empty are unambiguous and every slot is usable.&lt;/li&gt;
&lt;li&gt;It stores each payload’s length in its slot.&lt;/li&gt;
&lt;li&gt;It separates “create” from “attach”, so two processes never race to initialize the header.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;#include &amp;lt;atomic&amp;gt;
#include &amp;lt;cstdint&amp;gt;
#include &amp;lt;cstring&amp;gt;
#include &amp;lt;stdexcept&amp;gt;
#include &amp;lt;fcntl.h&amp;gt;
#include &amp;lt;sys/mman.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

constexpr size_t kCacheLine = 64;

struct RingHeader {
    alignas(kCacheLine) std::atomic&amp;lt;uint64_t&amp;gt; head;  // written by consumer
    alignas(kCacheLine) std::atomic&amp;lt;uint64_t&amp;gt; tail;  // written by producer
    alignas(kCacheLine) uint32_t capacity;           // power of two
    uint32_t slot_size;                              // includes 4-byte length prefix
};

static_assert(std::atomic&amp;lt;uint64_t&amp;gt;::is_always_lock_free,
              "atomics in shared memory must be lock-free");

class ShmRing {
public:
    // Producer side creates; consumer side attaches.
    static ShmRing create(const char* name, uint32_t capacity, uint32_t slot_size) {
        if (capacity == 0 || (capacity &amp;amp; (capacity - 1)) != 0)
            throw std::invalid_argument("capacity must be a power of two");
        size_t bytes = sizeof(RingHeader) + size_t(capacity) * slot_size;
        int fd = shm_open(name, O_CREAT | O_EXCL | O_RDWR, 0600);
        if (fd &amp;lt; 0) throw std::runtime_error("shm_open failed");
        if (ftruncate(fd, bytes) != 0) { close(fd); throw std::runtime_error("ftruncate failed"); }
        ShmRing r(fd, bytes);
        r.h_-&amp;gt;capacity = capacity;
        r.h_-&amp;gt;slot_size = slot_size;
        r.h_-&amp;gt;head.store(0, std::memory_order_relaxed);
        r.h_-&amp;gt;tail.store(0, std::memory_order_release);
        return r;
    }

    static ShmRing attach(const char* name) {
        int fd = shm_open(name, O_RDWR, 0600);
        if (fd &amp;lt; 0) throw std::runtime_error("shm_open failed");
        // Map the header first to learn the full size, then remap.
        void* hdr = mmap(nullptr, sizeof(RingHeader), PROT_READ, MAP_SHARED, fd, 0);
        if (hdr == MAP_FAILED) { close(fd); throw std::runtime_error("mmap failed"); }
        auto* ph = static_cast&amp;lt;RingHeader*&amp;gt;(hdr);
        size_t bytes = sizeof(RingHeader) + size_t(ph-&amp;gt;capacity) * ph-&amp;gt;slot_size;
        munmap(hdr, sizeof(RingHeader));
        return ShmRing(fd, bytes);
    }

    bool push(const void* data, uint32_t len) {            // producer only
        if (len &amp;gt; h_-&amp;gt;slot_size - sizeof(uint32_t)) return false;
        uint64_t tail = h_-&amp;gt;tail.load(std::memory_order_relaxed);
        uint64_t head = h_-&amp;gt;head.load(std::memory_order_acquire);
        if (tail - head == h_-&amp;gt;capacity) return false;     // full: apply backpressure
        char* slot = slots_ + (tail &amp;amp; (h_-&amp;gt;capacity - 1)) * h_-&amp;gt;slot_size;
        std::memcpy(slot, &amp;amp;len, sizeof(len));
        std::memcpy(slot + sizeof(len), data, len);
        h_-&amp;gt;tail.store(tail + 1, std::memory_order_release);
        return true;
    }

    bool pop(void* dst, uint32_t dst_cap, uint32_t&amp;amp; out_len) {  // consumer only
        uint64_t head = h_-&amp;gt;head.load(std::memory_order_relaxed);
        uint64_t tail = h_-&amp;gt;tail.load(std::memory_order_acquire);
        if (head == tail) return false;                    // empty
        const char* slot = slots_ + (head &amp;amp; (h_-&amp;gt;capacity - 1)) * h_-&amp;gt;slot_size;
        uint32_t len;
        std::memcpy(&amp;amp;len, slot, sizeof(len));
        if (len &amp;gt; dst_cap) return false;
        std::memcpy(dst, slot + sizeof(len), len);
        out_len = len;
        h_-&amp;gt;head.store(head + 1, std::memory_order_release);
        return true;
    }

private:
    ShmRing(int fd, size_t bytes) : bytes_(bytes) {
        void* m = mmap(nullptr, bytes, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        close(fd);  // the mapping stays valid after the descriptor is closed
        if (m == MAP_FAILED) throw std::runtime_error("mmap failed");
        h_ = static_cast&amp;lt;RingHeader*&amp;gt;(m);
        slots_ = static_cast&amp;lt;char*&amp;gt;(m) + sizeof(RingHeader);
    }
    RingHeader* h_;
    char* slots_;
    size_t bytes_;
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few things to note about this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is SPSC.&lt;/strong&gt; The producer only reads &lt;code&gt;head&lt;/code&gt; and writes &lt;code&gt;tail&lt;/code&gt;, and the consumer does the reverse. The release store on one side pairs with the acquire load on the other, so a consumer that sees the new tail also sees the slot contents. A &lt;code&gt;fetch_add&lt;/code&gt; on the tail does not make this multi-producer safe, because a producer could claim a slot before finishing its write. To get multiple producers, use one ring per producer (simple and fast), or use a per-slot sequence-number scheme such as Dmitry Vyukov’s bounded MPMC queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use one ring per worker, or per ingestion thread, as the scaling strategy.&lt;/strong&gt; The ingestion layer can shard by connection or hash, and each worker drains its own ring. This keeps every ring SPSC and avoids cross-core contention.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slot size is a trade-off.&lt;/strong&gt; Fixed slots make indexing trivial but waste space for small payloads. If your webhooks vary widely in size, consider variable-length records with a length header and wrap-around handling, or a second tier for oversized payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The ring only moves bytes.&lt;/strong&gt; Authentication, parsing and idempotency live in the code on either side of it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Pairing the ring with &lt;code&gt;io_uring&lt;/code&gt; for network ingress
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; is the Linux async I/O interface merged in kernel 5.1. It uses a submission queue and a completion queue shared between user space and the kernel, so many operations can be queued and reaped with few or no system calls.&lt;/p&gt;

&lt;p&gt;Several features matter for a webhook receiver, and each needs a minimum kernel version:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Batching.&lt;/strong&gt; One &lt;code&gt;io_uring_enter()&lt;/code&gt; call can submit many queued operations and wait for completions. In ordinary mode you still enter the kernel, but once per batch rather than once per operation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQPOLL.&lt;/strong&gt; With &lt;code&gt;IORING_SETUP_SQPOLL&lt;/code&gt;, a kernel thread polls the submission queue, so the application can submit and reap I/O without issuing system calls in the steady state. The cost is that the polling thread consumes a CPU core. Unprivileged use of SQPOLL has been allowed since kernel 5.12.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multishot receive and provided buffer rings.&lt;/strong&gt; Multishot &lt;code&gt;recv&lt;/code&gt; (kernel 6.0 and later) lets a single submission produce a completion for every arrival, with the kernel picking buffers from a pre-registered pool. This removes the need to re-arm a receive after every packet. Receive bundles, which fill several buffers per operation, arrived in kernel 6.10.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-copy receive (zcrx).&lt;/strong&gt; Kernel 6.15 added &lt;code&gt;io_uring&lt;/code&gt; zero-copy receive, which lets the NIC deliver payload data directly into application memory. This is the feature that actually removes &lt;code&gt;copy_to_user&lt;/code&gt; from the receive path. It has strict requirements: the NIC must support hardware header/data split, you must configure flow steering and RSS so that only the intended flows hit a zero-copy receive queue, and the ring must be created with &lt;code&gt;IORING_SETUP_SINGLE_ISSUER&lt;/code&gt;, &lt;code&gt;IORING_SETUP_DEFER_TASKRUN&lt;/code&gt; and &lt;code&gt;IORING_SETUP_CQE32&lt;/code&gt;. The NIC configuration is done outside &lt;code&gt;io_uring&lt;/code&gt;. In a demonstration described by the pull request coverage, a 200G link was saturated from a single CPU core.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Without zcrx, a normal &lt;code&gt;io_uring&lt;/code&gt; socket receive still copies data from kernel buffers into your buffers. What you save is system-call and wake-up overhead, not the copy itself. An accurate description of the pipeline is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-------------------------------------------------------------------+
| NETWORK: kernel TCP/IP stack, io_uring multishot recv             |
|          (zcrx on supported NICs and kernels &amp;gt;= 6.15)             |
+-------------------------------------------------------------------+
                                  │
                                  ▼
+-------------------------------------------------------------------+
| INGESTION THREADS: HTTP parsing, auth, size limits                |
+-------------------------------------------------------------------+
                                  │
                                  ▼  (memcpy into shared slot, no syscall)
+-------------------------------------------------------------------+
| SHARED MEMORY: one SPSC ring per worker                           |
+-------------------------------------------------------------------+
        │                         │                         │
        ▼                         ▼                         ▼
+---------------+         +---------------+         +---------------+
| Worker 1      |         | Worker 2      |         | Worker 3      |
+---------------+         +---------------+         +---------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Workers can verify HMAC-SHA256 signatures, apply business logic and dispatch downstream. The ingestion layer should acknowledge the sender only after the payload is safely enqueued (and, if you need durability, persisted), so a slow downstream never stretches your response time.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Full kernel bypass is a different tool.&lt;/strong&gt; DPDK and AF_XDP move packet handling into user space. DPDK uses poll-mode drivers that poll NIC queues from user space, and its AF_XDP driver redirects packets from a NIC queue into a user-space memory buffer. Both bypass the kernel network stack, which means you must supply your own TCP, and TLS if needed. That is a large step up in complexity, and for a typical HTTP webhook receiver it is usually unnecessary. &lt;code&gt;io_uring&lt;/code&gt; keeps the kernel stack and its tooling (&lt;code&gt;ss&lt;/code&gt;, &lt;code&gt;tcpdump&lt;/code&gt;, firewalling, congestion control).&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Production hardening
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Backpressure and durability
&lt;/h3&gt;

&lt;p&gt;When consumers fall behind, the ring fills, and &lt;code&gt;push&lt;/code&gt; returns &lt;code&gt;false&lt;/code&gt;. Decide in advance what happens:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shed load.&lt;/strong&gt; For low-value telemetry, answer with &lt;code&gt;429 Too Many Requests&lt;/code&gt; (or &lt;code&gt;503&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt;) and let the sender retry. Dropping the oldest entries is a deliberate data-loss policy, so only use it where loss is acceptable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persist before acknowledging.&lt;/strong&gt; For payments or other critical events, a shared-memory ring is not durable. A &lt;code&gt;shm_open&lt;/code&gt; object lives in memory (on Linux typically on tmpfs), so a machine reboot loses it. Write to an append-only log or a durable queue before returning &lt;code&gt;2xx&lt;/code&gt;, and make processing idempotent, because senders retry and at-least-once delivery means duplicates.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Be careful with file-backed &lt;code&gt;mmap&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;A &lt;code&gt;MAP_SHARED&lt;/code&gt; mapping of a regular file lets the kernel write dirty pages back to storage on its own schedule, and it can evict pages under memory pressure. The 2022 CIDR paper “Are You Sure You Want to Use MMAP in Your Database Management System?” lists the resulting hazards. Page faults can happen at any memory access and stall the thread. Evicting pages requires TLB shootdowns, which are inter-processor interrupts that can hurt multi-core scaling. I/O errors surface as &lt;code&gt;SIGBUS&lt;/code&gt; signals rather than return codes. The paper is about database buffer pools, but the same mechanisms apply to a file-backed ingest buffer. For a hot ring, prefer an anonymous or &lt;code&gt;shm_open&lt;/code&gt;-backed region, pre-fault it at startup (for example with &lt;code&gt;MAP_POPULATE&lt;/code&gt; or by touching every page), and use &lt;code&gt;mlock&lt;/code&gt; or huge pages if you need to keep it resident. If you need a durable log, write it with explicit I/O so you control when data is flushed and how errors are handled.&lt;/p&gt;

&lt;h3&gt;
  
  
  NUMA and CPU affinity
&lt;/h3&gt;

&lt;p&gt;On multi-socket machines, memory access cost depends on which socket owns the memory. Keep the NIC’s interrupt or polling cores, the ingestion threads and the shared-memory region on the same NUMA node where possible, and pin threads to cores with &lt;code&gt;pthread_setaffinity_np&lt;/code&gt; or &lt;code&gt;taskset&lt;/code&gt;. This reduces scheduler migrations and cross-socket cache traffic. Verify the effect with &lt;code&gt;perf&lt;/code&gt; rather than assuming it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Check the rest of the path
&lt;/h3&gt;

&lt;p&gt;Shared memory only fixes the handoff. At this rate you should also look at TLS (many deployments terminate it at a load balancer so the webhook service sees plain HTTP), connection reuse, HTTP parsing cost and signature verification, which is a real CPU cost at 500k verifications per second.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. What measurements actually show
&lt;/h2&gt;

&lt;p&gt;You will see articles quote tidy tables claiming a 5x throughput gain and a 10x p99 improvement for an &lt;code&gt;mmap&lt;/code&gt;/&lt;code&gt;io_uring&lt;/code&gt; design. Treat any such table with suspicion unless the benchmark setup is published, because results depend on hardware, kernel version, payload size, connection count and what the application does per request.&lt;/p&gt;

&lt;p&gt;Published, independent measurements are more modest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A University of Edinburgh MSc project comparing &lt;code&gt;io_uring&lt;/code&gt; with &lt;code&gt;epoll&lt;/code&gt; in HTTP server benchmarks found &lt;code&gt;io_uring&lt;/code&gt; ahead by up to 33% in throughput.&lt;/li&gt;
&lt;li&gt;A single-threaded TCP echo benchmark with 1024-byte payloads measured about 176k requests/s for &lt;code&gt;io_uring&lt;/code&gt; against 169k for &lt;code&gt;epoll&lt;/code&gt; with one connection, and nearly identical results (about 421k) at 256 connections. At 512 connections &lt;code&gt;epoll&lt;/code&gt; was ahead.&lt;/li&gt;
&lt;li&gt;A Netty benchmark on Linux 6.6 found &lt;code&gt;io_uring&lt;/code&gt; with half as many worker threads delivered about 3% higher throughput than &lt;code&gt;epoll&lt;/code&gt;, with over twice the per-thread efficiency and far fewer CPU migrations.&lt;/li&gt;
&lt;li&gt;The maintainers of uWebSockets have reported that &lt;code&gt;io_uring&lt;/code&gt; can lose to &lt;code&gt;epoll&lt;/code&gt; for larger messages when it uses more memory and falls out of CPU cache, and that localhost benchmarks can mislead.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The gains are real, but they are usually tens of percent, not an order of magnitude, and they depend heavily on the workload. For reference, public HTTP framework benchmarks (such as HttpArena) show well-tuned &lt;code&gt;epoll&lt;/code&gt; and &lt;code&gt;io_uring&lt;/code&gt; servers exceeding a million simple requests per second on large multi-core machines. So 500k RPS is reachable without exotic techniques when the per-request work is small. The real constraint is how much work each webhook needs.&lt;/p&gt;

&lt;p&gt;To evaluate this design for your own workload:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with a baseline: a well-tuned &lt;code&gt;epoll&lt;/code&gt; server with in-process queues, under realistic payloads and connection counts.&lt;/li&gt;
&lt;li&gt;Profile with &lt;code&gt;perf&lt;/code&gt; and flame graphs to see how much time is actually spent in system calls, copies, scheduling and TLS.&lt;/li&gt;
&lt;li&gt;Change one thing at a time (batching or multishot receive, then SQPOLL, then shared-memory handoff) and measure throughput, CPU per request and p99/p999 latency.&lt;/li&gt;
&lt;li&gt;Test on separate client and server machines over a real network, because loopback results can mislead.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Reaching 500k RPS on a webhook ingestion service means spending a few microseconds of CPU per request, so syscalls, copies, wake-ups and lock contention all matter. A shared-memory SPSC ring buffer gives you a clean, allocation-free, syscall-free handoff between ingestion and worker processes. &lt;code&gt;io_uring&lt;/code&gt; reduces the cost of the network side through batching, multishot receive, optional SQPOLL, and zero-copy receive on kernel 6.15+ with supporting NICs. Neither technique removes the kernel TCP stack, and independent benchmarks suggest incremental rather than dramatic gains over a well-tuned &lt;code&gt;epoll&lt;/code&gt; server. The reliable path is to measure your baseline, adopt these techniques where profiling shows a bottleneck, and design for backpressure and durability from the start.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux 6.15 &lt;code&gt;io_uring&lt;/code&gt; zero-copy receive: &lt;a href="https://www.phoronix.com/news/Linux-6.15-IO_uring" rel="noopener noreferrer"&gt;https://www.phoronix.com/news/Linux-6.15-IO_uring&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kernel documentation for &lt;code&gt;io_uring&lt;/code&gt; zero-copy Rx: &lt;a href="https://kernel.org/doc/html/v6.15/networking/iou-zcrx.html" rel="noopener noreferrer"&gt;https://kernel.org/doc/html/v6.15/networking/iou-zcrx.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Evolution of &lt;code&gt;io_uring&lt;/code&gt; Zero-Copy Receive (netdev 0x1a): &lt;a href="https://netdevconf.info/0x1A/docs/netdev-0x1a-paper41-talk-paper.pdf" rel="noopener noreferrer"&gt;https://netdevconf.info/0x1A/docs/netdev-0x1a-paper41-talk-paper.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;io_uring_setup(2)&lt;/code&gt; and SQPOLL: &lt;a href="https://docs.rs/crate/axboe-liburing/latest/source/liburing-rs/docs/io_uring_setup.md" rel="noopener noreferrer"&gt;https://docs.rs/crate/axboe-liburing/latest/source/liburing-rs/docs/io_uring_setup.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;io_uring_prep_recv(3)&lt;/code&gt; (multishot, buffer rings, bundles): &lt;a href="https://man7.org/linux/man-pages/man3/io_uring_prep_recv.3.html" rel="noopener noreferrer"&gt;https://man7.org/linux/man-pages/man3/io_uring_prep_recv.3.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;io_uring&lt;/code&gt; SQPOLL overview: &lt;a href="https://man.archlinux.org/man/extra/liburing/io_uring_sqpoll.7.en" rel="noopener noreferrer"&gt;https://man.archlinux.org/man/extra/liburing/io_uring_sqpoll.7.en&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Quantifying the Cost of Context Switch (Li, Ding, Shen): &lt;a href="https://www.cs.rochester.edu/%7Ekshen/papers/expcs2007.pdf" rel="noopener noreferrer"&gt;https://www.cs.rochester.edu/~kshen/papers/expcs2007.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Context-switch measurements: &lt;a href="https://web.eecs.umich.edu/%7Echshibo/files/microkiller_report.pdf" rel="noopener noreferrer"&gt;https://web.eecs.umich.edu/%7Echshibo/files/microkiller_report.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;System call and context switch measurements (2010): &lt;a href="https://git.captnemo.in/nemo/hn-classics/src/branch/master/_stories/2010/13930305.md" rel="noopener noreferrer"&gt;https://git.captnemo.in/nemo/hn-classics/src/branch/master/_stories/2010/13930305.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Are You Sure You Want to Use MMAP in Your Database Management System? (CIDR 2022): &lt;a href="https://www.pdl.cmu.edu/ftp/Database/p13-crotty.pdf" rel="noopener noreferrer"&gt;https://www.pdl.cmu.edu/ftp/Database/p13-crotty.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;DPDK AF_XDP poll mode driver: &lt;a href="https://doc.dpdk.org/guides-22.07/nics/af_xdp.html" rel="noopener noreferrer"&gt;https://doc.dpdk.org/guides-22.07/nics/af_xdp.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;io_uring&lt;/code&gt; vs &lt;code&gt;epoll&lt;/code&gt; HTTP server study (University of Edinburgh): &lt;a href="https://project-archive.inf.ed.ac.uk/msc/20236779/msc_proj.pdf" rel="noopener noreferrer"&gt;https://project-archive.inf.ed.ac.uk/msc/20236779/msc_proj.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;io_uring&lt;/code&gt; and &lt;code&gt;epoll&lt;/code&gt; echo server benchmark: &lt;a href="https://github.com/joakimthun/io-uring-echo-server" rel="noopener noreferrer"&gt;https://github.com/joakimthun/io-uring-echo-server&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Netty &lt;code&gt;io_uring&lt;/code&gt; vs &lt;code&gt;epoll&lt;/code&gt; benchmark: &lt;a href="https://www.besthub.dev/articles/does-netty-s-io-uring-make-the-2-cpu-thread-rule-obsolete-3358ba3ca620" rel="noopener noreferrer"&gt;https://www.besthub.dev/articles/does-netty-s-io-uring-make-the-2-cpu-thread-rule-obsolete-3358ba3ca620&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;uWebSockets &lt;code&gt;io_uring&lt;/code&gt; discussion: &lt;a href="https://git.tdem.in/uNetworking/uWebSockets/issues/1603" rel="noopener noreferrer"&gt;https://git.tdem.in/uNetworking/uWebSockets/issues/1603&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/memory-mapped-webhook-buffers-using-mmap-and-ring-buffers-to-cut-kernel-user-cro" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/memory-mapped-webhook-buffers-using-mmap-and-ring-buffers-to-cut-kernel-user-cro&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Building a Resilient Webhook Ingress Proxy in Go: Handling Slow Consumers Without Thread Starvation</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Sun, 01 Nov 2026 02:17:23 +0000</pubDate>
      <link>https://dev.to/instawebhook/building-a-resilient-webhook-ingress-proxy-in-go-handling-slow-consumers-without-thread-starvation-1alo</link>
      <guid>https://dev.to/instawebhook/building-a-resilient-webhook-ingress-proxy-in-go-handling-slow-consumers-without-thread-starvation-1alo</guid>
      <description>&lt;p&gt;Payment gateways, CI/CD systems, e-commerce platforms and chat tools all push events to your servers as HTTP POST requests. Accepting those requests is cheap. The hard part is what happens when something &lt;em&gt;behind&lt;/em&gt; your endpoint slows down: a database under lock contention, a downstream microservice in a garbage-collection spiral, or a third-party API having a bad afternoon.&lt;/p&gt;

&lt;p&gt;A naive receiver that does the downstream work inside the request path, or spawns a goroutine for every event, degrades in a predictable way. Memory grows, sockets pile up, latency climbs, and eventually the process stops accepting new webhooks.&lt;/p&gt;

&lt;p&gt;A note on terminology: Go multiplexes goroutines over a small set of OS threads, so the failure here is not classic thread-pool starvation. It is exhaustion of memory, sockets and file descriptors, plus unbounded queueing delay. This article uses “thread starvation” loosely, as most people searching for this problem do. The fix is the same: decouple ingestion from delivery and put hard limits on everything in between.&lt;/p&gt;

&lt;p&gt;By the end you will have a small, complete Go service that verifies signatures, acknowledges quickly, buffers into a bounded queue, delivers through a fixed worker pool with a tuned HTTP transport, sheds load explicitly, and shuts down without losing queued work. The code was compiled with Go 1.22 and smoke-tested locally.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Anatomy of the Webhook Ingress Problem
&lt;/h2&gt;

&lt;p&gt;Every webhook provider gives you a deadline and a retry policy, and they differ a lot:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Response deadline&lt;/th&gt;
&lt;th&gt;Automatic retries&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitHub&lt;/td&gt;
&lt;td&gt;2xx within 10 seconds&lt;/td&gt;
&lt;td&gt;None; failed deliveries must be redelivered manually or via the API&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Shopify&lt;/td&gt;
&lt;td&gt;2xx within 5 seconds&lt;/td&gt;
&lt;td&gt;Up to 8 retries over about 4 hours; persistent failures can remove the subscription&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack Events API&lt;/td&gt;
&lt;td&gt;2xx within 3 seconds&lt;/td&gt;
&lt;td&gt;Up to 3 retries with exponential backoff&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe&lt;/td&gt;
&lt;td&gt;No specific timeout published; return 2xx before any complex logic&lt;/td&gt;
&lt;td&gt;Live mode: up to 3 days with exponential backoff. Sandbox: 3 retries over a few hours&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Three practical conclusions follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;There is no universal “200–500 ms” rule.&lt;/strong&gt; The tightest documented deadline among these providers is Slack’s 3 seconds. Acknowledge as fast as you can, but design for the strictest sender you integrate with.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow responses cause duplicates.&lt;/strong&gt; If your handler misses a deadline, the provider counts the delivery as failed even if you eventually process it. Providers that retry will send the event again.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dropping an event is not equally recoverable everywhere.&lt;/strong&gt; If you shed load with a 503, Stripe, Shopify and Slack will retry. GitHub will not, so you must redeliver missed events yourself once you recover.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The architecture that satisfies all of this looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[ Third-Party Senders ]
         │  HTTP POST (webhooks at scale)
         ▼
┌─────────────────────────────────────────┐
│         Go Webhook Ingress Proxy        │
│  - Bounded body read                    │
│  - HMAC signature verification          │
│  - Fast validation, then 202 Accepted   │
└─────────────────────────────────────────┘
         │ non-blocking enqueue (full → 503 + Retry-After)
         ▼
[ Bounded in-memory queue ]  ──(optional)──►  [ Durable broker ]
         │
         ▼
[ Fixed worker pool ] ──► tuned HTTP transport ──► [ Downstream service ]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The Naive Anti-Pattern: Unbounded Goroutines
&lt;/h3&gt;

&lt;p&gt;A common trap is spawning a goroutine per request to handle downstream delivery:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// ANTI-PATTERN: DO NOT USE IN PRODUCTION&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;WebhookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;parseBody&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;sendToSlowDownstream&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// can block for a long time&lt;/span&gt;
            &lt;span class="c"&gt;// handle error&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}()&lt;/span&gt;

    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Goroutines are cheap, not free. A goroutine starts with a 2 KB stack, and since Go 1.19 the runtime adapts the initial stack size to the program’s observed average stack usage. That is why the old “2–8 KB” rule of thumb is only approximate.&lt;/p&gt;

&lt;p&gt;The real cost is everything each goroutine &lt;em&gt;holds&lt;/em&gt;. Little’s Law says average concurrency equals arrival rate multiplied by time in the system. At 15,000 events per second:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;With a 50 ms downstream, you have about 750 deliveries in flight.&lt;/li&gt;
&lt;li&gt;When the downstream slows to 5 seconds, you have about 75,000.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The stacks alone are roughly 150 MB at 2 KB each. On top of that, each goroutine pins its payload and typically an open connection. The failure modes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Memory growth.&lt;/strong&gt; Stacks, payload copies and per-connection buffers accumulate with no upper bound.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GC pressure.&lt;/strong&gt; The garbage collector has to scan every live goroutine’s stack, so tens of thousands of parked goroutines make each cycle more expensive.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Socket and file-descriptor exhaustion.&lt;/strong&gt; Each in-flight call to a slow downstream holds a TCP connection, and every open connection consumes a file descriptor, which the OS caps per process (&lt;code&gt;ulimit -n&lt;/code&gt; on Linux). Once you hit the cap, new inbound connections fail too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Silent data loss.&lt;/strong&gt; The handler returned 200 before the event was durable. If the process dies, those events are gone and the provider believes they were delivered.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Go’s HTTP server already runs each connection in its own goroutine, so the handler is exactly where you must not block on downstream work.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Architecture Blueprint: Decoupling Ingress and Egress
&lt;/h2&gt;

&lt;p&gt;A resilient proxy splits an event’s lifecycle into three phases:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingress and validation.&lt;/strong&gt; Read a size-limited body, verify the HMAC signature, check that the payload is well-formed, and return &lt;code&gt;202 Accepted&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer.&lt;/strong&gt; Enqueue into a &lt;em&gt;bounded&lt;/em&gt; structure. A buffered Go channel is the simplest option, but it lives in memory only. A channel cannot be “backed by persistent storage”; durability requires writing to something external (see section 11).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dispatch.&lt;/strong&gt; A fixed pool of workers pulls from the buffer and delivers through a shared, tuned HTTP client, with timeouts, bounded retries and (optionally) a circuit breaker.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every stage has a hard limit: body size, queue length, worker count, connection count, request timeout. When the downstream is slow, pressure propagates back to the one place designed to absorb it, the queue, and then to an explicit “no” at the front door.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Sizing the Pool: Little’s Law Again
&lt;/h2&gt;

&lt;p&gt;Before writing code, decide the numbers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Delivery capacity&lt;/strong&gt; ≈ workers ÷ average delivery latency. With 100 workers and a 100 ms downstream, you can deliver about 1,000 events per second. If latency rises to 1 second, capacity falls to about 100 per second.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue runway&lt;/strong&gt; ≈ queue size ÷ (arrival rate − delivery rate). If events arrive at 3,000 per second against a capacity of 1,000, a 5,000-slot queue fills in about 2.5 seconds.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two things follow. A queue is a shock absorber for &lt;em&gt;bursts&lt;/em&gt;, not a fix for sustained overload. And the queue size should be chosen from how much delay and memory you can tolerate, not set as large as possible. A very large queue only converts overload into long delays and eventually an out-of-memory crash.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Deep Dive 1: A Bounded Worker Pool
&lt;/h2&gt;

&lt;p&gt;You will often see a two-level pattern in tutorials: a dispatcher, plus a &lt;code&gt;chan chan Job&lt;/code&gt; that workers register their private channels into. It is unnecessary in Go. Goroutines blocked on receiving from a single channel are queued by the runtime, and each job goes to exactly one waiting worker. A single shared channel is simpler and easier to shut down correctly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// exits when the channel is closed and drained&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The worker count is the concurrency limit toward the downstream. Whatever the downstream does, you never have more than &lt;code&gt;workers&lt;/code&gt; deliveries in flight.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Deep Dive 2: Non-Blocking Enqueue and Backpressure
&lt;/h2&gt;

&lt;p&gt;When arrivals outpace delivery, the handler must never block waiting for space. A &lt;code&gt;select&lt;/code&gt; with a &lt;code&gt;default&lt;/code&gt; case makes the enqueue non-blocking:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt; &lt;span class="c"&gt;// queue full: shed load&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When &lt;code&gt;Enqueue&lt;/code&gt; returns false, the handler answers &lt;code&gt;503 Service Unavailable&lt;/code&gt; (or &lt;code&gt;429 Too Many Requests&lt;/code&gt;) with a &lt;code&gt;Retry-After&lt;/code&gt; header. Providers are not obliged to honor &lt;code&gt;Retry-After&lt;/code&gt;, but providers that retry will use their own backoff schedule and try again. Choose your overflow policy deliberately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Shed load.&lt;/strong&gt; Return 503 and rely on sender retries. This works for Stripe, Shopify and Slack, but for GitHub you need your own redelivery process.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Spill to a durable queue.&lt;/strong&gt; Write overflow to Redis Streams, RabbitMQ or Kafka instead of rejecting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Drop selectively.&lt;/strong&gt; For low-value events (heartbeats, noisy notifications), dropping may be acceptable. Make that an explicit per-event-type decision.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Deep Dive 3: Connection Pooling in Go’s HTTP Client
&lt;/h2&gt;

&lt;p&gt;Go’s &lt;code&gt;http.DefaultTransport&lt;/code&gt; is reasonable for general use, but it is tuned for modest concurrency: &lt;code&gt;MaxIdleConns&lt;/code&gt; is 100, &lt;code&gt;IdleConnTimeout&lt;/code&gt; is 90 seconds, and &lt;code&gt;DefaultMaxIdleConnsPerHost&lt;/code&gt; is only 2. A webhook proxy that sends many concurrent requests to a single downstream host will keep at most 2 idle connections for it. Every other connection is closed after use and re-dialed, with a new TCP handshake and, for HTTPS, a new TLS handshake. Under load, that churn also produces many sockets in &lt;code&gt;TIME_WAIT&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Two clarifications on common advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creating a new &lt;code&gt;http.Client&lt;/code&gt; is not itself the problem. A client with a nil &lt;code&gt;Transport&lt;/code&gt; shares &lt;code&gt;DefaultTransport&lt;/code&gt;. The problem is creating a new &lt;code&gt;http.Transport&lt;/code&gt; per request, which defeats connection reuse entirely. Create one transport and share it.&lt;/li&gt;
&lt;li&gt;To reuse a connection, the response body must be read to EOF and closed. Skipping the drain makes Go close the connection instead of returning it to the pool.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A transport tuned for a proxy delivering to one primary downstream:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyFromEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dialer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;KeepAlive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ForceAttemptHTTP2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// hard cap on connections to a single downstream&lt;/span&gt;
    &lt;span class="n"&gt;MaxIdleConns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxIdleConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// the default is 2&lt;/span&gt;
    &lt;span class="n"&gt;IdleConnTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TLSHandshakeTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ResponseHeaderTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ExpectContinueTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why these values matter for slow consumers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;MaxConnsPerHost&lt;/code&gt; and &lt;code&gt;MaxIdleConnsPerHost&lt;/code&gt; should track the worker count.&lt;/strong&gt; There is no point keeping 500 idle connections to a host when only 100 workers can use them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts bound how long a worker can be held hostage.&lt;/strong&gt; Without &lt;code&gt;Client.Timeout&lt;/code&gt; and &lt;code&gt;ResponseHeaderTimeout&lt;/code&gt;, a single hung downstream can occupy every worker indefinitely.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;ForceAttemptHTTP2&lt;/code&gt; is needed here.&lt;/strong&gt; Go disables automatic HTTP/2 on a custom transport that sets &lt;code&gt;DialContext&lt;/code&gt; or a TLS config unless you ask for it. With HTTP/2, requests are multiplexed over few connections, so the per-host idle settings matter less for HTTPS targets.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. A Complete, Runnable Example
&lt;/h2&gt;

&lt;p&gt;The program below combines everything: size-limited reads, HMAC verification, a non-blocking bounded queue, a worker pool with bounded retries and jitter, a tuned transport, and a graceful shutdown that drains the queue. It requires Go 1.22 or newer (method-based &lt;code&gt;ServeMux&lt;/code&gt; patterns, &lt;code&gt;math/rand/v2&lt;/code&gt;).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"context"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/hmac"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/sha256"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/hex"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/json"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"math/rand/v2"&lt;/span&gt;
    &lt;span class="s"&gt;"net"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"os/signal"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
    &lt;span class="s"&gt;"time"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;maxWorkers&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;100&lt;/span&gt;
    &lt;span class="n"&gt;queueSize&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5000&lt;/span&gt;
    &lt;span class="n"&gt;maxBodyBytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c"&gt;// 1 MiB&lt;/span&gt;
    &lt;span class="n"&gt;maxAttempts&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Job is one accepted webhook waiting for delivery.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EventID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
    &lt;span class="n"&gt;Body&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// the original bytes, forwarded untouched&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// WorkerPool delivers jobs with a fixed number of goroutines.&lt;/span&gt;
&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;WorkerPool&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;ctx&lt;/span&gt;    &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt; &lt;span class="c"&gt;// cancelled to abort in-flight work on a hard shutdown&lt;/span&gt;
    &lt;span class="n"&gt;jobs&lt;/span&gt;   &lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;
    &lt;span class="n"&gt;wg&lt;/span&gt;     &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WaitGroup&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;NewWorkerPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queueSize&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workers&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="k"&gt;range&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="c"&gt;// exits when the channel is closed and drained&lt;/span&gt;
                &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Enqueue never blocks: it returns false when the queue is full.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Drain stops accepting work and waits for queued jobs to finish or ctx to expire.&lt;/span&gt;
&lt;span class="c"&gt;// Call it only after the HTTP server has stopped, so nothing can send on a closed channel.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Drain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;done&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt;&lt;span class="p"&gt;{})&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;wg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Wait&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="nb"&gt;close&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;done&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Millisecond&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event %s abandoned: shutting down"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;retry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;maxAttempts&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c"&gt;// In production: write to a dead-letter queue instead of only logging.&lt;/span&gt;
            &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"event %s failed after %d attempt(s): %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;sleep&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;N&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;backoff&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// jitter avoids synchronized retries&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;After&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;backoff&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="m"&gt;2&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt; &lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewRequestWithContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodPost&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Event-ID"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;job&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Do&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="c"&gt;// timeouts and connection errors are retryable&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="c"&gt;// Read the body to EOF (bounded) so the connection can be reused.&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Discard&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LimitReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;200&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;300&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusTooManyRequests&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusRequestTimeout&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;500&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"downstream returned %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"downstream rejected event with %d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CutPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"sha256="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// constant-time comparison&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;webhookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;WorkerPool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxBytesReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxBodyBytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;tooBig&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxBytesError&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;As&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;tooBig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload too large"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusRequestEntityTooLarge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;return&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bad request"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Authenticate before doing anything else with the payload.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Hub-Signature-256"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;EventID&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"event_id"`&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid payload"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Enqueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Job&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;evt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;EventID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Retry-After"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"5"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"overloaded, retry later"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusServiceUnavailable&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"application/json"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusAccepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;`{"status":"accepted"}`&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WEBHOOK_SECRET"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Getenv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"DOWNSTREAM_URL"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"WEBHOOK_SECRET and DOWNSTREAM_URL must be set"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;transport&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Proxy&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ProxyFromEnvironment&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Dialer&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;KeepAlive&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DialContext&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ForceAttemptHTTP2&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;     &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="n"&gt;maxWorkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// hard cap on connections to one downstream&lt;/span&gt;
        &lt;span class="n"&gt;MaxIdleConns&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;          &lt;span class="n"&gt;maxWorkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;MaxIdleConnsPerHost&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;maxWorkers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// the default is only 2&lt;/span&gt;
        &lt;span class="n"&gt;IdleConnTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;90&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;TLSHandshakeTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ResponseHeaderTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ExpectContinueTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;Transport&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;transport&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;workCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancelWork&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithCancel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancelWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;NewWorkerPool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;workCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;target&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queueSize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Start&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;maxWorkers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;mux&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewServeMux&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"POST /webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhookHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandleFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"GET /healthz"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;})&lt;/span&gt;

    &lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="s"&gt;":8080"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;           &lt;span class="n"&gt;mux&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ReadHeaderTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;5&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;ReadTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;WriteTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;10&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;IdleTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;sigCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;signal&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NotifyContext&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interrupt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SIGTERM&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;serverErr&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;go&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;serverErr&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServe&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;}()&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"webhook ingress proxy listening on :8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;serverErr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatalf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP server error: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="n"&gt;sigCtx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Done&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"shutdown signal received"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;stop&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;shutdownCtx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WithTimeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Background&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;cancel&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="c"&gt;// 1. Stop accepting requests and wait for in-flight handlers to return.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;srv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Shutdown&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdownCtx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HTTP shutdown: %v"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// 2. Let workers finish what is already queued, up to the deadline.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Drain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;shutdownCtx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Printf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"drain timed out with %d event(s) still queued"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;cancelWork&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// abort anything still in flight&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Println&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"stopped"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Design notes on the code
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;http.MaxBytesReader&lt;/code&gt; instead of &lt;code&gt;io.LimitReader&lt;/code&gt;.&lt;/strong&gt; &lt;code&gt;LimitReader&lt;/code&gt; silently truncates at the limit, so an oversized payload turns into confusing invalid JSON. &lt;code&gt;MaxBytesReader&lt;/code&gt; returns a typed error, so the handler can answer &lt;code&gt;413&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signature verification uses the raw bytes.&lt;/strong&gt; Re-serializing JSON before checking can change the bytes and break the signature. The check uses &lt;code&gt;hmac.Equal&lt;/code&gt; for constant-time comparison, and runs before JSON parsing and before the queue, so unauthenticated traffic never consumes queue capacity. The proxy also forwards the original bytes untouched.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shutdown ordering matters.&lt;/strong&gt; After &lt;code&gt;Shutdown&lt;/code&gt; is called, &lt;code&gt;ListenAndServe&lt;/code&gt; returns &lt;code&gt;ErrServerClosed&lt;/code&gt; &lt;em&gt;immediately&lt;/em&gt;, and the Go documentation warns that the program must not exit before &lt;code&gt;Shutdown&lt;/code&gt; itself returns. A common bug is to call &lt;code&gt;Shutdown&lt;/code&gt; in a goroutine and let &lt;code&gt;main&lt;/code&gt; return as soon as &lt;code&gt;ListenAndServe&lt;/code&gt; does. Here &lt;code&gt;main&lt;/code&gt; waits for &lt;code&gt;Shutdown&lt;/code&gt;, then closes the queue (safe, because no handler can still be enqueueing), then waits for workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Server timeouts are set.&lt;/strong&gt; &lt;code&gt;ReadHeaderTimeout&lt;/code&gt;, &lt;code&gt;ReadTimeout&lt;/code&gt;, &lt;code&gt;WriteTimeout&lt;/code&gt; and &lt;code&gt;IdleTimeout&lt;/code&gt; protect the ingress side from slow &lt;em&gt;senders&lt;/em&gt;, the mirror image of the slow-consumer problem.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Method routing.&lt;/strong&gt; Since Go 1.22, &lt;code&gt;ServeMux&lt;/code&gt; accepts patterns like &lt;code&gt;"POST /webhook"&lt;/code&gt; and automatically replies &lt;code&gt;405 Method Not Allowed&lt;/code&gt; with an &lt;code&gt;Allow&lt;/code&gt; header for other methods.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Try it against a slow consumer
&lt;/h3&gt;

&lt;p&gt;Start any stub downstream that sleeps before replying, point &lt;code&gt;DOWNSTREAM_URL&lt;/code&gt; at it, and send a signed request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;BODY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{"event_id":"evt_1","data":{}}'&lt;/span&gt;
&lt;span class="nv"&gt;SIG&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | openssl dgst &lt;span class="nt"&gt;-sha256&lt;/span&gt; &lt;span class="nt"&gt;-hmac&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$WEBHOOK_SECRET&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;sed&lt;/span&gt; &lt;span class="s1"&gt;'s/^.* //'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
curl &lt;span class="nt"&gt;-i&lt;/span&gt; &lt;span class="nt"&gt;-X&lt;/span&gt; POST localhost:8080/webhook &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Hub-Signature-256: sha256=&lt;/span&gt;&lt;span class="nv"&gt;$SIG&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$BODY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You should see &lt;code&gt;202 Accepted&lt;/code&gt; within milliseconds regardless of how slow the stub is. A request with a bad signature returns &lt;code&gt;401&lt;/code&gt;, and a GET returns &lt;code&gt;405&lt;/code&gt;. To see load shedding, make the stub sleep for several seconds and fire more than &lt;code&gt;maxWorkers + queueSize&lt;/code&gt; events quickly. The surplus gets &lt;code&gt;503&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt;, while the process’s goroutine count stays flat.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Observability: Know When You Are Absorbing Pain
&lt;/h2&gt;

&lt;p&gt;Building the proxy is half the job. These signals tell you whether a slow consumer is hurting you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion rate and response codes.&lt;/strong&gt; Track 202, 401, 413 and 503 separately. A rising 503 rate means the queue is overflowing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue depth&lt;/strong&gt; (&lt;code&gt;len(jobs)&lt;/code&gt;) &lt;strong&gt;and oldest-event age.&lt;/strong&gt; Depth near capacity means your workers cannot keep up. Age is often more useful than depth because it is the delay your users actually experience.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In-flight deliveries&lt;/strong&gt; against the worker count. If every worker is permanently busy, you are at capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Downstream latency and error rate&lt;/strong&gt; (p95/p99 of outgoing calls), plus retry counts and dead-lettered events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Goroutine count&lt;/strong&gt; via &lt;code&gt;runtime.NumGoroutine()&lt;/code&gt;. In this design it should be roughly constant: workers plus per-connection server goroutines. A steady climb indicates a leak. Go 1.27 (released August 2026) also makes the &lt;code&gt;goroutineleak&lt;/code&gt; profile in &lt;code&gt;runtime/pprof&lt;/code&gt; generally available, after it was an experiment in Go 1.26.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  9. Idempotency: Assume Duplicates
&lt;/h2&gt;

&lt;p&gt;Retries mean duplicates, so your downstream must tolerate seeing an event more than once. Stripe’s documentation says events can be delivered more than once and are not guaranteed to arrive in order. Slack documents retry headers (&lt;code&gt;X-Slack-Retry-Num&lt;/code&gt;, &lt;code&gt;X-Slack-Retry-Reason&lt;/code&gt;). GitHub reuses the same &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; GUID when you request a redelivery.&lt;/p&gt;

&lt;p&gt;Practical rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Deduplicate on the provider’s event ID, with a TTL longer than the provider’s retry window.&lt;/li&gt;
&lt;li&gt;Authenticate first, then deduplicate. GitHub’s delivery header is not itself a signed event ID, so don’t trust identifiers until the signature has been verified.&lt;/li&gt;
&lt;li&gt;Don’t assume ordering. Design handlers so that a late or repeated event cannot overwrite newer state.&lt;/li&gt;
&lt;li&gt;The proxy above forwards an &lt;code&gt;X-Event-ID&lt;/code&gt; header so the downstream can implement idempotency cheaply.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  10. Security Notes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Use the right header.&lt;/strong&gt; GitHub’s SHA-256 signature is in &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;. Don’t rely on the older SHA-1 &lt;code&gt;X-Hub-Signature&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reject stale requests where the provider supports it.&lt;/strong&gt; Some providers include a timestamp in the signed material, which lets you reject replays. Check your provider’s documentation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider IP allow-lists as an extra layer.&lt;/strong&gt; GitHub publishes its delivery IP ranges through its &lt;code&gt;/meta&lt;/code&gt; endpoint and notes that they change occasionally, so refresh the list periodically. Treat it as defense in depth, not a replacement for signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Terminate TLS properly and require HTTPS.&lt;/strong&gt; Both GitHub and Stripe expect HTTPS endpoints.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  11. Advanced Enhancements
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Durability.&lt;/strong&gt; An in-memory queue loses everything on a crash or restart, and a &lt;code&gt;202&lt;/code&gt; only promises that the event reached memory. For high-stakes events such as payments, write to a durable broker (Redis Streams, RabbitMQ, Kafka) &lt;em&gt;before&lt;/em&gt; acknowledging, and let the workers consume from there. A common compromise is the in-memory queue as a fast path with a durable spillover when it fills.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Per-destination isolation.&lt;/strong&gt; One shared pool means one slow destination can occupy every worker and starve healthy ones (head-of-line blocking). If you fan out to several downstreams, give each its own queue and concurrency limit.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Circuit breaking.&lt;/strong&gt; When a downstream is failing, retrying only adds load. The &lt;code&gt;sony/gobreaker&lt;/code&gt; library (use the generic &lt;code&gt;v2&lt;/code&gt; module) wraps calls in a breaker that fails fast once failures cross a threshold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="s"&gt;"github.com/sony/gobreaker/v2"&lt;/span&gt;

&lt;span class="n"&gt;cb&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewCircuitBreaker&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Settings&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Name&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="s"&gt;"downstream"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Timeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// how long the breaker stays open before probing&lt;/span&gt;
    &lt;span class="n"&gt;ReadyToTrip&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Counts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Requests&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;
            &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TotalFailures&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;c&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Requests&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;cb&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Execute&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// perform the POST and return the status code&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Is&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;gobreaker&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ErrOpenState&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// don't burn a retry attempt; requeue with a delay or pause consumption&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Decide what workers do while the breaker is open. Failing fast without pausing consumption simply moves events from the queue into your retry or dead-letter path faster.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limiting.&lt;/strong&gt; Cap per-sender or per-tenant ingress and per-destination egress so one noisy source cannot consume the shared queue.&lt;/p&gt;

&lt;h2&gt;
  
  
  12. Conclusion
&lt;/h2&gt;

&lt;p&gt;A webhook proxy is resilient when every resource is bounded and every limit has an explicit failure behavior:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bounded body size&lt;/strong&gt; protects memory at the edge.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A fixed worker pool&lt;/strong&gt; caps concurrency toward the downstream, regardless of how slow it gets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A bounded, non-blocking queue&lt;/strong&gt; absorbs bursts and turns sustained overload into a fast, explicit &lt;code&gt;503&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A shared, tuned &lt;code&gt;http.Transport&lt;/code&gt;&lt;/strong&gt; with timeouts, connection caps and body draining prevents connection churn and hung workers.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ordered graceful shutdown&lt;/strong&gt; drains accepted work instead of dropping it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent consumers and durable queues&lt;/strong&gt; cover duplicates and crashes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of this makes failures impossible. It makes them bounded, visible and recoverable, which is what you want when someone else’s system is slow and your ingress tier still has to stay up.&lt;/p&gt;

&lt;h2&gt;
  
  
  Sources and Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;GitHub Docs: Best practices for using webhooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Stripe Docs: Receive Stripe events in your webhook endpoint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://shopify.dev/docs/apps/build/webhooks/troubleshoot" rel="noopener noreferrer"&gt;Shopify Docs: Troubleshoot webhooks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://api.slack.com/apis/connections/events-api#the-events-api__responding-to-events" rel="noopener noreferrer"&gt;Slack: Events API, responding to events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://golang.org/src/net/http/transport.go" rel="noopener noreferrer"&gt;Go source: &lt;code&gt;net/http&lt;/code&gt; transport defaults&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/blog/routing-enhancements" rel="noopener noreferrer"&gt;The Go Blog: Routing Enhancements for Go 1.22&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.dev/doc/go1.27" rel="noopener noreferrer"&gt;Go 1.27 Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.uber.com/blog/zero-growth-stack/" rel="noopener noreferrer"&gt;Uber Engineering: Zero-Growth Stack, Real Gains&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/github.com/sony/gobreaker/v2" rel="noopener noreferrer"&gt;&lt;code&gt;sony/gobreaker&lt;/code&gt; v2 on pkg.go.dev&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/building-a-resilient-webhook-ingress-proxy-in-go-handling-slow-consumers-without" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/building-a-resilient-webhook-ingress-proxy-in-go-handling-slow-consumers-without&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>High-Performance Ingress &amp; Network Architecture: Building Resilience at Scale</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Sat, 31 Oct 2026 02:15:30 +0000</pubDate>
      <link>https://dev.to/instawebhook/high-performance-ingress-network-architecture-building-resilience-at-scale-56lc</link>
      <guid>https://dev.to/instawebhook/high-performance-ingress-network-architecture-building-resilience-at-scale-56lc</guid>
      <description>&lt;p&gt;Webhook ingress gateways have to absorb bursty traffic from third-party senders and stay responsive when some of those connections are slow or hostile. This article covers three techniques for hardening such a gateway: using HTTP/3 and QUIC where they fit, defending against slow-connection attacks (Slowloris and Slow-Read), and using memory-mapped (&lt;code&gt;mmap&lt;/code&gt;) ring buffers to absorb bursts. It also states where each technique stops helping, because each one is often oversold.&lt;/p&gt;

&lt;p&gt;The article is aimed at teams who run their own ingress layer, whether they receive webhooks from providers such as Stripe or GitHub, or ingest events from their own devices and agents.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. Know the Contract Before Tuning the Network
&lt;/h2&gt;

&lt;p&gt;Network tuning only matters relative to what the sender expects from you. Two documented examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; expects a webhook endpoint to use HTTPS, accept POST requests with a JSON event payload, and quickly return a 2xx status. In live mode, Stripe attempts delivery of an event for up to three days with exponential backoff. Events created in a sandbox are retried three times over a few hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; expects a 2xx response within 10 seconds. If your server takes longer, GitHub terminates the connection and counts the delivery as failed. GitHub’s guidance is to enqueue the payload and process it in the background. GitHub does not automatically redeliver failed deliveries, so recovering from an outage means redelivering missed webhooks yourself once the server is back.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Deadlines and retry behavior vary by provider, so check each sender’s documentation rather than assuming a general standard. Two consequences follow for gateway design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The gateway’s main job is to acknowledge fast and persist durably.&lt;/strong&gt; Everything else (parsing, business logic, fan-out) belongs behind a queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your own endpoint must tolerate duplicates.&lt;/strong&gt; Retries and redeliveries mean the same event can arrive more than once, so deduplicate on an event or delivery ID.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  2. HTTP/3 and QUIC: What They Fix and What They Don’t
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where TCP-based HTTP hurts
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Handshake cost.&lt;/strong&gt; A new HTTPS connection over TCP needs a TCP handshake plus a TLS handshake before the first request byte, which is roughly two round trips with TLS 1.3 and more with TLS 1.2. QUIC integrates TLS 1.3 into its own handshake, so a new connection completes in one round trip. Resumed connections can send application data in the first flight (0-RTT).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport-level head-of-line (HoL) blocking.&lt;/strong&gt; TCP delivers a byte stream in order. When a segment is lost, data that arrived after it waits in the receive buffer until the retransmission arrives. HTTP/2 multiplexes many streams over one TCP connection, so one lost packet can stall every stream on that connection. HTTP/1.1 sidesteps this by using many connections, at the cost of more handshakes and sockets.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCP head-of-line blocking (HTTP/2 over one TCP connection):
Packet 1 [OK] -&amp;gt; Packet 2 [LOST] -&amp;gt; Packet 3 [HELD] -&amp;gt; Packet 4 [HELD]
                 Delivery to all streams stalls until Packet 2 is retransmitted
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What QUIC changes
&lt;/h3&gt;

&lt;p&gt;HTTP/3 (RFC 9114) runs over QUIC (RFC 9000), a UDP-based transport with TLS 1.3 built in. QUIC streams are independent at the transport layer, so loss on one stream does not block delivery on another.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;QUIC independent streams:
Stream A: frame [OK]   -&amp;gt; delivered immediately
Stream B: frame [LOST] -&amp;gt; retransmitted, only Stream B waits
Stream C: frame [OK]   -&amp;gt; delivered immediately
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two caveats matter for webhook gateways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;0-RTT data is replayable.&lt;/strong&gt; An attacker who captures a 0-RTT flight can replay it, so 0-RTT should only carry idempotent requests. The quic-go library makes this explicit: its client allows 0-RTT only for GET requests and notes that 0-RTT provides no replay protection. A webhook is a POST that triggers side effects, so for webhook endpoints leave 0-RTT off or make processing strictly idempotent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plain HTTP/3 already gives you stream independence.&lt;/strong&gt; You do not need datagrams to get the head-of-line benefit. Ordinary HTTP/3 request streams are reliable and independent.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Where HTTP/3 Datagrams fit
&lt;/h3&gt;

&lt;p&gt;QUIC DATAGRAM frames (RFC 9221) carry unreliable data. They are not retransmitted if lost, but unlike raw UDP they remain subject to the connection’s congestion control. HTTP/3 Datagrams (RFC 9297) map them onto HTTP requests. They are associated with a request stream, and in practice they are used with extended CONNECT, as in WebTransport and CONNECT-UDP (RFC 9298).&lt;/p&gt;

&lt;p&gt;That has direct consequences for webhook design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Standard webhook providers do not send datagrams.&lt;/strong&gt; They send HTTPS POST requests, and the documentation of the providers cited above specifies exactly that. Datagrams are relevant only when you control both ends, for example telemetry agents, IoT devices, or location trackers that you operate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datagrams suit data where the newest value supersedes older ones.&lt;/strong&gt; For a status heartbeat or a position update, dropping a stale value is better than delaying fresh ones behind a retransmission. They are a poor fit for anything that must not be lost, such as payment events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Datagrams are size-limited.&lt;/strong&gt; A datagram must fit in a single QUIC packet, so payloads are limited to roughly the path MTU minus protocol overhead. Larger payloads need ordinary streams.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Server sketch in Go (&lt;code&gt;quic-go&lt;/code&gt;)
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;quic-go&lt;/code&gt; HTTP/3 package supports HTTP Datagrams (RFC 9297) on both client and server, and the HTTP/3 API changed substantially across releases. For example, &lt;code&gt;Settingser&lt;/code&gt; was removed in v0.43.0, and &lt;code&gt;Settings.EnableDatagram&lt;/code&gt; was renamed &lt;code&gt;EnableDatagrams&lt;/code&gt;. At the time of writing, pkg.go.dev lists v0.61.0 as the latest release. Treat the following as a sketch and verify the types against the version you pin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"log"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/quic-go/quic-go"&lt;/span&gt;
    &lt;span class="s"&gt;"github.com/quic-go/quic-go/http3"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Bounded hand-off to the persistence layer. When it is full, datagrams&lt;/span&gt;
&lt;span class="c"&gt;// are dropped, which is acceptable for supersedable telemetry.&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ingest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;chan&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;4096&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;datagramHandler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c"&gt;// HTTP Datagrams ride on an HTTP request stream, typically extended CONNECT.&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Method&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MethodConnect&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"CONNECT required"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusMethodNotAllowed&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusOK&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c"&gt;// The ResponseWriter implements http3.HTTPStreamer, which gives access&lt;/span&gt;
    &lt;span class="c"&gt;// to the underlying request stream and its datagram methods.&lt;/span&gt;
    &lt;span class="n"&gt;str&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPStreamer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HTTPStream&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;str&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReceiveDatagram&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Context&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="c"&gt;// connection closed or request canceled&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;ingest&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;-&lt;/span&gt; &lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="c"&gt;// queue full: drop rather than spawn unbounded goroutines&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;server&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;http3&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;            &lt;span class="s"&gt;":4433"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;EnableDatagrams&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;QUICConfig&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;quic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;EnableDatagrams&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;         &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;datagramHandler&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fatal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;server&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ListenAndServeTLS&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"cert.pem"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"key.pem"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Note the bounded channel: spawning a goroutine per datagram, as many examples do, lets an attacker or a traffic spike create unbounded memory growth.&lt;/p&gt;

&lt;p&gt;QUIC also has its own amplification and address-validation concerns, so the usual UDP-facing protections (rate limiting, retry tokens, and a fronting proxy or CDN that terminates QUIC) apply to any public HTTP/3 listener.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. Preventing Slowloris and Slow-Read Attacks
&lt;/h2&gt;

&lt;p&gt;Webhook endpoints are public by necessity, which makes them targets for application-layer “low-and-slow” attacks that exhaust connection capacity rather than bandwidth.&lt;/p&gt;

&lt;h3&gt;
  
  
  The attack patterns
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slowloris&lt;/strong&gt; (popularized by Robert Hansen in 2009). The attacker opens many connections and sends partial HTTP request headers, adding a header line every few seconds to keep each connection alive. If the server dedicates a thread, process, or fixed connection slot to each connection, the pool fills and legitimate requests are refused.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow request body.&lt;/strong&gt; The attacker sends valid headers, then trickles the body (including chunked bodies) at a few bytes per interval.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Slow-Read.&lt;/strong&gt; The attacker sends a valid request but advertises a very small TCP receive window, so the server can only transmit its response in tiny segments. Response buffers and connection state stay allocated for a long time.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Slowloris lifecycle:
Client --[ POST /webhook HTTP/1.1 ]--------&amp;gt; Server allocates connection state
Client --[ ...wait... one more header ]-----&amp;gt; connection stays open
Client --[ ...wait... one more header ]-----&amp;gt; connection stays open
Result: connection slots exhausted, legitimate senders refused
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Event-driven servers such as NGINX handle idle connections cheaply, so thread-per-connection servers are the most exposed. Even so, every server has a finite number of connections and file descriptors, so timeouts and limits still matter.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key principle: most timeouts measure gaps, not totals
&lt;/h3&gt;

&lt;p&gt;A common mistake is assuming &lt;code&gt;client_header_timeout&lt;/code&gt;-style settings bound the total time to receive a request. In NGINX, &lt;code&gt;client_header_timeout&lt;/code&gt;, &lt;code&gt;client_body_timeout&lt;/code&gt;, and &lt;code&gt;send_timeout&lt;/code&gt; apply to the interval between two successive read or write operations, not to the whole transfer. An attacker who sends one byte every few seconds can therefore stay under a gap-based timeout indefinitely. Effective defense combines several layers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gap timeouts (so completely idle connections are dropped quickly),&lt;/li&gt;
&lt;li&gt;caps on header and body size,&lt;/li&gt;
&lt;li&gt;per-IP or per-client connection limits,&lt;/li&gt;
&lt;li&gt;an absolute deadline for receiving the request head, where the server supports one,&lt;/li&gt;
&lt;li&gt;and a front proxy or CDN that absorbs the slow clients.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NGINX has no directive for a minimum transfer rate. If you need rate-based eviction, you must implement it in your own server code or rely on a CDN or WAF feature.&lt;/p&gt;

&lt;h3&gt;
  
  
  NGINX example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;http&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;# Timeouts between successive reads/writes, not whole-request limits&lt;/span&gt;
    &lt;span class="kn"&gt;client_header_timeout&lt;/span&gt; &lt;span class="s"&gt;5s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;client_body_timeout&lt;/span&gt;   &lt;span class="s"&gt;10s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;send_timeout&lt;/span&gt;          &lt;span class="s"&gt;10s&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Drop timed-out connections immediately instead of lingering&lt;/span&gt;
    &lt;span class="kn"&gt;reset_timedout_connection&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Bound request size; webhooks are usually small&lt;/span&gt;
    &lt;span class="kn"&gt;client_max_body_size&lt;/span&gt; &lt;span class="mi"&gt;2m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Per-IP concurrent connection cap&lt;/span&gt;
    &lt;span class="kn"&gt;limit_conn_zone&lt;/span&gt; &lt;span class="nv"&gt;$binary_remote_addr&lt;/span&gt; &lt;span class="s"&gt;zone=per_ip:10m&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;http2&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;# the "http2" parameter of listen is deprecated since 1.25.1&lt;/span&gt;
        &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;ingress.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;limit_conn&lt;/span&gt; &lt;span class="s"&gt;per_ip&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/api/v1/webhooks&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="c1"&gt;# NGINX buffers the request body by default, so a slow client&lt;/span&gt;
            &lt;span class="c1"&gt;# does not hold an upstream connection while it trickles data.&lt;/span&gt;
            &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://internal_webhook_cluster&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two practical notes. First, the &lt;code&gt;http2&lt;/code&gt; directive was introduced in NGINX 1.25.1, which is why &lt;code&gt;listen 443 ssl http2;&lt;/code&gt; is now deprecated. Second, per-IP limits can punish legitimate senders: providers often deliver from a small set of shared IP ranges, so give published sender ranges (such as those GitHub exposes through its &lt;code&gt;/meta&lt;/code&gt; endpoint) higher limits or exempt them, and apply the strictest limits to everyone else.&lt;/p&gt;

&lt;h3&gt;
  
  
  Go &lt;code&gt;net/http&lt;/code&gt; example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="n"&gt;srv&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Server&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;Addr&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;              &lt;span class="s"&gt;":8443"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;ReadHeaderTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="m"&gt;4&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;  &lt;span class="c"&gt;// absolute deadline for request headers&lt;/span&gt;
    &lt;span class="n"&gt;ReadTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c"&gt;// absolute deadline for the whole request&lt;/span&gt;
    &lt;span class="n"&gt;WriteTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;      &lt;span class="m"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;IdleTimeout&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;       &lt;span class="m"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Second&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;MaxHeaderBytes&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="m"&gt;16&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// In the handler, also cap the body size:&lt;/span&gt;
&lt;span class="c"&gt;// r.Body = http.MaxBytesReader(w, r.Body, 2&amp;lt;&amp;lt;20)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Go’s server timeouts are absolute, per-request deadlines, which is stricter than NGINX’s gap-based timeouts. The trade-off is that a fixed &lt;code&gt;ReadTimeout&lt;/code&gt; can cut off a large but legitimate upload on a slow link. If that matters, use &lt;code&gt;http.ResponseController&lt;/code&gt; to extend the read deadline as data arrives.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rust (Actix Web) example
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;actix_web&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;web&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;App&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;handle_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;HttpResponse&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Persist or enqueue the payload, then acknowledge quickly.&lt;/span&gt;
    &lt;span class="nn"&gt;HttpResponse&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.finish&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[actix_web::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nn"&gt;HttpServer&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(||&lt;/span&gt; &lt;span class="nn"&gt;App&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.service&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;web&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;resource&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/webhook"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.to&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;handle_webhook&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="c1"&gt;// Time allowed for the client to send the full request head.&lt;/span&gt;
        &lt;span class="c1"&gt;// Slow clients receive 408 Request Timeout. Default: 5 seconds.&lt;/span&gt;
        &lt;span class="nf"&gt;.client_request_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="c1"&gt;// Time allowed for connection shutdown. Default: 1 second.&lt;/span&gt;
        &lt;span class="nf"&gt;.client_disconnect_timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.bind&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;
        &lt;span class="nf"&gt;.run&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;.await&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;client_request_timeout&lt;/code&gt; covers only reading the request head, and &lt;code&gt;client_disconnect_timeout&lt;/code&gt; governs connection shutdown; neither is a minimum-throughput control. Enforce body size limits and body-read deadlines separately.&lt;/p&gt;

&lt;h3&gt;
  
  
  Related protocol-level attacks
&lt;/h3&gt;

&lt;p&gt;HTTP/2 introduced its own resource-exhaustion vector: the “Rapid Reset” attack (CVE-2023-44487), disclosed in October 2023, abuses stream creation and immediate cancellation to overload servers. Keep your HTTP/2 implementation and proxies patched and configure concurrent-stream limits. Slow-connection defenses and HTTP/2 hardening are separate concerns.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Surviving Traffic Bursts: Durable Buffering with &lt;code&gt;mmap&lt;/code&gt;
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The problem
&lt;/h3&gt;

&lt;p&gt;Bursts of webhooks happen when an upstream system emits many events at once, such as a batch job, a retry storm after your own outage, or a bulk operation. If the ingress process holds every in-flight payload in heap memory (Go slices, Rust &lt;code&gt;Vec&amp;lt;u8&amp;gt;&lt;/code&gt;), memory grows with the burst. In a container, exceeding the memory limit gets the process killed by the kernel’s OOM killer and in-flight data is lost.&lt;/p&gt;

&lt;p&gt;The most reliable defense is architectural, not a buffer trick:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Cap request body sizes and concurrent in-flight requests.&lt;/li&gt;
&lt;li&gt;Apply backpressure: return &lt;code&gt;429&lt;/code&gt; or &lt;code&gt;503&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt; when capacity is exhausted. Providers that retry, such as Stripe, will redeliver later.&lt;/li&gt;
&lt;li&gt;Acknowledge only after the payload is durably stored, so an acknowledged event is never lost.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What an &lt;code&gt;mmap&lt;/code&gt; ring buffer gives you
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;mmap&lt;/code&gt; maps a file into the process’s address space. Writes go to kernel page-cache pages that the kernel writes back to disk in the background. A fixed-size file used as a circular buffer gives you a bounded, disk-backed staging area with these properties:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Bounded, preallocated capacity.&lt;/strong&gt; The buffer cannot grow past the file size. When it is full you reject new work explicitly instead of crashing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Survives process crashes.&lt;/strong&gt; Pages written through a &lt;code&gt;MAP_SHARED&lt;/code&gt; mapping belong to the kernel’s page cache, so data written before the process dies is not lost when the process is killed. It is not durable against power loss or a kernel crash unless you flush with &lt;code&gt;msync&lt;/code&gt; or &lt;code&gt;fsync&lt;/code&gt;. Each flush costs latency, so choose a flush policy deliberately.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Less heap pressure.&lt;/strong&gt; Payload bytes live in file-backed pages rather than the Go or Rust heap, which relieves garbage-collector and allocator pressure.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What it does not give you
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is not “zero OOM.”&lt;/strong&gt; On Linux, page cache and file-backed memory are charged to the container’s memory cgroup. Kubernetes’ working-set metric, which is what the OOM decision tracks, includes active file-backed memory. The kernel can reclaim clean pages and write back dirty ones, but under heavy write load into a tight limit, dirty pages can cause memory-cgroup OOM kills. Test your configuration under load, tune dirty-page writeback, and size limits with headroom.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is not zero-copy.&lt;/strong&gt; Copying the payload from the network buffer into the mapping is still a memory copy. The savings are in allocation and GC behavior, not in copy count.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It needs a defined recovery protocol.&lt;/strong&gt; After a crash, the consumer must be able to find valid records. Production designs add record checksums or sequence numbers and flush ordering rules.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A correct single-producer, single-consumer ring in Go
&lt;/h3&gt;

&lt;p&gt;Head and tail are monotonically increasing byte counters, with the position in the buffer computed modulo capacity. Records are length-prefixed and may wrap around the end of the region, which the copy helpers handle. This version supports exactly one producer goroutine and one consumer goroutine; with multiple producers, serialize writes with a mutex or give each producer its own ring.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;ringbuf&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/binary"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"fmt"&lt;/span&gt;
    &lt;span class="s"&gt;"os"&lt;/span&gt;
    &lt;span class="s"&gt;"sync/atomic"&lt;/span&gt;
    &lt;span class="s"&gt;"syscall"&lt;/span&gt;
    &lt;span class="s"&gt;"unsafe"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;headerSize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt; &lt;span class="c"&gt;// head at offset 0, tail at offset 8, rest reserved&lt;/span&gt;
    &lt;span class="n"&gt;lenPrefix&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;8&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;ErrFull&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ring buffer full"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrEmpty&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;errors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"ring buffer empty"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Ring&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;file&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;File&lt;/span&gt;
    &lt;span class="n"&gt;mem&lt;/span&gt;    &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// whole mapping, including header&lt;/span&gt;
    &lt;span class="n"&gt;region&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt; &lt;span class="c"&gt;// data area&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
    &lt;span class="n"&gt;tail&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Open maps a file-backed ring of the given total size. Reopening an&lt;/span&gt;
&lt;span class="c"&gt;// existing file of the same size preserves its contents and positions.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt; &lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;OpenFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_RDWR&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;O_CREATE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0o600&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Truncate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Mmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROT_READ&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PROT_WRITE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MAP_SHARED&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Errorf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"mmap failed: %w"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;    &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;headerSize&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
        &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;   &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;8&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Write appends one record. It returns ErrFull instead of overwriting&lt;/span&gt;
&lt;span class="c"&gt;// unread data, so the caller can apply backpressure (for example HTTP 503).&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrFull&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PutUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StoreUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;need&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// publish only after data is written&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Read removes and returns the oldest record.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LoadUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tail&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;tail&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ErrEmpty&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;binary&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LittleEndian&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hdr&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="n"&gt;out&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;copyOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;atomic&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StoreUint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;head&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;lenPrefix&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;out&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;copyIn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;src&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="c"&gt;// wrap around&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;copyOut&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dst&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;off&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pos&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;off&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="nb"&gt;copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dst&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;region&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c"&gt;// wrap around&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Sync flushes dirty pages to disk. Call it on your chosen durability schedule.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Sync&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;errno&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Syscall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SYS_MSYNC&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;unsafe&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pointer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])),&lt;/span&gt; &lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="kt"&gt;uintptr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MS_SYNC&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;errno&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;errno&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Ring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="kt"&gt;error&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;syscall&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Munmap&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mem&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This sketch uses Linux-specific system calls and omits checksums, corruption recovery, and multi-producer support. Treat it as a starting point rather than production code; mature alternatives such as a write-ahead log library or an embedded queue are often a better choice than writing your own.&lt;/p&gt;

&lt;h3&gt;
  
  
  Choosing a buffering strategy
&lt;/h3&gt;

&lt;p&gt;I deliberately omit latency figures for the options below, because they depend heavily on hardware, payload size, and configuration. Benchmark on your own infrastructure.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Memory behavior&lt;/th&gt;
&lt;th&gt;Durability&lt;/th&gt;
&lt;th&gt;Main risk&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In-process heap queue&lt;/td&gt;
&lt;td&gt;Grows with the burst&lt;/td&gt;
&lt;td&gt;Lost on process crash&lt;/td&gt;
&lt;td&gt;OOM kill under bursts&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Remote queue or broker (Redis, Kafka, RabbitMQ)&lt;/td&gt;
&lt;td&gt;Offloaded to a separate system&lt;/td&gt;
&lt;td&gt;Depends on broker configuration&lt;/td&gt;
&lt;td&gt;Network hop and an extra dependency on the ingest path&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;mmap&lt;/code&gt;-backed ring on local disk&lt;/td&gt;
&lt;td&gt;Bounded file size; page cache counts toward cgroup limits&lt;/td&gt;
&lt;td&gt;Survives process crash; power loss needs &lt;code&gt;msync&lt;/code&gt;/&lt;code&gt;fsync&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Dirty-page pressure and a custom recovery protocol&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  5. Putting It Together
&lt;/h2&gt;

&lt;p&gt;A defensible layered design looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Public senders
      │
      ▼
┌──────────────────────────────────────────────────────┐
│ Edge layer: CDN / reverse proxy (NGINX, Envoy, etc.) │
│  • Header/body gap timeouts, size caps, conn limits  │
│  • Terminates TLS (and HTTP/2, optionally HTTP/3)    │
│  • Buffers slow clients away from your application   │
└───────────────────────────┬──────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────┐
│ Ingress worker (Go / Rust)                           │
│  • Verify signature, enforce body limit              │
│  • Append to durable buffer; ack only after success  │
│  • Return 503 + Retry-After when the buffer is full  │
└───────────────────────────┬──────────────────────────┘
                            ▼
┌──────────────────────────────────────────────────────┐
│ Durable buffer (mmap ring, WAL, or message broker)   │
└───────────────────────────┬──────────────────────────┘
                            ▼
              Async consumers: dedupe, process, route
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The priorities, in order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge fast, process later.&lt;/strong&gt; Providers publish deadlines (10 seconds for GitHub, for example), and handlers that do real work inline miss them and trigger retries.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bound everything.&lt;/strong&gt; Connection counts, header and body sizes, queue depth, and buffer capacity all need explicit limits, with backpressure when they are reached.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use HTTP/3 where you control the clients and it measurably helps.&lt;/strong&gt; For third-party webhook providers, expect HTTPS POST over HTTP/1.1 or HTTP/2 and design for that. Reserve datagrams for loss-tolerant, supersedable telemetry from your own clients.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make duplicates harmless.&lt;/strong&gt; Retries and redelivery are part of every provider’s delivery model, so idempotent processing is mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load-test the failure modes.&lt;/strong&gt; Simulate slow clients, bursts at several times your normal peak, and a process kill mid-write, and watch the container’s memory working set, not just the process RSS.&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RFC 9000 (QUIC), RFC 9114 (HTTP/3), RFC 9221 (QUIC DATAGRAM), RFC 9297 (HTTP Datagrams), RFC 9298 (CONNECT-UDP), at &lt;a href="https://www.rfc-editor.org" rel="noopener noreferrer"&gt;rfc-editor.org&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stripe: &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Receive Stripe events in your webhook endpoint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;Best practices for using webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;quic-go: &lt;a href="https://pkg.go.dev/github.com/quic-go/quic-go/http3" rel="noopener noreferrer"&gt;HTTP/3 package documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NGINX: &lt;a href="https://nginx.org/en/docs/http/ngx_http_v2_module.html" rel="noopener noreferrer"&gt;ngx_http_v2_module&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/high-performance-ingress-network-architecture-building-resilience-at-scale" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/high-performance-ingress-network-architecture-building-resilience-at-scale&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Deterministic Reordering of Concurrent Webhooks: Handling Out-of-Order State Transitions with CRDTs</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Fri, 30 Oct 2026 02:13:23 +0000</pubDate>
      <link>https://dev.to/instawebhook/deterministic-reordering-of-concurrent-webhooks-handling-out-of-order-state-transitions-with-crdts-flf</link>
      <guid>https://dev.to/instawebhook/deterministic-reordering-of-concurrent-webhooks-handling-out-of-order-state-transitions-with-crdts-flf</guid>
      <description>&lt;h2&gt;
  
  
  Introduction: The Multi-Region Webhook Delivery Dilemma
&lt;/h2&gt;

&lt;p&gt;Webhooks are the default way for SaaS providers to push state changes to your systems. Once you accept them through several regional ingress points, you inherit a hard problem: the order in which events arrive is not the order in which they happened.&lt;/p&gt;

&lt;p&gt;Providers say this openly. Stripe states that it does not guarantee events arrive in the order they were generated, and that the same event can be delivered more than once. In live mode it retries failed deliveries with exponential backoff for up to three days. Its own example is a subscription creation that can emit &lt;code&gt;customer.subscription.created&lt;/code&gt;, &lt;code&gt;invoice.created&lt;/code&gt;, &lt;code&gt;invoice.paid&lt;/code&gt; and &lt;code&gt;charge.created&lt;/code&gt;, with no promise about their arrival order. Shopify and GitHub give similar warnings. GitHub says deliveries can arrive in a different order than the events occurred. It also does not automatically redeliver failed deliveries, so you must redeliver them manually. Analyses of dozens of webhook platforms find the same pattern: most providers disclaim ordering without supplying the metadata a consumer needs to rebuild it.&lt;/p&gt;

&lt;p&gt;Deploying ingress in several regions adds more reordering. Different network paths, queue workers that finish jobs in a different order than they started them, and retries all interleave events for the same resource.&lt;/p&gt;

&lt;p&gt;Retries also mean you cannot get exactly-once delivery. If a sender never retries, it loses data when a delivery fails. If it does retry, it will occasionally duplicate events that had already arrived. The practical target is therefore &lt;strong&gt;at-least-once delivery plus an idempotent, order-insensitive consumer&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This article shows one way to build such a consumer. It combines &lt;strong&gt;Conflict-Free Replicated Data Types (CRDTs)&lt;/strong&gt; with logical timestamps and a deterministic tie-breaker, so that every replica converges to the same state no matter how events are interleaved. It also covers when this design is overkill.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Anatomy of Out-of-Order Webhook Failures
&lt;/h2&gt;

&lt;p&gt;HTTP itself gives you no delivery guarantee. Reliability comes from the provider’s retry logic, and that logic is the source of both duplicates and reordering.&lt;/p&gt;

&lt;h3&gt;
  
  
  Common Failure Modes in Multi-Region Ingress
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;The ghost update.&lt;/strong&gt; An &lt;code&gt;updated&lt;/code&gt; event arrives before the &lt;code&gt;created&lt;/code&gt; event it depends on. A handler that does a strict update fails. An upsert that fills in missing fields with defaults or nulls creates a half-populated record.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State reversal.&lt;/strong&gt; An older event is retried or delayed and arrives after a newer one has been committed. If the handler applies events in arrival order, it overwrites newer state with older state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrent mutation divergence.&lt;/strong&gt; Two regions ingest different events for the same resource at nearly the same time and apply them in different orders. Without a merge rule that gives the same result regardless of order, the replicas diverge permanently.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Provider emits:  E1 (created, t=1)   E2 (updated, t=2)

                 E1 ── slow route / retry ──────────► Region B ── applies 2nd
                 E2 ── fast route ───────► Region A ── applies 1st

Region A sees: E2, E1     Region B sees: E1, E2
Arrival-order handling → A and B end in different states
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A common first fix is Last-Write-Wins (LWW) on the payload’s wall-clock timestamp. That fix is only as good as the clocks behind it. If the timestamps come from machines with skewed clocks, or you compare them against your own server’s &lt;code&gt;Date.now()&lt;/code&gt;, “latest” is not reliable. Plain LWW also silently discards one side of a true concurrent write. LWW is not inherently broken, though. As shown below, an LWW register with logical timestamps and a deterministic tie-breaker is itself a valid CRDT.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Foundations: CRDTs and Strong Eventual Consistency
&lt;/h2&gt;

&lt;p&gt;CRDTs were formalized by Shapiro, Preguiça, Baquero and Zawirski in 2011. They defined &lt;strong&gt;Strong Eventual Consistency (SEC)&lt;/strong&gt; and gave sufficient conditions under which replicas of a data type are guaranteed to converge without rollback or coordination. SEC requires three things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Eventual delivery.&lt;/strong&gt; An update applied at one correct replica is eventually applied at all correct replicas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Termination.&lt;/strong&gt; Every update execution finishes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strong convergence.&lt;/strong&gt; Replicas that have applied the same set of updates are in equivalent states, regardless of order.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  State-based CRDTs (CvRDTs)
&lt;/h3&gt;

&lt;p&gt;A state-based CRDT models its state as a &lt;strong&gt;join-semilattice&lt;/strong&gt;. This is a partially ordered set in which every pair of states has a least upper bound, and the merge function computes that bound (⊔). The merge must satisfy three laws:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Commutative:&lt;/strong&gt; A ⊔ B = B ⊔ A, so arrival order does not matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Associative:&lt;/strong&gt; (A ⊔ B) ⊔ C = A ⊔ (B ⊔ C), so batching does not matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotent:&lt;/strong&gt; A ⊔ A = A, so duplicate deliveries are harmless.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Updates must also be &lt;strong&gt;monotonic&lt;/strong&gt;: a state may only move up the lattice. This is why a plain counter cannot support decrement in a naive state-based design, since merging by max would ignore it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Operation-based CRDTs (CmRDTs)
&lt;/h3&gt;

&lt;p&gt;Operation-based CRDTs send operations instead of state. They are compact on the wire, but they push guarantees onto the transport: each operation must be delivered exactly once, and in causal order where the data type requires it.&lt;/p&gt;

&lt;p&gt;That is the wrong fit for webhooks, which are at-least-once and unordered, and whose operation history you do not control. Webhook handlers should therefore treat each payload as a &lt;strong&gt;state fragment&lt;/strong&gt; and merge it with a state-based join. State-based merges tolerate duplicate and out-of-order delivery by construction.&lt;/p&gt;

&lt;h3&gt;
  
  
  Logical Clocks
&lt;/h3&gt;

&lt;p&gt;To order events without trusting physical clocks, use logical ones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lamport timestamps&lt;/strong&gt; (Lamport, 1978) guarantee that if event &lt;em&gt;a&lt;/em&gt; causally precedes &lt;em&gt;b&lt;/em&gt;, then L(a) &amp;lt; L(b). The converse does not hold, so they cannot tell you whether two events were concurrent.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vector clocks&lt;/strong&gt; (Fidge and Mattern, 1988) do capture causality, so concurrent events can be detected. They cost one counter per participant.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provider-supplied versions&lt;/strong&gt; are the best source of all when they exist: a monotonic sequence number per resource, an &lt;code&gt;updated_at&lt;/code&gt; or version field on the object, or an explicit parent event reference.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the provider supplies none of these, anything you stamp at the edge reflects arrival order at your edge, not the provider’s order. Edge stamping can still give you a consistent internal order for deduplication and replay. It cannot recover the provider’s true event order.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deterministic Tie-Breaking
&lt;/h3&gt;

&lt;p&gt;When two events carry the same logical timestamp, compare a fixed sequence of keys: &lt;code&gt;(logicalTimestamp, actorId, eventId)&lt;/code&gt;. Every replica applies the same comparison, so every replica picks the same winner. This works only if that tuple is unique per event. That is why the example below includes &lt;code&gt;eventId&lt;/code&gt; as the final key.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Architecture of a Deterministic Reordering Layer
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+--------------------------------------------------------------+
|                   Multi-Region Edge Ingress                  |
|   verify signature · return 2xx fast · persist raw event     |
+--------------------------------------------------------------+
                              │
                              ▼
+--------------------------------------------------------------+
|             Dedup + Metadata Extraction                      |
|   eventId · resourceId · logical timestamp / version · actor |
+--------------------------------------------------------------+
                              │
                              ▼
+--------------------------------------------------------------+
|                  CRDT State Resolution                       |
|   event → state fragment → join (⊔) with stored state        |
+--------------------------------------------------------------+
                              │
                              ▼
+--------------------------------------------------------------+
|        Visibility Gate (dependencies) + Reconciliation       |
|   hold side effects until `created` seen; TTL → API fetch    |
+--------------------------------------------------------------+
                              │
                              ▼
+--------------------------------------------------------------+
|                    Persisted State Storage                   |
+--------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Ingestion.&lt;/strong&gt; Verify the signature, acknowledge with a 2xx quickly, and process asynchronously. Providers enforce delivery timeouts. For example, Primer documents a 10-second timeout. A slow synchronous handler therefore causes retries, which cause duplicates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplication.&lt;/strong&gt; Store processed event IDs with a unique constraint. Stripe events carry a stable &lt;code&gt;id&lt;/code&gt; for this purpose. Deduplication is not the same as domain idempotency, though. A provider can emit two distinct events that describe the same business transition. The idempotent join below handles that case as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Join.&lt;/strong&gt; Convert the event into a state fragment and merge it into the stored state. Because the merge is commutative, associative and idempotent, arrival order is irrelevant to the final state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility gate.&lt;/strong&gt; The join converges even when &lt;code&gt;updated&lt;/code&gt; arrives before &lt;code&gt;created&lt;/code&gt;. What you still need to hold back are side effects and foreign-key-dependent writes. Keep the merged state internal until the creation event has been seen. If it never arrives within a time limit, fall back to fetching the resource from the provider’s API.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Reference Implementation (TypeScript)
&lt;/h2&gt;

&lt;p&gt;This sketch models a resource as a map of per-field LWW registers, plus a grow-only &lt;code&gt;created&lt;/code&gt; flag and a delete tombstone. It is not a drop-in production system.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;eventType&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;UPDATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;logicalTimestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;   &lt;span class="c1"&gt;// provider version/sequence, or a Lamport value&lt;/span&gt;
  &lt;span class="nl"&gt;actorId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// issuing node/region, used for tie-breaking&lt;/span&gt;
  &lt;span class="nl"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// per-field last-writer-wins registers&lt;/span&gt;
  &lt;span class="nl"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;           &lt;span class="c1"&gt;// grow-only flag: once true, stays true&lt;/span&gt;
  &lt;span class="nl"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;            &lt;span class="c1"&gt;// tombstone (delete-wins policy)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Total order on stamps: (ts, actor, eventId). Unique per event.&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;cmp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ts&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actor&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="c1"&gt;// The join (⊔): commutative, associative, idempotent.&lt;/span&gt;
&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;fa&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fa&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fb&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="kd"&gt;let&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nf"&gt;cmp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;deleted&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;toFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;Stamp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;logicalTimestamp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;actorId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;eventId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventId&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;Field&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;entries&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;v&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;created&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;CREATED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;eventType&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETED&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;stamp&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;WebhookReorderingEngine&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;states&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ResourceState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;private&lt;/span&gt; &lt;span class="nx"&gt;waitingSince&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nb"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// local TTL bookkeeping only&lt;/span&gt;

  &lt;span class="nf"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookEvent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()):&lt;/span&gt; &lt;span class="k"&gt;void&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;??&lt;/span&gt; &lt;span class="nx"&gt;EMPTY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;toFragment&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
    &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingSince&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingSince&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;has&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingSince&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;e&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Visible only after `created` is known and while not tombstoned.&lt;/span&gt;
  &lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;unknown&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;states&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;resourceId&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;created&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;deleted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;Object&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromEntries&lt;/span&gt;&lt;span class="p"&gt;([...&lt;/span&gt;&lt;span class="nx"&gt;s&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;]));&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// Resources stuck without a `created` event: reconcile via the provider's API.&lt;/span&gt;
  &lt;span class="nf"&gt;expiredWaiting&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;now&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ttlMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitingSince&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(([,&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="nx"&gt;since&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;ttlMs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(([&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few design notes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Date.now()&lt;/code&gt; appears only in the local TTL bookkeeping. It never influences event ordering.&lt;/li&gt;
&lt;li&gt;The version number is not stored in the state. A counter incremented on every applied event would differ across replicas that drop or reorder events differently, which breaks convergence.&lt;/li&gt;
&lt;li&gt;Merging per field means the result can combine fields from different events into a state the sender never actually had. If an invariant spans several fields, keep the whole entity in one register keyed by the provider’s version.&lt;/li&gt;
&lt;li&gt;Delete-wins is a policy choice. A later “recreate” would need a new incarnation ID in the key.&lt;/li&gt;
&lt;li&gt;State lives in memory here. In production it belongs in a durable store, and the merge must run atomically per resource, for example inside a transaction or compare-and-set.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Production Resilience Patterns
&lt;/h2&gt;

&lt;h3&gt;
  
  
  A. Unbounded buffers and missing events
&lt;/h3&gt;

&lt;p&gt;If a &lt;code&gt;created&lt;/code&gt; event is lost, for instance because the provider’s retries were exhausted, resources waiting for it accumulate. Bound the wait with a TTL, move the stuck events to a dead-letter queue, and reconcile by fetching the resource’s current state from the provider’s API. Choose the TTL against the provider’s behavior. Stripe retries over days, so a short TTL means you will usually reconcile by fetching rather than wait. Stripe’s List Events API only returns events from the last 30 days, so a lost event can only be backfilled within that window. Treat that as a hard limit when you plan recovery.&lt;/p&gt;

&lt;h3&gt;
  
  
  B. Clock drift and sequence sources
&lt;/h3&gt;

&lt;p&gt;Do not order by physical &lt;code&gt;Date.now()&lt;/code&gt; across regions. Prefer, in this order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A version or sequence that the provider supplies.&lt;/li&gt;
&lt;li&gt;A Lamport timestamp that your consumers maintain.&lt;/li&gt;
&lt;li&gt;An edge-assigned counter per resource. If you use one, note that Redis Cluster does not provide strong consistency. Its replication is asynchronous, so it can lose acknowledged writes during failovers. The &lt;code&gt;WAIT&lt;/code&gt; command reduces that risk but does not eliminate it. A counter that must never go backwards needs a store with consensus, such as Raft-based systems like etcd.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Remember that an edge-assigned counter records arrival order at your edge, not the provider’s order.&lt;/p&gt;

&lt;h3&gt;
  
  
  C. Tombstone garbage collection
&lt;/h3&gt;

&lt;p&gt;You cannot drop a tombstone arbitrarily. If it is pruned while a delayed &lt;code&gt;created&lt;/code&gt; or &lt;code&gt;updated&lt;/code&gt; event is still in flight, that event can resurrect the deleted resource. The CRDT literature handles this with causal stability: a tombstone is safe to remove only once every replica is known to have seen the delete. For webhooks, a practical rule is to keep tombstones longer than the provider’s maximum retry window plus your own replay window. Stripe retries for up to three days. Adyen queues events for up to 30 days. Hookdeck documents both. A fixed 72-hour window would therefore be too short for some providers, so make it configurable per provider.&lt;/p&gt;

&lt;h3&gt;
  
  
  D. When to skip the CRDT
&lt;/h3&gt;

&lt;p&gt;If the provider has an API that returns an authoritative current object, the simplest approach is often to treat the webhook as a notification. You fetch the object by ID and overwrite your local copy from that response. Many engineers recommend this for Stripe subscription, customer and invoice objects, at the cost of one extra API call per event. The CRDT approach is worth its complexity when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the payload is the only source of truth,&lt;/li&gt;
&lt;li&gt;the API is rate limited or slow,&lt;/li&gt;
&lt;li&gt;you need replicas in several regions to converge without calling the provider on each event.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary: Approaches Compared
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Strategy&lt;/th&gt;
&lt;th&gt;Out-of-order delivery&lt;/th&gt;
&lt;th&gt;Concurrent multi-region writes&lt;/th&gt;
&lt;th&gt;Duplicates&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apply in arrival order&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Overwrites newer data with older data&lt;/td&gt;
&lt;td&gt;Replicas diverge&lt;/td&gt;
&lt;td&gt;Unsafe without dedup&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LWW on physical timestamps&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Correct only if clocks are reliable&lt;/td&gt;
&lt;td&gt;Subject to clock skew; one concurrent write is silently lost&lt;/td&gt;
&lt;td&gt;Safe if writes are idempotent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;LWW/CRDT with logical stamps + tie-breaker&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Converges regardless of arrival order&lt;/td&gt;
&lt;td&gt;Deterministic winner at every replica&lt;/td&gt;
&lt;td&gt;Idempotent join, so safe&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Fetch current state on notification&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Not affected by order&lt;/td&gt;
&lt;td&gt;Not affected by order&lt;/td&gt;
&lt;td&gt;Safe, but costs an API call per event&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Webhook ordering is not something to rely on. Major providers document that it is not guaranteed, and retries guarantee that duplicates will happen. A robust consumer acknowledges quickly, deduplicates on a stable event ID, and merges each event into state with a function that is commutative, associative and idempotent. A fixed tie-breaker keeps every replica’s choice identical, and logical clocks or provider versions replace unreliable wall-clock time. Where the provider offers an authoritative API, combine that approach with reconciliation by fetch. Together these give you convergence in every region, even when the network interleaves events differently at each one.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Shapiro, Preguiça, Baquero, Zawirski, “A Comprehensive Study of Convergent and Commutative Replicated Data Types,” INRIA RR-7506, 2011: &lt;a href="https://inria.hal.science/inria-00555588/document" rel="noopener noreferrer"&gt;https://inria.hal.science/inria-00555588/document&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Shapiro et al., “Conflict-free Replicated Data Types,” SSS 2011: &lt;a href="https://www.cs.tufts.edu/%7Enr/cs257/archive/marc-shapiro/CRDTs_SSS-2011.pdf" rel="noopener noreferrer"&gt;https://www.cs.tufts.edu/~nr/cs257/archive/marc-shapiro/CRDTs_SSS-2011.pdf&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Lamport, “Time, Clocks, and the Ordering of Events in a Distributed System,” CACM, 1978&lt;/li&gt;
&lt;li&gt;Fidge, “Timestamps in Message-Passing Systems That Preserve the Partial Ordering,” 1988; Mattern, “Virtual Time and Global States of Distributed Systems,” 1988&lt;/li&gt;
&lt;li&gt;Stripe webhook behavior (retries, ordering, duplicates): &lt;a href="https://dev.to/shannonllc/stripe-webhooks-retries-ordering-duplicates-timeouts-and-ips-46f0"&gt;https://dev.to/shannonllc/stripe-webhooks-retries-ordering-duplicates-timeouts-and-ips-46f0&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hookdeck, “Common Mistakes with Outbound Webhooks”: &lt;a href="https://hookdeck.com/outpost/guides/common-outbound-webhook-mistakes.md" rel="noopener noreferrer"&gt;https://hookdeck.com/outpost/guides/common-outbound-webhook-mistakes.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hookdeck, “At-Least-Once vs. Exactly-Once Webhook Delivery Guarantees”: &lt;a href="https://hookdeck.com/webhooks/guides/webhook-delivery-guarantees.md" rel="noopener noreferrer"&gt;https://hookdeck.com/webhooks/guides/webhook-delivery-guarantees.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Provider ordering summary (Stripe, Shopify, GitHub): &lt;a href="https://www.vincentschmalbach.com/why-webhook-events-arrive-out-of-order/" rel="noopener noreferrer"&gt;https://www.vincentschmalbach.com/why-webhook-events-arrive-out-of-order/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Primer webhook documentation: &lt;a href="https://primer.io/docs/api-reference/get-started/configure-webhooks" rel="noopener noreferrer"&gt;https://primer.io/docs/api-reference/get-started/configure-webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Redis Cluster consistency guarantees: &lt;a href="https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/" rel="noopener noreferrer"&gt;https://redis.io/docs/latest/operate/oss_and_stack/management/scaling/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/deterministic-reordering-of-concurrent-webhooks-handling-out-of-order-state-tran" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/deterministic-reordering-of-concurrent-webhooks-handling-out-of-order-state-tran&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>High-Velocity Webhook Engineering: Low-Level Ingress, Dynamic Flow Control, and Multi-Tenant Isolation</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Thu, 29 Oct 2026 02:07:54 +0000</pubDate>
      <link>https://dev.to/instawebhook/high-velocity-webhook-engineering-low-level-ingress-dynamic-flow-control-and-multi-tenant-5g38</link>
      <guid>https://dev.to/instawebhook/high-velocity-webhook-engineering-low-level-ingress-dynamic-flow-control-and-multi-tenant-5g38</guid>
      <description>&lt;p&gt;Webhook traffic is bursty by nature. A payment provider, a Git host, or a SaaS platform can fire a large batch of events at your endpoint at once. When a router fans those events out to many downstream customers, three problems show up: the cost of network I/O at the edge, one slow customer stalling everyone else, and noisy senders consuming shared capacity.&lt;/p&gt;

&lt;p&gt;This guide covers three building blocks of a resilient multi-tenant webhook router:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Efficient ingress with Linux &lt;code&gt;io_uring&lt;/code&gt;&lt;/strong&gt;: multishot receive and provided buffer rings, plus the kernel’s separate zero-copy receive feature.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic flow control&lt;/strong&gt;: bounded per-tenant queues, credit-style backpressure, and load shedding to stop “slow consumer” cascades.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant rate limiting&lt;/strong&gt;: an atomic token bucket in Redis (Lua), with notes on Redis Cluster.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One caveat applies throughout. Many webhook workloads are limited by TLS termination, HTTP parsing, signature verification, and downstream I/O rather than by &lt;code&gt;recv()&lt;/code&gt; calls. Profile before you adopt anything in section 1.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Ingress with &lt;code&gt;io_uring&lt;/code&gt;: Multishot Receive and Provided Buffers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Where the standard epoll model spends its time
&lt;/h3&gt;

&lt;p&gt;Most high-performance Linux network services use readiness-based event loops. Tokio, for example, runs on epoll through the &lt;code&gt;mio&lt;/code&gt; library. Epoll tells you a socket is &lt;em&gt;ready&lt;/em&gt;, and your code then issues the actual system call. Reading one chunk of a webhook request looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The NIC DMA-copies the packet into kernel memory, and the kernel’s network stack processes it and queues the payload on the socket.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;epoll_wait()&lt;/code&gt; returns, telling userspace the socket is readable.&lt;/li&gt;
&lt;li&gt;Userspace calls &lt;code&gt;read()&lt;/code&gt; or &lt;code&gt;recv()&lt;/code&gt;, which crosses the user/kernel boundary.&lt;/li&gt;
&lt;li&gt;The kernel copies the payload from the socket buffer into the application’s buffer.&lt;/li&gt;
&lt;li&gt;The application parses and processes the bytes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each step is cheap. At very high request rates, though, per-call overhead and the kernel-to-user copy become measurable. Gains from &lt;code&gt;io_uring&lt;/code&gt; vary widely with workload, kernel version, mitigations, and batching, so benchmark your own stack instead of relying on generic figures.&lt;/p&gt;

&lt;h3&gt;
  
  
  What &lt;code&gt;io_uring&lt;/code&gt; changes
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; is a Linux asynchronous I/O interface built around two ring buffers shared between the kernel and the application: a submission queue (SQ) and a completion queue (CQ). Instead of waiting for readiness and then making a call, the application submits operations and later reaps completions. Many operations can be submitted and completed with a single &lt;code&gt;io_uring_enter()&lt;/code&gt; call.&lt;/p&gt;

&lt;p&gt;Two features matter most for webhook ingestion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Multishot receive.&lt;/strong&gt; The kernel’s &lt;code&gt;io_uring_prep_recv&lt;/code&gt; documentation describes a multishot variant in which one receive request repeatedly posts completions as data arrives, instead of needing a new submission per receive. Multishot receive is available since kernel 6.0.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Provided buffer rings.&lt;/strong&gt; The application registers a pool of buffers with the kernel, and each completion consumes one buffer from the pool. The completion reports which buffer was used. The application hands buffers back to the ring when it has finished with them. This allows memory to be allocated per-pool rather than per-connection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  A correction on “zero-copy”
&lt;/h3&gt;

&lt;p&gt;Multishot receive with provided buffers is &lt;strong&gt;not zero-copy&lt;/strong&gt;. The kernel still receives packets into its own memory, runs the TCP stack, and copies the payload into your provided buffer. What you save is submission overhead and per-connection buffer pinning.&lt;/p&gt;

&lt;p&gt;True zero-copy receive is a separate feature, &lt;strong&gt;io_uring zero-copy Rx (zcrx)&lt;/strong&gt;, added in Linux 6.15. The kernel documentation says it removes the kernel-to-user copy on the receive path, so payload data lands directly in userspace memory. Unlike full kernel-bypass stacks such as DPDK, packet headers are still processed by the kernel TCP stack. It also has strict hardware requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The NIC must support &lt;strong&gt;header/data split&lt;/strong&gt;, so headers go to kernel memory and payloads go to userspace memory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flow steering&lt;/strong&gt; is needed to direct only the intended flows to the zero-copy queues.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;RSS&lt;/strong&gt; is needed to steer all other traffic away from those queues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The kernel API does not configure the NIC for you; you have to set it up yourself. For a typical webhook gateway behind a load balancer on cloud VMs, zcrx may simply be unavailable. Multishot receive with provided buffers is the more practical starting point.&lt;/p&gt;

&lt;h3&gt;
  
  
  How the data flows
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; Userspace                                   Kernel
 ┌──────────────────────┐   shared rings    ┌──────────────────────────┐
 │ Submission queue (SQ)│ ───────────────►  │ Executes multishot recv  │
 │ Completion queue (CQ)│ ◄───────────────  │ on the socket            │
 │ Provided buffer ring │ ◄── payload ───── │ Copies payload from the  │
 └──────────────────────┘    copied into    │ socket buffer into a     │
                             a free buffer  │ provided buffer          │
                                            └────────────▲─────────────┘
                                                         │
                                        NIC → kernel network stack (TCP)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  A Rust sketch
&lt;/h3&gt;

&lt;p&gt;Rust has several &lt;code&gt;io_uring&lt;/code&gt; options. The &lt;code&gt;io-uring&lt;/code&gt; crate from the tokio-rs project is a low-level binding, and several runtimes (for example &lt;code&gt;tokio-uring&lt;/code&gt;, &lt;code&gt;monoio&lt;/code&gt;, and &lt;code&gt;glommio&lt;/code&gt;) build on or implement &lt;code&gt;io_uring&lt;/code&gt;. Tokio’s main runtime itself uses epoll.&lt;/p&gt;

&lt;p&gt;The sketch below submits a multishot receive and handles completions. It is simplified: it omits buffer-ring registration and error handling, and the exact API differs between crate versions, so check the docs for the version you use. Buffer rings are registered through the &lt;code&gt;Submitter&lt;/code&gt;, for example with &lt;code&gt;register_buf_ring_with_flags&lt;/code&gt;, and require careful unsafe memory management.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;io_uring&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;cqueue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;opcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IoUring&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;fd&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;RawFd&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;BUF_GROUP&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u16&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// buffer group id of a registered provided buffer ring&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;submit_multishot_recv&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;IoUring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;conn_fd&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RawFd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// One submission; the kernel posts a completion each time data arrives.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sqe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;opcode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;RecvMulti&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;types&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn_fd&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;BUF_GROUP&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.user_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;conn_fd&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.submission&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sqe&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;other&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"submission queue full"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.submit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;reap_completions&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;IoUring&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;cqe&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.completion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="nf"&gt;.result&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt; &lt;span class="c1"&gt;// number of bytes received, or a negative errno&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;buf_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;cqueue&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;buffer_select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="nf"&gt;.flags&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt; &lt;span class="c1"&gt;// which provided buffer was filled&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;multishot_still_armed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;cqueue&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;more&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="nf"&gt;.flags&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;

        &lt;span class="c1"&gt;// 1. If bytes &amp;gt; 0 and buf_id is Some, read the payload from that buffer,&lt;/span&gt;
        &lt;span class="c1"&gt;//    then return the buffer to the ring.&lt;/span&gt;
        &lt;span class="c1"&gt;// 2. If the multishot request is no longer armed (no MORE flag),&lt;/span&gt;
        &lt;span class="c1"&gt;//    re-submit it for this connection.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;buf_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;multishot_still_armed&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Security and operational caveats
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; has a large kernel attack surface. In June 2023, Google’s security team reported that 60% of the Linux kernel exploits submitted to its bug bounty program in 2022 involved &lt;code&gt;io_uring&lt;/code&gt;. As a result, Google disabled it for Android apps, on ChromeOS, and on its servers. Docker also removed it from its default seccomp profile, which means &lt;code&gt;io_uring&lt;/code&gt; calls are blocked by default in many container environments. Check your runtime’s seccomp policy before assuming it will work in production. Administrators can also restrict it system-wide using the &lt;code&gt;kernel.io_uring_disabled&lt;/code&gt; sysctl. For a service that handles untrusted internet input, such as a webhook gateway, weigh the throughput gains against that exposure.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Preventing the Slow Consumer Cascade
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Head-of-line blocking in a multi-tenant router
&lt;/h3&gt;

&lt;p&gt;A webhook router accepts events and forwards them to many customer endpoints. If one customer’s endpoint slows or goes offline and the router does not isolate that customer, the failure spreads:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Shared workers block while trying to deliver to the slow tenant.&lt;/li&gt;
&lt;li&gt;Healthy tenants’ events queue behind them.&lt;/li&gt;
&lt;li&gt;Threads, connections, and memory run out, and the failure becomes platform-wide.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming events
   │
   ├─► Tenant A queue (full) ──► Endpoint A (slow or failing)  → circuit opens, A's events shed or parked
   │
   └─► Tenant B queue (healthy) ─► Endpoint B (fast)           → unaffected
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core rule is that a delivery problem for one tenant must never consume a resource that another tenant needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Bounded queues as credits
&lt;/h3&gt;

&lt;p&gt;Credit-based flow control is an established idea in networking. It appears in technologies such as Fibre Channel, InfiniBand, and PCIe, where a sender may transmit only while it holds credits granted by the receiver. The same idea works at the application layer.&lt;/p&gt;

&lt;p&gt;Give each tenant a fixed budget of in-flight events:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accepting an event consumes one credit from that tenant’s budget.&lt;/li&gt;
&lt;li&gt;A credit is returned only when the event leaves the queue, either after successful delivery or after it is moved to durable storage.&lt;/li&gt;
&lt;li&gt;When a tenant has no credits left, the ingress path &lt;strong&gt;does not block&lt;/strong&gt;. It sheds load or spills to durable storage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A bounded Tokio channel is a natural fit for this. Its capacity is the credit budget, and &lt;code&gt;try_send&lt;/code&gt; (or &lt;code&gt;try_reserve&lt;/code&gt;) fails immediately instead of waiting.&lt;/p&gt;

&lt;h3&gt;
  
  
  What “shedding” means depends on who is sending
&lt;/h3&gt;

&lt;p&gt;How you reject an event matters because the upstream provider’s retry behavior varies. Stripe, for instance, documents that in live mode it retries delivery for up to three days with exponential backoff, and recommends returning a 2xx quickly before doing heavy work. If your router returns &lt;code&gt;429&lt;/code&gt; or &lt;code&gt;503&lt;/code&gt; to such a sender, the event will come back later. Other senders may retry only briefly or not at all. The safer pattern is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Verify the signature and &lt;strong&gt;persist the event durably first&lt;/strong&gt;, then return &lt;code&gt;2xx&lt;/code&gt; quickly.&lt;/li&gt;
&lt;li&gt;Deliver downstream asynchronously from a per-tenant queue.&lt;/li&gt;
&lt;li&gt;When a tenant’s in-memory queue is full, spill to a disk-backed or broker-backed queue (RocksDB, Kafka, SQS, or similar) rather than dropping.&lt;/li&gt;
&lt;li&gt;Reserve &lt;code&gt;429&lt;/code&gt; or &lt;code&gt;503&lt;/code&gt; with a &lt;code&gt;Retry-After&lt;/code&gt; header for cases where durable storage itself is saturated, and only for providers you know will retry.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Per-tenant isolation in Rust
&lt;/h3&gt;

&lt;p&gt;The sketch below gives each tenant a bounded channel and its own consumer task. It uses a read-write lock so lookups for different tenants do not serialize on a single mutex.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;WebhookPayload&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;IngestError&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;UnknownTenant&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;TenantSaturated&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// out of credits: spill to durable storage or shed&lt;/span&gt;
    &lt;span class="n"&gt;TenantClosed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;RouterManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;tenants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;RwLock&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&amp;gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;RouterManager&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;tenants&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;RwLock&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;register_tenant&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

        &lt;span class="c1"&gt;// One isolated consumer per tenant: a slow endpoint only stalls its own queue.&lt;/span&gt;
        &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;move&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Bound every delivery attempt so a hung endpoint cannot block forever.&lt;/span&gt;
                &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;time&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nn"&gt;Duration&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_secs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                    &lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
                &lt;span class="c1"&gt;// On timeout or error: schedule a retry, and trip a per-tenant circuit breaker.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;});&lt;/span&gt;

        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tenants&lt;/span&gt;&lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ingest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;IngestError&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tenants&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.tenants&lt;/span&gt;&lt;span class="nf"&gt;.read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tenants&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tenant_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;IngestError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UnknownTenant&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

        &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="nf"&gt;.try_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(()),&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;TrySendError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Full&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;IngestError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TenantSaturated&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
            &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;error&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;TrySendError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Closed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;IngestError&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TenantClosed&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;deliver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_tenant_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// HTTP POST to the customer's endpoint with signing, timeouts, and retry scheduling.&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two design notes. First, a bounded channel gives you fairness in memory, but you still need per-tenant concurrency limits on outbound connections, otherwise one tenant with thousands of queued events can monopolize the shared HTTP client pool. Second, a circuit breaker per endpoint, which stops attempts after repeated failures and probes occasionally for recovery, prevents wasted work against a dead endpoint.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Per-Tenant Rate Limiting with a Redis Token Bucket
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why rate limit
&lt;/h3&gt;

&lt;p&gt;Failures and bugs can create floods without any malicious intent. A customer’s endpoint that returns errors triggers retries, and a buggy integration can loop and resend events. Per-tenant limits keep one sender’s problem from consuming shared capacity.&lt;/p&gt;

&lt;h3&gt;
  
  
  Atomic token bucket in Lua
&lt;/h3&gt;

&lt;p&gt;A token bucket stores the number of tokens and the time of the last refill. A read-modify-write sequence done through separate &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;SET&lt;/code&gt; commands is racy when several gateway nodes run it concurrently. A Redis Lua script runs atomically as a single command, so no other command executes in the middle of it.&lt;/p&gt;

&lt;p&gt;The script below improves on the common version in three ways:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reads time from Redis with &lt;code&gt;TIME&lt;/code&gt; instead of trusting each gateway’s clock, which avoids skew between nodes. Reading time in scripts works with current Redis replication behavior, where scripts replicate by their effects.&lt;/li&gt;
&lt;li&gt;It uses &lt;code&gt;HSET&lt;/code&gt;, because &lt;code&gt;HMSET&lt;/code&gt; has been deprecated since Redis 4.0.&lt;/li&gt;
&lt;li&gt;It returns floats as strings, because Redis converts Lua numbers in replies to integers and truncates them.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight lua"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- KEYS[1]: bucket key, e.g. "{tenant_xyz}:rate_limit"&lt;/span&gt;
&lt;span class="c1"&gt;-- ARGV[1]: bucket capacity (maximum burst)&lt;/span&gt;
&lt;span class="c1"&gt;-- ARGV[2]: refill rate in tokens per second&lt;/span&gt;
&lt;span class="c1"&gt;-- ARGV[3]: tokens requested for this call (usually 1)&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;         &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;KEYS&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;refill_rate&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ARGV&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;   &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"TIME"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;1000000&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;bucket&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HMGET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;tonumber&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bucket&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;math.max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;ts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;math.min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;refill_rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="kd"&gt;local&lt;/span&gt; &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="k"&gt;then&lt;/span&gt;
  &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt;
  &lt;span class="n"&gt;allowed&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;
  &lt;span class="n"&gt;retry_after&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;refill_rate&lt;/span&gt;
&lt;span class="k"&gt;end&lt;/span&gt;

&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"HSET"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"tokens"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"ts"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;-- Expire idle buckets once they would have fully refilled anyway.&lt;/span&gt;
&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"PEXPIRE"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;math.ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;refill_rate&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;allowed&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokens&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nb"&gt;tostring&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;retry_after&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On a denied request, use &lt;code&gt;retry_after&lt;/code&gt; to populate a &lt;code&gt;Retry-After&lt;/code&gt; header.&lt;/p&gt;

&lt;p&gt;Redis 7.0 also introduced Redis Functions, which are invoked with &lt;code&gt;FCALL&lt;/code&gt;. Unlike &lt;code&gt;EVAL&lt;/code&gt; scripts, functions are persisted with the dataset and replicated to replicas, so you do not need to resend script source from every client. Consider them if you run Redis 7 or later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Redis Cluster considerations
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Declare every key in &lt;code&gt;KEYS&lt;/code&gt;.&lt;/strong&gt; In Redis Cluster, all keys a script touches must be passed in &lt;code&gt;KEYS&lt;/code&gt; and must hash to the same slot. Accessing undeclared keys inside a script breaks cluster routing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use hash tags when a script touches multiple keys.&lt;/strong&gt; The script above uses one key per tenant, so it works as is. If you extend it to also read, say, a per-tenant configuration key, name both with the same tag (for example &lt;code&gt;{tenant_xyz}:bucket&lt;/code&gt; and &lt;code&gt;{tenant_xyz}:config&lt;/code&gt;) so they land in the same slot.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Expect hot slots.&lt;/strong&gt; Each tenant’s bucket lives on one node, so a single very busy tenant is bounded by what that node can serve. Spread tenants across slots and monitor slot-level load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Limit round trips.&lt;/strong&gt; Redis executes commands on a main thread per node, so every limiter call costs capacity there. For very high-volume tenants, let each gateway keep a local bucket and lease tokens from Redis in batches (for example, take 100 tokens at a time and refill when they run low). This reduces Redis calls and latency at the cost of precision: the global limit becomes approximate by up to the total size of outstanding leases. Choose the batch size according to how strict the limit must be. The reduction in Redis traffic depends on your batch size and traffic pattern, so measure it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;Common baseline&lt;/th&gt;
&lt;th&gt;Alternative discussed here&lt;/th&gt;
&lt;th&gt;What you gain, and what to watch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network ingress&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;epoll plus a &lt;code&gt;recv()&lt;/code&gt; call per read&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;io_uring&lt;/code&gt; multishot receive with provided buffer rings; zcrx on supported NICs and kernels (6.15+)&lt;/td&gt;
&lt;td&gt;Fewer syscalls and simpler buffer management. Multishot is not zero-copy, and the security exposure of &lt;code&gt;io_uring&lt;/code&gt; needs review.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Flow control&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Shared worker pool, unbounded queues, blocking writes&lt;/td&gt;
&lt;td&gt;Bounded per-tenant queues with credit-style limits, timeouts, circuit breakers, and durable spill&lt;/td&gt;
&lt;td&gt;One tenant’s slowness stays contained; durable persistence avoids data loss when shedding.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rate limiting&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Non-atomic &lt;code&gt;GET&lt;/code&gt;/&lt;code&gt;INCR&lt;/code&gt;/&lt;code&gt;SET&lt;/code&gt; sequences&lt;/td&gt;
&lt;td&gt;Atomic Lua token bucket using Redis &lt;code&gt;TIME&lt;/code&gt;, with hash-tagged keys and optional local token leasing&lt;/td&gt;
&lt;td&gt;No race conditions and cluster-safe routing, traded against slot hot spots and approximate global limits when leasing.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Further Reading
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Linux kernel documentation: &lt;a href="https://docs.kernel.org/6.15/networking/iou-zcrx.html" rel="noopener noreferrer"&gt;io_uring zero copy Rx&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;liburing manual: &lt;a href="https://man7.org/linux/man-pages/man3/io_uring_prep_recv_multishot.3.html" rel="noopener noreferrer"&gt;&lt;code&gt;io_uring_prep_recv&lt;/code&gt;&lt;/a&gt;, including the multishot variant&lt;/li&gt;
&lt;li&gt;Stripe documentation: &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Receive Stripe events in your webhook endpoint&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Redis documentation: &lt;a href="https://redis.io/docs/latest/develop/interact/programmability/eval-intro/" rel="noopener noreferrer"&gt;Lua scripting with &lt;code&gt;EVAL&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://redis.io/docs/latest/develop/interact/programmability/functions-intro/" rel="noopener noreferrer"&gt;Redis Functions&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/high-velocity-webhook-engineering-low-level-ingress-dynamic-flow-control-and-mul" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/high-velocity-webhook-engineering-low-level-ingress-dynamic-flow-control-and-mul&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
    </item>
    <item>
      <title>Advanced Webhook Operations &amp; Resiliency: Architecture Patterns for Production Engineers</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Wed, 28 Oct 2026 02:05:19 +0000</pubDate>
      <link>https://dev.to/instawebhook/advanced-webhook-operations-resiliency-architecture-patterns-for-production-engineers-h6i</link>
      <guid>https://dev.to/instawebhook/advanced-webhook-operations-resiliency-architecture-patterns-for-production-engineers-h6i</guid>
      <description>&lt;p&gt;Webhooks are the main way SaaS platforms, payment processors and internal microservices tell other systems that something happened. At a few thousand events a day, a loop that POSTs to a URL and retries on failure is enough. At hundreds of millions of events a day, the same loop turns small outages into large ones. Retries pile up, healthy customers get starved by unhealthy ones, and the infrastructure sitting in front of your consumers starts rejecting requests for reasons your application never sees.&lt;/p&gt;

&lt;p&gt;This guide covers four areas of production webhook operations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive backoff&lt;/strong&gt; combined with per-endpoint &lt;strong&gt;circuit breakers&lt;/strong&gt;.&lt;/li&gt;
&lt;li&gt;Preventing &lt;strong&gt;retry storms&lt;/strong&gt; and cascading failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero-downtime migration&lt;/strong&gt; of delivery infrastructure and payload schemas.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header bloat&lt;/strong&gt; and the request-size limits of reverse proxies and load balancers.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The numbers in the examples (timeouts, thresholds, cooldowns) are illustrative starting points. Tune them against your own traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Adaptive Backoff with Circuit Breakers
&lt;/h2&gt;

&lt;p&gt;Plain exponential backoff (&lt;code&gt;delay = base * 2^attempt&lt;/code&gt;) treats every failure the same way. Failures differ in what they say about the consumer, and the dispatcher should react to each differently:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 429 (Too Many Requests):&lt;/strong&gt; the consumer, or something in front of it, is shedding load on purpose. Retrying on your normal schedule ignores that signal and can get your delivery IPs throttled or blocked. If the response carries a &lt;code&gt;Retry-After&lt;/code&gt; header, honor it. &lt;a href="https://www.rfc-editor.org/rfc/rfc9110" rel="noopener noreferrer"&gt;RFC 9110&lt;/a&gt; defines the value as either a number of seconds or an HTTP date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;HTTP 5xx:&lt;/strong&gt; the consumer is crashing, deploying, or out of database connections. Fast retries add load to a system that is trying to recover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Timeouts and connection resets:&lt;/strong&gt; the consumer is slow, overloaded or unreachable. Every slow attempt also holds one of your own workers and sockets, so keep client timeouts short.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                +----------------------------------------+
                |           Incoming Event Queue         |
                +----------------------------------------+
                                    |
                                    v
                +----------------------------------------+
                |     Circuit Breaker State Inspection   |
                +----------------------------------------+
                         /                      \
                [CLOSED / HALF-OPEN probe]    [OPEN]
                       /                          \
                      v                            v
     +----------------------------+     +--------------------------+
     |  Attempt webhook delivery  |     | Skip the network call,   |
     +----------------------------+     | schedule for later retry |
        /          |            \       +--------------------------+
 [2xx Success] [429 Rate Limit] [5xx / Timeout]
      |             |                 |
      v             v                 v
 Reset failure  Honor Retry-After  Increment failure
 counter, close or scaled backoff  counter, trip if
 circuit                           threshold is met
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Retry Policy by Response Type
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Response / failure&lt;/th&gt;
&lt;th&gt;Likely cause&lt;/th&gt;
&lt;th&gt;Suggested action&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;429 Too Many Requests&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Consumer or its gateway is rate limiting&lt;/td&gt;
&lt;td&gt;Honor &lt;code&gt;Retry-After&lt;/code&gt; (seconds or HTTP date) and pause delivery to that endpoint. If the header is missing, use a steeper backoff multiplier than for 5xx.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;502 / 503 / 504&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Gateway or app server down, or mid-deployment&lt;/td&gt;
&lt;td&gt;Exponential backoff with &lt;strong&gt;full jitter&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;500&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bug or unhandled exception in the consumer’s handler&lt;/td&gt;
&lt;td&gt;Slower backoff and a lower attempt cap. Retrying rarely fixes a code bug, so don’t let these events clog shared queues.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;408, connection or read timeout&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Thread pool exhaustion, slow queries, network problems&lt;/td&gt;
&lt;td&gt;Short client timeouts (a few seconds) so workers are freed quickly. Route retries to a slower queue.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;400, 401, 403, 404&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bad request, expired credentials, misconfigured or removed endpoint&lt;/td&gt;
&lt;td&gt;Usually not worth retrying quickly. Dead-letter after a small number of attempts, or retry slowly, because 401/403/404 can appear transiently during consumer deployments.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;410 Gone&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The consumer has explicitly removed the endpoint&lt;/td&gt;
&lt;td&gt;Stop delivering and disable the endpoint.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Providers differ on this. Stripe, for example, treats any non-2xx response as a failed delivery and retries in live mode with exponential backoff for up to three days (&lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;Stripe docs&lt;/a&gt;). Other platforms stop retrying on most 4xx responses but still retry 408 and 429. Whichever policy you pick, document it for your consumers.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why Full Jitter
&lt;/h3&gt;

&lt;p&gt;If many events fail at the same moment and all use the same backoff formula, they all retry at the same moments too. Jitter spreads those retries out. In &lt;strong&gt;full jitter&lt;/strong&gt;, described in the AWS Architecture Blog’s &lt;a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/" rel="noopener noreferrer"&gt;Exponential Backoff and Jitter&lt;/a&gt;, the delay is a random value between 0 and the capped exponential ceiling:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;sleep = random_between(0, min(cap, base * 2^attempt))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Full jitter can occasionally return a very short delay. If that matters, use a variant that keeps a minimum floor (often called “equal jitter”), or add a minimum delay yourself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Distributed Circuit Breakers for Outbound Webhooks
&lt;/h3&gt;

&lt;p&gt;When one consumer endpoint fails for a long time, retrying millions of events against it wastes worker time and sockets, and it slows delivery to healthy consumers that share the worker pool. A circuit breaker keyed per endpoint (or per consumer domain) stops that. Keep its state in a fast shared store such as Redis, so every dispatcher instance sees the same state.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Closed (normal):&lt;/strong&gt; events are dispatched normally, and failures are counted.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Open (tripped):&lt;/strong&gt; when failures cross a threshold (for example, 10 failures within a window, or an error rate above a set percentage), the breaker opens. Events for that endpoint skip the network and go straight to a delayed retry queue.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Half-open (probing):&lt;/strong&gt; after a cooldown, one probe request is let through.

&lt;ul&gt;
&lt;li&gt;If the probe succeeds, the breaker closes and normal delivery resumes.&lt;/li&gt;
&lt;li&gt;If it fails, the breaker re-opens, ideally with a longer cooldown.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Count only failures that indicate an unhealthy consumer (timeouts, connection errors, 5xx, and arguably 429). A 400 or 401 usually indicates a configuration problem and shouldn’t trip a breaker on its own.&lt;/p&gt;

&lt;h4&gt;
  
  
  Python Sketch: Adaptive Delay, Jitter and a Redis-Backed Breaker
&lt;/h4&gt;

&lt;p&gt;This is a simplified sketch. The Redis operations are not atomic as written. In production, move the read-modify-write logic into a Lua script or use a library that provides atomic breaker state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;timezone&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;email.utils&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;parsedate_to_datetime&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;parse_retry_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
    &lt;span class="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Retry-After is either delay-seconds or an HTTP-date (RFC 9110).&lt;/span&gt;&lt;span class="sh"&gt;"""&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;float&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;pass&lt;/span&gt;
    &lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parsedate_to_datetime&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;when&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tzinfo&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;when&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;datetime&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;timezone&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;utc&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nf"&gt;total_seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="nf"&gt;except &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;TypeError&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;ValueError&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;AdaptiveWebhookDispatcher&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;redis_client&lt;/span&gt;          &lt;span class="c1"&gt;# redis-py client, decode_responses=True
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.0&lt;/span&gt;              &lt;span class="c1"&gt;# seconds
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_delay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;3600.0&lt;/span&gt;            &lt;span class="c1"&gt;# cap retry delay at 1 hour
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_threshold&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;        &lt;span class="c1"&gt;# failures per window before tripping
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_window&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;300&lt;/span&gt;          &lt;span class="c1"&gt;# fixed 5-minute counting window
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown_period&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;120&lt;/span&gt;         &lt;span class="c1"&gt;# first OPEN period, seconds
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cooldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3600&lt;/span&gt;           &lt;span class="c1"&gt;# cap on escalated cooldowns
&lt;/span&gt;        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probe_ttl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;                &lt;span class="c1"&gt;# how long a half-open probe slot is held
&lt;/span&gt;
    &lt;span class="c1"&gt;# ---- retry delay -------------------------------------------------
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;retry_delay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
                    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Mapping&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;h&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;items&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry-after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;parse_retry_after&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;h&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;retry-after&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;requested&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_delay&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="c1"&gt;# Never retry earlier than asked; add a little jitter on top.
&lt;/span&gt;                &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;requested&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mf"&gt;0.1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c1"&gt;# Steeper growth for 429 without Retry-After, standard growth otherwise.
&lt;/span&gt;        &lt;span class="n"&gt;multiplier&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;429&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="n"&gt;ceiling&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_delay&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;base_delay&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;multiplier&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;attempt&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;random&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;uniform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ceiling&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# full jitter
&lt;/span&gt;
    &lt;span class="c1"&gt;# ---- circuit breaker --------------------------------------------
&lt;/span&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;cb:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tripped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)):&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;          &lt;span class="c1"&gt;# cooldown expired, not yet proven healthy
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOSED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;allow_request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CLOSED&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="bp"&gt;False&lt;/span&gt;
        &lt;span class="c1"&gt;# HALF_OPEN: allow exactly one probe at a time
&lt;/span&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;probe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                                   &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ex&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;probe_ttl&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_failure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;HALF_OPEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;        &lt;span class="c1"&gt;# probe failed: re-open with longer cooldown
&lt;/span&gt;            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="n"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;expire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# fixed window, set once
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;failure_threshold&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_trip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;trips&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;incr&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trips&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;cooldown&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cooldown_period&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;trips&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max_cooldown&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pipe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;pipeline&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cooldown&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tripped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;probe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="n"&gt;pipe&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;record_success&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;redis&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_k&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;endpoint&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt;
                            &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;failures&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;open&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tripped&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;trips&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;probe&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Webhook Storms: Detecting and Preventing Cascading Failures
&lt;/h2&gt;

&lt;p&gt;A &lt;strong&gt;webhook storm&lt;/strong&gt; (or retry storm) happens when an outage creates a large backlog of failed deliveries, and the retries of that backlog, added to normal live traffic, overload the consumer as it recovers and knock it over again.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;          [ Consumer outage or bad deployment ]
                          |
                          v
             Many deliveries fail at once
                          |
                          v
          Failed events enter retry pipelines
                          |
             Retries scheduled at identical
             offsets (no jitter): T+30s, T+60s ...
                          |
                          v
        Consumer recovers, receives a retry spike
        on top of live traffic, and fails again
                          |
                          v
              Repeated or cascading outage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Common Causes of Amplification
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Synchronized retries.&lt;/strong&gt; Without jitter, every event that failed in the same outage retries on the same schedule, so the consumer sees repeated spikes instead of a smooth ramp.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Head-of-line blocking.&lt;/strong&gt; If fresh events share a queue with thousands of failing retries, they wait behind them. Healthy consumers are delayed because one consumer is down.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Naive re-enqueueing.&lt;/strong&gt; Pushing a failed message straight back to the front of the main queue creates a tight failure loop. Failed work should go through a delayed path instead.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No per-consumer limits.&lt;/strong&gt; Without concurrency and rate caps per endpoint, a backlog drains as fast as your workers allow, which may be far faster than the consumer can absorb.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Architecture to Contain Retry Storms
&lt;/h3&gt;

&lt;p&gt;Separate real-time delivery from retries, and put limits in front of every consumer:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 +-------------------------+
                 |     Primary event       |
                 |        ingress          |
                 +-------------------------+
                             |
                             v
                 +-------------------------+
                 | Tier 1: ingestion queue |
                 | (Kafka, Pulsar, etc.)   |
                 +-------------------------+
                             |
                             v
                 +-------------------------+
                 |  Real-time worker pool  |
                 |  (per-endpoint limits)  |
                 +-------------------------+
                      /               \
               [2xx Success]       [Failure]
                    |                  |
                    v                  v
           +----------------+  +---------------------------+
           |   Completed    |  | Tier 2: delayed retry     |
           +----------------+  | queues (short/medium/long)|
                               +---------------------------+
                                          |
                                          | attempts exhausted or
                                          | non-retriable error
                                          v
                               +---------------------------+
                               | Dead Letter Queue (DLQ)   |
                               | inspection and replay     |
                               +---------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Design Principles
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Two tiers of queues.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tier 1&lt;/strong&gt; handles first-attempt deliveries with short timeouts, so slow consumers can’t hold workers for long.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier 2&lt;/strong&gt; holds retries, split into delay buckets (for example 1 minute, 15 minutes, 1 hour). Retry traffic never competes with first attempts for the same workers.&lt;/li&gt;
&lt;li&gt;Implementation note: Kafka has no built-in per-message delay, so delay buckets are usually implemented as separate topics or a scheduler that re-publishes when a message is due. Pulsar supports delayed message delivery natively.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-endpoint concurrency and rate limits.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Cap in-flight requests per endpoint (a semaphore or lease in Redis) so a deep backlog cannot open hundreds of simultaneous connections to one consumer.&lt;/li&gt;
&lt;li&gt;Add a per-endpoint token bucket for request rate. When a consumer returns 429, reduce its rate and recover gradually.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breakers&lt;/strong&gt; (Section 1) so persistently failing endpoints stop consuming worker capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded retries and a dead letter queue.&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Stop retrying after a fixed budget, for example a maximum number of attempts or a maximum age (24 hours in your policy, 3 days like Stripe’s live-mode window, or whatever you document).&lt;/li&gt;
&lt;li&gt;Move exhausted or non-retriable events to a DLQ.&lt;/li&gt;
&lt;li&gt;Offer replay, via API or dashboard, so consumers can re-ingest events after they fix the problem. Replays should also be rate limited.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Visibility.&lt;/strong&gt; Track retry queue depth, age of the oldest retry, per-endpoint failure rate and breaker state. A growing retry backlog is an early warning of a storm.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  3. Zero-Downtime Migration of Webhook Delivery
&lt;/h2&gt;

&lt;p&gt;Moving webhook delivery to a new region, a new gateway or a new delivery engine, or introducing a new payload version, must not drop events, send duplicates or break the contract consumers rely on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Dual-Run with Shadow Delivery
&lt;/h3&gt;

&lt;p&gt;Run the new pipeline alongside the old one, but let only one of them deliver to real customer endpoints.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                 +----------------------------------+
                 |       Upstream event publisher   |
                 +----------------------------------+
                                  |
                                  v
                 +----------------------------------+
                 |          Routing layer           |
                 +----------------------------------+
                      /                        \
                     v                          v
   +-------------------------------+  +-------------------------------+
   | Legacy engine                 |  | New engine                    |
   | ACTIVE: delivers to customers |  | SHADOW: delivers to test sink |
   +-------------------------------+  +-------------------------------+
                     |                          |
                     v                          v
        Customer webhook endpoint       Validation sink and logs
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Phase 1: Shadow Mirroring and Validation
&lt;/h4&gt;

&lt;p&gt;Publish each event to both engines. The new engine builds payloads, signs them and applies its routing logic, but sends the HTTP request to an internal validation sink instead of the customer URL. Compare outputs with the legacy engine: payload bytes, signatures, headers, latency and error behavior. Because the shadow engine never reaches customers, it can’t prove that customer endpoints are reachable from the new network. Check reachability separately, for example by testing egress IPs and DNS from the new region.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 2: Payload Versioning
&lt;/h4&gt;

&lt;p&gt;When the payload schema changes, for example a timestamp format change or a restructured object, avoid forcing every consumer to upgrade at once:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Pin a version per endpoint.&lt;/strong&gt; Each webhook endpoint is configured with a payload version, and the dispatcher renders that version. Stripe takes this approach: events are rendered according to the API version configured for the endpoint.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefer additive changes.&lt;/strong&gt; Adding fields is usually backward compatible. Renaming fields, changing types or changing units is not.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Include a version field&lt;/strong&gt; in the payload and in your documentation, and publish a deprecation timeline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Support old versions for a defined period&lt;/strong&gt; and track which endpoints still use them before turning a version off.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Avoid sending both the old and new payload shapes in a single body. It makes payloads larger, ambiguous for consumers and harder to deprecate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"evt_987654321"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-10-01"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"payment.succeeded"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"data"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"account_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"acc_123"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"amount_in_cents"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4999&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"currency"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"USD"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Phase 3: Idempotency and Standard Headers
&lt;/h4&gt;

&lt;p&gt;Failover, replays and migrations all raise the chance that a consumer receives the same event twice. Give consumers a stable ID to deduplicate on, and sign requests in a way libraries already understand. The &lt;a href="https://www.standardwebhooks.com/" rel="noopener noreferrer"&gt;Standard Webhooks&lt;/a&gt; specification defines a widely adopted set of headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;webhook-id&lt;/code&gt;: unique per message and stable across all delivery attempts, so consumers can use it as a deduplication key. Any unique string works (it doesn’t have to be a UUID).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook-timestamp&lt;/code&gt;: Unix timestamp in &lt;strong&gt;seconds&lt;/strong&gt;, set when that attempt is sent (retries carry a new timestamp). Consumers use it to reject stale or replayed requests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook-signature&lt;/code&gt;: one or more space-separated signatures, each in the form &lt;code&gt;v1,&amp;lt;base64&amp;gt;&lt;/code&gt;, where the value is a Base64-encoded HMAC-SHA256 over &lt;code&gt;{webhook-id}.{webhook-timestamp}.{raw body}&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Because the header holds a list of signatures, you can rotate signing secrets by sending signatures from both the old and new secret during the transition. Stripe uses a similar scheme with its own format: the &lt;code&gt;Stripe-Signature&lt;/code&gt; header carries &lt;code&gt;t=&amp;lt;timestamp&amp;gt;&lt;/code&gt; and &lt;code&gt;v1=&amp;lt;hex HMAC-SHA256 of "timestamp.payload"&amp;gt;&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Consumers must verify the signature against the &lt;strong&gt;raw, unparsed request body&lt;/strong&gt;. A framework that parses and re-serializes the JSON first will produce a different byte sequence and the signature will fail.&lt;/p&gt;

&lt;h4&gt;
  
  
  Phase 4: Canary Cutover
&lt;/h4&gt;

&lt;p&gt;Move traffic to the new engine gradually, by tenant. Assign each tenant to exactly one active engine using a stable hash of the tenant ID, so that no tenant is delivered to by both engines at once. Raise the share in steps (for example 1%, 10%, 50%, 100%), watching delivery success, latency, signature-verification failures reported by consumers, and retry volume. Keep the routing switch simple enough to roll back immediately.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Header Bloat and Proxy Limits
&lt;/h2&gt;

&lt;p&gt;Webhook requests often pass through several hops: a CDN or WAF, a load balancer, an API gateway, and an ingress proxy, before reaching the application. Each hop enforces its own limits on request line and header size. Requests that exceed them are rejected &lt;strong&gt;before&lt;/strong&gt; they reach your consumer’s application code, so the consumer’s application logs show nothing. The evidence is in the proxy or load balancer access logs.&lt;/p&gt;

&lt;p&gt;Common sources of large headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Large &lt;strong&gt;cookies&lt;/strong&gt; and &lt;strong&gt;JWT bearer tokens&lt;/strong&gt; (a JWT with many claims can be several kilobytes on its own).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;X-Forwarded-For&lt;/code&gt; and &lt;code&gt;Via&lt;/code&gt; chains that grow with each proxy hop.&lt;/li&gt;
&lt;li&gt;Multiple custom signature or key-identifier headers.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;tracestate&lt;/code&gt; and &lt;code&gt;baggage&lt;/code&gt; headers from distributed tracing. Note that &lt;code&gt;traceparent&lt;/code&gt; itself is small and fixed-length (55 characters per the &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;W3C Trace Context&lt;/a&gt; spec), so the tracing headers that grow are &lt;code&gt;tracestate&lt;/code&gt; and &lt;code&gt;baggage&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming request
  |
  +-- Headers:
  |    |-- Authorization: Bearer &amp;lt;large JWT&amp;gt;
  |    |-- traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
  |    |-- tracestate / baggage: &amp;lt;vendor and application entries&amp;gt;
  |    |-- Cookie: &amp;lt;many cookies&amp;gt;
  |    |-- X-Forwarded-For: 203.0.113.7, 10.0.0.1, 172.16.0.1 ...
  |    +-- Custom signature / metadata headers
  v
+---------------------------------------------------------+
| Reverse proxy or load balancer                          |
| Header size limit exceeded                              |
+---------------------------------------------------------+
  |
  v
Request rejected at the proxy (400 / 431 / other), never reaching the app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Default Limits of Common Proxies
&lt;/h3&gt;

&lt;p&gt;Defaults vary by version and configuration. Verify against the version you run.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Proxy&lt;/th&gt;
&lt;th&gt;Default limits&lt;/th&gt;
&lt;th&gt;Typical failure&lt;/th&gt;
&lt;th&gt;Tuning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;NGINX&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;client_header_buffer_size&lt;/code&gt; 1k; &lt;code&gt;large_client_header_buffers&lt;/code&gt; 4 × 8k. The request line and each individual header line must fit in one 8k buffer, and all headers together must fit in the allocated buffers (32k at defaults).&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;414&lt;/code&gt; if the request line is too long; &lt;code&gt;400 Bad Request&lt;/code&gt; (“Request Header Or Cookie Too Large”) for oversized headers&lt;/td&gt;
&lt;td&gt;&lt;code&gt;large_client_header_buffers 4 16k;&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Envoy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;max_request_headers_kb&lt;/code&gt;: 60 KiB; &lt;code&gt;max_headers_count&lt;/code&gt;: 100&lt;/td&gt;
&lt;td&gt;&lt;code&gt;431 Request Header Fields Too Large&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Raise &lt;code&gt;max_request_headers_kb&lt;/code&gt; in the &lt;code&gt;HttpConnectionManager&lt;/code&gt; (older versions cap it at 96; recent versions allow much higher values)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Apache HTTPD&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LimitRequestLine&lt;/code&gt; 8190 bytes; &lt;code&gt;LimitRequestFieldSize&lt;/code&gt; 8190 bytes per header; &lt;code&gt;LimitRequestFields&lt;/code&gt; 100 headers&lt;/td&gt;
&lt;td&gt;Request rejected&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;LimitRequestFieldSize 16384&lt;/code&gt;, &lt;code&gt;LimitRequestFields 200&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;AWS Application Load Balancer&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Request line 16 K; single header 16 K; entire request header 64 K&lt;/td&gt;
&lt;td&gt;Request rejected&lt;/td&gt;
&lt;td&gt;Hard limits that cannot be changed. Reduce header size.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Two notes on status codes. &lt;code&gt;431&lt;/code&gt; is the status defined by &lt;a href="https://www.rfc-editor.org/rfc/rfc6585" rel="noopener noreferrer"&gt;RFC 6585&lt;/a&gt; for oversized headers. &lt;code&gt;413&lt;/code&gt; refers to the request &lt;em&gt;body&lt;/em&gt; being too large, so a &lt;code&gt;413&lt;/code&gt; from a webhook endpoint points to payload size, not header size. A &lt;code&gt;502&lt;/code&gt; can also appear when an upstream closes the connection because of its own limits, so check the logs of every hop.&lt;/p&gt;

&lt;p&gt;Also remember that raising the limit on your edge proxy only helps if every proxy and application server behind it accepts the larger size too. An Envoy that admits 128 KB headers in front of a backend that rejects anything above 8 KB just moves the failure one hop inward.&lt;/p&gt;

&lt;h3&gt;
  
  
  Mitigations
&lt;/h3&gt;

&lt;h4&gt;
  
  
  1. Keep Headers Small
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Keep authentication to a single compact credential. Where you control token contents, avoid stuffing large claim sets into a bearer token.&lt;/li&gt;
&lt;li&gt;Limit &lt;code&gt;tracestate&lt;/code&gt; and &lt;code&gt;baggage&lt;/code&gt; to what you actually use, and drop them at the edge when sending to third-party consumers.&lt;/li&gt;
&lt;li&gt;Don’t add per-attempt or per-hop debug headers to outbound webhooks unless they are needed.&lt;/li&gt;
&lt;li&gt;If you sign with several secrets during a rotation, use the standard single &lt;code&gt;webhook-signature&lt;/code&gt; header with space-separated signatures, rather than one header per version.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Anti-pattern: one header per signature version plus extras
X-Webhook-Signature-V1: &amp;lt;signature&amp;gt;
X-Webhook-Signature-V2: &amp;lt;signature&amp;gt;
X-Webhook-Key-Identifier: key_prod_9921381

# Better: one header carrying all active signatures
webhook-signature: v1,&amp;lt;base64-signature-A&amp;gt; v1,&amp;lt;base64-signature-B&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  2. Don’t Move Metadata into a Signed Body
&lt;/h4&gt;

&lt;p&gt;It is tempting to push tracing or delivery metadata into the JSON body to save header space. Be careful with this for outbound webhooks. The body is what gets signed, so anything in it that changes per attempt (such as a delivery-attempt counter) changes the signature and makes it harder for consumers to deduplicate by content. Keep the event payload immutable across retries, and carry per-attempt data only where it is needed, such as the timestamp in the signed headers.&lt;/p&gt;

&lt;h4&gt;
  
  
  3. Tune NGINX Ingress
&lt;/h4&gt;

&lt;p&gt;For a Kubernetes cluster running the NGINX-based ingress controller, the relevant ConfigMap keys are &lt;code&gt;client-header-buffer-size&lt;/code&gt; (default &lt;code&gt;1k&lt;/code&gt;) and &lt;code&gt;large-client-header-buffers&lt;/code&gt; (default &lt;code&gt;4 8k&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;apiVersion&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;v1&lt;/span&gt;
&lt;span class="na"&gt;kind&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ConfigMap&lt;/span&gt;
&lt;span class="na"&gt;metadata&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress-nginx-controller&lt;/span&gt;
  &lt;span class="na"&gt;namespace&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress-nginx&lt;/span&gt;
&lt;span class="na"&gt;data&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;client-header-buffer-size&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4k"&lt;/span&gt;
  &lt;span class="na"&gt;large-client-header-buffers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;4&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;16k"&lt;/span&gt;   &lt;span class="c1"&gt;# up to 64k total, 16k per header line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two cautions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;http2-max-field-size&lt;/code&gt; and &lt;code&gt;http2-max-header-size&lt;/code&gt; keys are deprecated in favor of &lt;code&gt;large-client-header-buffers&lt;/code&gt;, so don’t rely on them.&lt;/li&gt;
&lt;li&gt;The community &lt;strong&gt;Ingress NGINX&lt;/strong&gt; project was retired by the Kubernetes project in March 2026, meaning it no longer receives maintenance or security fixes. The Kubernetes Steering and Security Response Committees urged users to plan a migration to Gateway API or another supported controller. If you still run it, treat the migration as a priority. The F5/NGINX-maintained “NGINX Ingress Controller” is a different project.&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  4. Raise Envoy’s Header Limit Where Needed
&lt;/h4&gt;

&lt;p&gt;In Envoy, set &lt;code&gt;max_request_headers_kb&lt;/code&gt; on the &lt;code&gt;HttpConnectionManager&lt;/code&gt;. The configuration below is a minimal, complete listener, including the router filter that an HTTP connection manager requires:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;static_resources&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;listeners&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress_webhook_listener&lt;/span&gt;
    &lt;span class="na"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;socket_address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;address&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;0.0.0.0&lt;/span&gt;&lt;span class="pi"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;port_value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;443&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
    &lt;span class="na"&gt;filter_chains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;envoy.filters.network.http_connection_manager&lt;/span&gt;
        &lt;span class="na"&gt;typed_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager&lt;/span&gt;
          &lt;span class="s"&gt;stat_prefix&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ingress_http&lt;/span&gt;
          &lt;span class="s"&gt;max_request_headers_kb&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="m"&gt;96&lt;/span&gt;     &lt;span class="c1"&gt;# default is 60&lt;/span&gt;
          &lt;span class="na"&gt;route_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;local_route&lt;/span&gt;
            &lt;span class="na"&gt;virtual_hosts&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
            &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;webhook_services&lt;/span&gt;
              &lt;span class="na"&gt;domains&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;webhooks.example.com"&lt;/span&gt;&lt;span class="pi"&gt;]&lt;/span&gt;
              &lt;span class="na"&gt;routes&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;match&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;prefix&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;/v1/ingest"&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
                &lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;{&lt;/span&gt; &lt;span class="nv"&gt;cluster&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="nv"&gt;webhook_backend_cluster&lt;/span&gt; &lt;span class="pi"&gt;}&lt;/span&gt;
          &lt;span class="na"&gt;http_filters&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;envoy.filters.http.router&lt;/span&gt;
            &lt;span class="na"&gt;typed_config&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
              &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;@type"&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt; &lt;span class="s"&gt;type.googleapis.com/envoy.extensions.filters.http.router.v3.Router&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A production listener would also need TLS configuration (&lt;code&gt;transport_socket&lt;/code&gt;) and a defined &lt;code&gt;webhook_backend_cluster&lt;/code&gt;, which are omitted here for brevity. Older Envoy releases cap &lt;code&gt;max_request_headers_kb&lt;/code&gt; at 96. Newer ones accept larger values, but raising it far above what you actually need weakens the protection the limit provides.&lt;/p&gt;

&lt;h2&gt;
  
  
  Production Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Error classification:&lt;/strong&gt; Do 429, 5xx, timeouts, and 4xx responses each follow their own retry policy? Does the dispatcher honor &lt;code&gt;Retry-After&lt;/code&gt; in both its formats?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Jitter:&lt;/strong&gt; Do all backoff schedules use jitter?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Circuit breakers:&lt;/strong&gt; Are persistently failing endpoints isolated automatically, with half-open probes and escalating cooldowns?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Queue separation:&lt;/strong&gt; Are first-attempt deliveries decoupled from retry traffic?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-endpoint limits:&lt;/strong&gt; Are concurrency and request rate capped per consumer?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bounded retries and DLQ:&lt;/strong&gt; Is there a retry budget, a dead letter queue and a replay path?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency:&lt;/strong&gt; Is &lt;code&gt;webhook-id&lt;/code&gt; (or equivalent) stable across retries, and are consumers told to deduplicate on it?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Signatures:&lt;/strong&gt; Is the signed content documented (id, timestamp, raw body), and are timestamps checked on the consumer side?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Versioning:&lt;/strong&gt; Is the payload version pinned per endpoint, with a published deprecation policy?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Migration safety:&lt;/strong&gt; Does each tenant have exactly one active delivery engine during cutover, with an instant rollback?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Header budget:&lt;/strong&gt; Are header sizes measured, and are every hop’s limits (CDN, WAF, load balancer, ingress, app server) known and consistent?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observability:&lt;/strong&gt; Do you monitor retry backlog age, per-endpoint failure rates, breaker state and proxy-level rejections?&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RFC 9110, HTTP Semantics (&lt;code&gt;Retry-After&lt;/code&gt;): &lt;a href="https://www.rfc-editor.org/rfc/rfc9110" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc9110&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 6585, Additional HTTP Status Codes (429, 431): &lt;a href="https://www.rfc-editor.org/rfc/rfc6585" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc6585&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS Architecture Blog, “Exponential Backoff And Jitter”: &lt;a href="https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/" rel="noopener noreferrer"&gt;https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Standard Webhooks specification: &lt;a href="https://www.standardwebhooks.com/" rel="noopener noreferrer"&gt;https://www.standardwebhooks.com/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stripe documentation, receiving webhook events and retry behavior: &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;https://stripe.com/docs/webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;W3C Trace Context: &lt;a href="https://www.w3.org/TR/trace-context/" rel="noopener noreferrer"&gt;https://www.w3.org/TR/trace-context/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;AWS, Quotas for your Application Load Balancers: &lt;a href="https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html" rel="noopener noreferrer"&gt;https://docs.aws.amazon.com/elasticloadbalancing/latest/application/load-balancer-limits.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NGINX, &lt;code&gt;large_client_header_buffers&lt;/code&gt;: &lt;a href="https://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers" rel="noopener noreferrer"&gt;https://nginx.org/en/docs/http/ngx_http_core_module.html#large_client_header_buffers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Envoy, &lt;code&gt;HttpConnectionManager&lt;/code&gt; (&lt;code&gt;max_request_headers_kb&lt;/code&gt;): &lt;a href="https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto" rel="noopener noreferrer"&gt;https://www.envoyproxy.io/docs/envoy/latest/api-v3/extensions/filters/network/http_connection_manager/v3/http_connection_manager.proto&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Apache HTTP Server, core directives (&lt;code&gt;LimitRequestFieldSize&lt;/code&gt;, &lt;code&gt;LimitRequestFields&lt;/code&gt;, &lt;code&gt;LimitRequestLine&lt;/code&gt;): &lt;a href="https://httpd.apache.org/docs/2.4/mod/core.html" rel="noopener noreferrer"&gt;https://httpd.apache.org/docs/2.4/mod/core.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes blog, “Ingress NGINX Retirement: What You Need to Know”: &lt;a href="https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/" rel="noopener noreferrer"&gt;https://kubernetes.io/blog/2025/11/11/ingress-nginx-retirement/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Kubernetes blog, “Ingress NGINX: Statement from the Kubernetes Steering and Security Response Committees”: &lt;a href="https://www.kubernetes.io/blog/2026/01/29/ingress-nginx-statement/" rel="noopener noreferrer"&gt;https://www.kubernetes.io/blog/2026/01/29/ingress-nginx-statement/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/advanced-webhook-operations-resiliency-architecture-patterns-for-production-engi" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/advanced-webhook-operations-resiliency-architecture-patterns-for-production-engi&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Enterprise Webhook Architecture at Scale: High-Throughput Ingress, Multi-Region CRDTs, and Adaptive Backpressure</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Tue, 27 Oct 2026 02:05:03 +0000</pubDate>
      <link>https://dev.to/instawebhook/enterprise-webhook-architecture-at-scale-high-throughput-ingress-multi-region-crdts-and-adaptive-1kcn</link>
      <guid>https://dev.to/instawebhook/enterprise-webhook-architecture-at-scale-high-throughput-ingress-multi-region-crdts-and-adaptive-1kcn</guid>
      <description>&lt;p&gt;Webhook traffic is bursty and outside your control. Senders fire when something happens, such as a flash sale, a settlement batch or a fleet of devices reconnecting, and they all do it at once. This article covers three layers that together keep an ingestion tier alive under that load: an in-memory ingress buffer, active-active multi-region replication, and adaptive load shedding. For each layer it also covers the trade-offs, which matter as much as the mechanisms.&lt;/p&gt;

&lt;p&gt;A note on numbers: “500,000 requests per second” is used here as a design target, not a benchmark. The real ceiling depends on payload size, TLS termination, core count and NIC bandwidth, so measure on your own hardware.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 1: RAM-Disk Ingress Buffers — Using Linux &lt;code&gt;tmpfs&lt;/code&gt; and Shared Memory to Absorb Webhook Bursts
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why senders force this design
&lt;/h3&gt;

&lt;p&gt;Major webhook senders expect a fast acknowledgment and do the waiting for you. Stripe’s documentation tells receivers to &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;return a 2xx immediately and defer complex logic&lt;/a&gt;. In live mode it retries failed deliveries for up to three days with exponential backoff. GitHub expects a 2xx &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;within 10 seconds&lt;/a&gt; and otherwise terminates the connection and records a failure. It also &lt;a href="https://docs.github.com/webhooks/using-webhooks/handling-failed-webhook-deliveries" rel="noopener noreferrer"&gt;does not automatically redeliver failed deliveries&lt;/a&gt;, so a missed delivery stays missed until someone redelivers it.&lt;/p&gt;

&lt;p&gt;The consequence is that the hot path should do as little as possible before acknowledging. That means validating the signature, appending the payload to a buffer and returning. Writing each payload straight into a relational database, or into a broker configured for synchronous disk persistence, puts storage latency inside the sender’s timeout window. During a spike this produces timeouts, and the timeouts produce retries that add to the spike.&lt;/p&gt;

&lt;h3&gt;
  
  
  How &lt;code&gt;tmpfs&lt;/code&gt; behaves
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;tmpfs&lt;/code&gt; keeps its contents in the kernel’s page cache and, if swap is enabled, can push unneeded pages out to swap. According to the &lt;a href="https://git.zx2c4.com/linux-rng/tree/Documentation/filesystems/tmpfs.rst" rel="noopener noreferrer"&gt;kernel documentation&lt;/a&gt;, a few details matter for an ingress buffer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The default size limit is half of physical RAM, and &lt;code&gt;nr_inodes&lt;/code&gt; caps the inode count.&lt;/li&gt;
&lt;li&gt;Swap is enabled by default. The &lt;code&gt;noswap&lt;/code&gt; mount option disables it, which you want here, because a buffer that silently spills to swap gives up the latency profile you built it for.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;/dev/shm&lt;/code&gt; is normally a &lt;code&gt;tmpfs&lt;/code&gt; mount, so POSIX shared memory (&lt;code&gt;shm_open&lt;/code&gt;) and files under a dedicated &lt;code&gt;tmpfs&lt;/code&gt; mount draw on the same memory pool.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# 64 GiB buffer, no swap, owner-only access&lt;/span&gt;
mount &lt;span class="nt"&gt;-t&lt;/span&gt; tmpfs &lt;span class="nt"&gt;-o&lt;/span&gt; &lt;span class="nv"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;64G,nr_inodes&lt;span class="o"&gt;=&lt;/span&gt;2M,mode&lt;span class="o"&gt;=&lt;/span&gt;0700,noswap tmpfs /mnt/ingress-buffer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Memory in &lt;code&gt;tmpfs&lt;/code&gt; is real RAM that other processes can no longer use, so alert on &lt;code&gt;df&lt;/code&gt; usage and size the mount against your worst-case burst.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Sizing arithmetic (illustrative).&lt;/strong&gt; Assume an average payload of 4 KiB at 500,000 RPS. That is roughly 1.9 GiB/s of inbound payload data, so a 64 GiB buffer absorbs about 33 seconds of sustained peak if nothing drains. Two consequences follow:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The buffer buys you time but is not unlimited. The drain path must be sized to catch up after the burst.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Do not write one file per webhook.&lt;/strong&gt; At 500,000 RPS, a 2M-inode limit would be exhausted in about four seconds. Append payloads to large segment files instead, for example 64–256 MiB each, with a small length-prefixed record header.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Ring buffers and &lt;code&gt;io_uring&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The pipeline looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client TLS / HTTP ──► Edge proxy (Rust or Go; epoll or io_uring)
                            │  validate signature, enforce size limits
                            ▼
                 Append-only segment files on tmpfs
                            │
                            ▼
               Drain workers (user space, batched)
                            │
                            ▼
           Local NVMe log ► Kafka / object storage
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A webhook endpoint needs TCP and TLS termination, so the data path goes through the normal kernel network stack. XDP and eBPF are useful earlier in the path, for dropping obviously bad traffic at the NIC before it costs you CPU. They don’t replace the HTTP server.&lt;/p&gt;

&lt;p&gt;Within the proxy, lock-free single-producer/single-consumer rings (one per worker thread) are a common way to hand records to a writer without contention. &lt;code&gt;io_uring&lt;/code&gt; can then batch the file writes.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;io_uring&lt;/code&gt; has an &lt;code&gt;IORING_SETUP_SQPOLL&lt;/code&gt; mode in which the kernel creates a thread that polls the submission queue. The &lt;a href="https://man7.org/linux/man-pages/man2/io_uring_setup.2.html" rel="noopener noreferrer"&gt;man page&lt;/a&gt; says this lets an application issue I/O without context switching into the kernel, with two caveats worth knowing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If the polling thread is idle for longer than &lt;code&gt;sq_thread_idle&lt;/code&gt; milliseconds, it goes to sleep and sets a flag. The application must then call &lt;code&gt;io_uring_enter(2)&lt;/code&gt; to wake it. SQPOLL reduces system calls under sustained load, but it does not eliminate them.&lt;/li&gt;
&lt;li&gt;Unprivileged SQPOLL requires a newer kernel (5.13, per the man page). The polling thread also occupies a CPU, so budget cores for it. The &lt;a href="https://man7.org/linux/man-pages/man7/io_uring_sqpoll.7.html" rel="noopener noreferrer"&gt;&lt;code&gt;io_uring_sqpoll(7)&lt;/code&gt;&lt;/a&gt; page notes that it only affects submissions, so completion handling still costs CPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Some hardened or containerized environments restrict &lt;code&gt;io_uring&lt;/code&gt;, so confirm it is permitted on your hosts before building around it.&lt;/p&gt;

&lt;p&gt;An illustrative sketch with the Rust &lt;a href="https://crates.io/crates/io-uring" rel="noopener noreferrer"&gt;&lt;code&gt;io-uring&lt;/code&gt;&lt;/a&gt; crate follows. Check the API against the crate version you pin:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;io_uring&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;opcode&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;IoUring&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;OpenOptions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;os&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;unix&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AsRawFd&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// SQPOLL: a kernel thread polls the submission queue and sleeps after 1000 ms idle.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;IoUring&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;builder&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.setup_sqpoll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;256&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;OpenOptions&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/mnt/ingress-buffer/segment-000001.log"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;b"{&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;event&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;:&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;example&lt;/span&gt;&lt;span class="se"&gt;\"&lt;/span&gt;&lt;span class="s"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;write&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;opcode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="nn"&gt;types&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;Fd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file&lt;/span&gt;&lt;span class="nf"&gt;.as_raw_fd&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="nf"&gt;.as_ptr&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
        &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.offset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;.build&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="nf"&gt;.user_data&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Safety: `payload` and `file` outlive the completion below.&lt;/span&gt;
    &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.submission&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.push&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"submission queue full"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.submit_and_wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;cqe&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ring&lt;/span&gt;&lt;span class="nf"&gt;.completion&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.next&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"completion missing"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nd"&gt;assert!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="nf"&gt;.result&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"write failed: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;cqe&lt;/span&gt;&lt;span class="nf"&gt;.result&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
    &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  The durability trade-off
&lt;/h3&gt;

&lt;p&gt;A buffer in RAM does not survive a power failure or kernel panic, and &lt;code&gt;fsync()&lt;/code&gt; on &lt;code&gt;tmpfs&lt;/code&gt; buys nothing because there is no backing store. Acknowledging with &lt;code&gt;202 Accepted&lt;/code&gt; after a &lt;code&gt;tmpfs&lt;/code&gt; write therefore creates a window in which the sender believes the event is safe but your platform could lose it. Because senders only retry on failure, a lost event that was acknowledged is not retried.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml" rel="noopener noreferrer"&gt;RFC 9110 defines &lt;code&gt;202&lt;/code&gt;&lt;/a&gt; as acceptance for processing, not completion. That fits the pattern, but it doesn’t make the data durable. There are three common ways to narrow the window:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Replicate before acknowledging.&lt;/strong&gt; Copy the record into a second host’s memory in another failure domain and acknowledge after both writes succeed. This costs a network round trip inside a region, typically on the order of a millisecond between availability zones, according to &lt;a href="https://maisonbisson.com/post/inter-az-cloud-network-performance/" rel="noopener noreferrer"&gt;ThousandEyes measurements&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Group-commit to local NVMe.&lt;/strong&gt; Acknowledge after a batched, sequential append to a local log. This is slower than RAM but far cheaper than per-request database writes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Accept the window deliberately&lt;/strong&gt; for low-value event classes, and reconcile against the sender’s API or redelivery tools after an incident.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick one per event class and document it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Draining to persistent storage
&lt;/h3&gt;

&lt;p&gt;Drain workers run in user space. They read sealed segments from &lt;code&gt;tmpfs&lt;/code&gt;, batch them and write them onward. A few practical points:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Object storage.&lt;/strong&gt; S3 multipart parts must be &lt;a href="https://docs.aws.amazon.com/us_en/AmazonS3/latest/userguide/qfacts.html" rel="noopener noreferrer"&gt;5 MiB to 5 GiB, except the last part, with at most 10,000 parts per upload&lt;/a&gt;. Use chunks of 8–16 MiB or larger. Chunks below 5 MiB are rejected for non-final parts.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Direct I/O.&lt;/strong&gt; &lt;code&gt;O_DIRECT&lt;/code&gt; is relevant when writing to a local disk log, not when uploading over HTTP. &lt;code&gt;tmpfs&lt;/code&gt; itself gained &lt;code&gt;O_DIRECT&lt;/code&gt; support in &lt;a href="https://lists.linux.it/pipermail/ltp/2023-November/036146.html" rel="noopener noreferrer"&gt;Linux 6.6&lt;/a&gt;; on older kernels, opening a &lt;code&gt;tmpfs&lt;/code&gt; file with it fails. The drain side can simply use ordinary reads or &lt;code&gt;mmap&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Idempotency.&lt;/strong&gt; Draining can crash midway and replay a segment. Give every record a stable ID, such as the sender’s delivery ID (GitHub supplies &lt;code&gt;X-GitHub-Delivery&lt;/code&gt;) or a hash, so downstream consumers can deduplicate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Part 2: Active-Active Cross-Region Webhook Ingress — Convergence with CRDTs and Vector Clocks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why active-active
&lt;/h3&gt;

&lt;p&gt;Consider a provider serving customers from AWS &lt;code&gt;us-east-1&lt;/code&gt; (N. Virginia), &lt;code&gt;eu-central-1&lt;/code&gt; (Frankfurt) and &lt;code&gt;ap-southeast-1&lt;/code&gt; (Singapore). Each region should accept webhooks locally, so that a regional outage or a slow inter-region link doesn’t block ingestion.&lt;/p&gt;

&lt;p&gt;To steer clients to a nearby region, there are two options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anycast.&lt;/strong&gt; The same IP is announced from multiple locations and network routing (BGP) delivers each client to a nearby one. Services such as AWS Global Accelerator work this way.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;DNS-based routing&lt;/strong&gt; (latency or geolocation policies). This is a separate mechanism from anycast. Don’t conflate them: anycast operates at the network layer, not through DNS.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A webhook sender such as Stripe will use whichever endpoint you give it. If that is a single hostname, steering happens on your side.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why not synchronous consensus across regions
&lt;/h3&gt;

&lt;p&gt;Quorum protocols such as Paxos or Raft need at least one round trip to a majority of replicas per decision. Inside a region that is around a millisecond. Across continents it is limited by distance and routing, and typically ranges from tens to hundreds of milliseconds. AWS publishes measured inter-region numbers through &lt;a href="https://docs.aws.amazon.com/network-manager/latest/infrastructure-performance/how-nmip-works.html" rel="noopener noreferrer"&gt;Network Manager Infrastructure Performance&lt;/a&gt;. Putting that on the acknowledgment path eats into the sender’s timeout budget and ties availability to the slowest link. The alternative is to accept writes locally and replicate asynchronously, and to design the data model so that replicas converge.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminology: conflicts, not split-brain
&lt;/h3&gt;

&lt;p&gt;“Split-brain” usually describes two nodes both believing they are the sole leader. In an active-active design, every region is &lt;em&gt;supposed&lt;/em&gt; to accept writes, so the real problem is concurrent, possibly conflicting updates to the same entity, plus out-of-order delivery. Two tools address this: vector clocks, which detect concurrency, and CRDTs, which merge it deterministically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Vector clocks
&lt;/h3&gt;

&lt;p&gt;Each entity carries a vector clock, a map from region ID to a counter. A region increments its own entry when it records an event and takes the element-wise maximum when it receives remote state.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Region A: {A:1, B:0, C:0}  ← event 1
Region B: {A:0, B:1, C:0}  ← event 2 (neither saw the other: concurrent)
After exchange: merged clock {A:1, B:1, C:0}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For vector clocks V₁ and V₂ over N regions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;V₁ ≤ V₂ if V₁[i] ≤ V₂[i] for every region i.&lt;/li&gt;
&lt;li&gt;V₁ causally precedes V₂ if V₁ ≤ V₂ and V₁[j] &amp;lt; V₂[j] for at least one j.&lt;/li&gt;
&lt;li&gt;If neither V₁ ≤ V₂ nor V₂ ≤ V₁, the events are &lt;strong&gt;concurrent&lt;/strong&gt;, and the merge rule decides the outcome.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Vector clocks grow with the number of participants. That is fine for three or five regions but not for per-client identifiers. Keep them per region, not per sender.&lt;/p&gt;

&lt;p&gt;Many systems pair this with &lt;strong&gt;hybrid logical clocks&lt;/strong&gt;, which combine physical time with a logical counter, so that timestamps stay close to wall-clock time yet never go backwards. They are useful as a tiebreaker or for ordering display, but wall clocks alone are not a safe basis for correctness.&lt;/p&gt;

&lt;h3&gt;
  
  
  CRDTs
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://perso.lip6.fr/Marc.Shapiro/papers/replicated-data-types-Encyclopedia-DB-systems-2016-authorversion.pdf" rel="noopener noreferrer"&gt;Shapiro, Preguiça, Baquero and Zawirski’s 2011 study&lt;/a&gt; formalized two families of conflict-free replicated data types. Both guarantee that replicas converge without foreground coordination:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;State-based (CvRDT):&lt;/strong&gt; replicas exchange full or delta state and merge it with an associative, commutative, idempotent function.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operation-based (CmRDT):&lt;/strong&gt; replicas exchange operations that commute, which requires reliable, exactly-once delivery of those operations.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Useful building blocks for a webhook platform include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;PN-Counters&lt;/strong&gt; for approximate global metrics such as delivery attempts or events ingested. They converge, but they cannot enforce a strict global limit, because that would require coordination. Treat a PN-Counter-based rate limit as approximate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Observed-Remove Sets (OR-Sets)&lt;/strong&gt; for subscription or endpoint registries, where concurrent adds and removes must converge predictably. An add concurrent with a remove of the same element wins.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;LWW registers&lt;/strong&gt; for single-value fields where “latest write wins” is acceptable.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here is a last-writer-wins register that uses the vector clock for causality and falls back to a timestamp only for truly concurrent writes. The original idea of merging clocks but then deciding on the timestamp alone can silently discard a causally &lt;em&gt;newer&lt;/em&gt; update when clocks are skewed, so the causal check comes first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;LwwRegister&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;clock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// vector clock: region -&amp;gt; counter&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;hlc_micros&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;             &lt;span class="c1"&gt;// hybrid logical timestamp&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="n"&gt;replica_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;          &lt;span class="c1"&gt;// deterministic tiebreaker&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="n"&gt;Causality&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;Before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;After&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Concurrent&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;HashMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Causality&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;a_less&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;b_less&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;k&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nf"&gt;.keys&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.chain&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nf"&gt;.keys&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;a&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.copied&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;&lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;k&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.copied&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.unwrap_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;a_less&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;x&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;y&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;b_less&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;a_less&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;b_less&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Before&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;After&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Concurrent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Clone&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;LwwRegister&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;merge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;remote_wins&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="nf"&gt;compare&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.clock&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.clock&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Before&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                      &lt;span class="c1"&gt;// remote is causally newer&lt;/span&gt;
            &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;After&lt;/span&gt; &lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;// local is newer or identical&lt;/span&gt;
            &lt;span class="nn"&gt;Causality&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Concurrent&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Timestamp first, replica ID as a deterministic tiebreaker&lt;/span&gt;
                &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.hlc_micros&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.replica_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.hlc_micros&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.replica_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;};&lt;/span&gt;

        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.clock&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.clock&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="nf"&gt;.entry&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="nf"&gt;.or_insert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remote_wins&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.value&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.hlc_micros&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.hlc_micros&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
            &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.replica_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;remote&lt;/span&gt;&lt;span class="py"&gt;.replica_id&lt;/span&gt;&lt;span class="nf"&gt;.clone&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.clock&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;merged&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two cautions apply. First, with a concurrent update, LWW keeps one value and discards the other. That is acceptable for something like “current endpoint URL” but not for data where both writes matter. For those, use a multi-value register or a domain-specific merge. Second, because senders may deliver the same event more than once and don’t guarantee ordering, receivers should be idempotent regardless. Stripe’s documentation says as much, and GitHub exposes a per-delivery ID header for deduplication.&lt;/p&gt;

&lt;h2&gt;
  
  
  Part 3: Adaptive Backpressure — Shedding Load Gracefully Before Downstream Saturation Hits 100%
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The failure mode
&lt;/h3&gt;

&lt;p&gt;Even with a RAM buffer and multiple regions, downstream consumers can saturate: a database exhausts connections, a partner endpoint slows down, or workers fall behind. Fixed rate limits struggle here because the right limit depends on how much the downstream can handle at that moment. The goal is to start slowing intake &lt;em&gt;before&lt;/em&gt; the queue is full, and to slow the least important traffic first.&lt;/p&gt;

&lt;h3&gt;
  
  
  Little’s Law
&lt;/h3&gt;

&lt;p&gt;For a stable system in steady state, Little’s Law states:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;L = λ × W&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;where L is the average number of items in the system, λ is the average arrival rate, and W is the average time an item spends in the system.&lt;/p&gt;

&lt;p&gt;The practical use is as a sizing and sanity check. If downstream slows and W rises while λ stays constant, L must rise. More precisely, once the arrival rate exceeds the service rate, the steady-state assumption no longer holds and the queue grows until it hits a limit, which for an unbounded in-memory queue means running out of memory. Queueing delay also climbs sharply as utilization approaches 100%, so waiting until the queue is nearly full is too late. Shedding reduces λ to something the downstream can sustain. A bounded queue turns an eventual crash into an explicit, controlled rejection.&lt;/p&gt;

&lt;h3&gt;
  
  
  Saturation signal and dynamic token bucket
&lt;/h3&gt;

&lt;p&gt;Define queue saturation as:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;S = current queue depth / maximum queue capacity&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A token bucket with a fixed refill rate cannot react to S. Instead, scale the refill rate down as S rises between a low and high watermark. For example, with S_low = 0.75 and S_high = 0.90:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;R = R_max × (1 − clamp((S − S_low) / (S_high − S_low), 0, 1))&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Below S_low, requests get the full rate. Above S_high, low-priority traffic is refused entirely. Many systems use AIMD (additive increase, multiplicative decrease) on the admitted rate instead, borrowing from TCP congestion control. Either way, feed the controller a smoothed signal such as queue depth plus drain rate, so it doesn’t oscillate.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming webhook ──► classify priority ──► token bucket (rate scaled by S)
                                              │
                          ┌───────────────────┴──────────────────┐
                      tokens available                       no tokens
                          │                                      │
                  append to buffer ──► 202                 429 + Retry-After
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  HTTP semantics: &lt;code&gt;429&lt;/code&gt; and &lt;code&gt;Retry-After&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml" rel="noopener noreferrer"&gt;RFC 6585&lt;/a&gt; defines &lt;code&gt;429 Too Many Requests&lt;/code&gt;. A &lt;code&gt;Retry-After&lt;/code&gt; header may accompany it, either as a number of seconds or as an HTTP date. Add jitter so that all clients don’t retry at the same instant. AWS’s “Exponential Backoff And Jitter” article is a good reference on why. A formula consistent with the thresholds below:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;delay = min(MaxBackoff, BaseDelay × 2^(k × p)) × jitter&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;where p = clamp((S − S_low) / (1 − S_low), 0, 1), k is a steepness constant and jitter is a random factor, for example between 0.5 and 1.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;backpressure&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"math"&lt;/span&gt;
    &lt;span class="s"&gt;"math/rand"&lt;/span&gt;
    &lt;span class="s"&gt;"strconv"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;lowWatermark&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;0.75&lt;/span&gt;
    &lt;span class="n"&gt;baseDelaySecs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;5.0&lt;/span&gt;
    &lt;span class="n"&gt;maxDelaySecs&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;120.0&lt;/span&gt;
    &lt;span class="n"&gt;steepness&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;4.0&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// RetryAfter returns a Retry-After value in seconds, or ok=false when no&lt;/span&gt;
&lt;span class="c"&gt;// throttling applies.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;RetryAfter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;saturation&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ok&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;saturation&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;lowWatermark&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s"&gt;""&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;saturation&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lowWatermark&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;lowWatermark&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseDelaySecs&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;steepness&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;maxDelaySecs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;delay&lt;/span&gt; &lt;span class="o"&gt;*=&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="m"&gt;0.5&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;rand&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Float64&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c"&gt;// jitter between 50% and 100% of delay&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Itoa&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;))),&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With these constants, the pre-jitter delay is 5 s at S = 0.75, about 26 s at S = 0.90 and 80 s at S = 1.0.&lt;/p&gt;

&lt;h3&gt;
  
  
  Not every sender will cooperate
&lt;/h3&gt;

&lt;p&gt;Shedding with &lt;code&gt;429&lt;/code&gt; only helps if the sender retries. The differences between senders are large:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Stripe retries failed deliveries in live mode for up to three days with exponential backoff, so a &lt;code&gt;429&lt;/code&gt; or &lt;code&gt;503&lt;/code&gt; costs a delay, not data.&lt;/li&gt;
&lt;li&gt;GitHub does not automatically redeliver failed deliveries, so a shed GitHub webhook is lost until someone redelivers it manually or via the API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So classify by what the sender will do, not only by business importance. Events whose senders won’t retry should be the last to be shed, and should preferably go to the durable buffer or a spill queue. Where you must drop them, log enough to reconcile later. A &lt;code&gt;503 Service Unavailable&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt; is also a reasonable shed response for tiers that are unavailable rather than rate-limited.&lt;/p&gt;

&lt;h3&gt;
  
  
  Shedding states
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;State&lt;/th&gt;
&lt;th&gt;Queue saturation (S)&lt;/th&gt;
&lt;th&gt;Ingress action&lt;/th&gt;
&lt;th&gt;Response&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Normal&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S ≤ 0.75&lt;/td&gt;
&lt;td&gt;Accept all traffic into the buffer&lt;/td&gt;
&lt;td&gt;&lt;code&gt;202 Accepted&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Degraded&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;0.75 &amp;lt; S ≤ 0.90&lt;/td&gt;
&lt;td&gt;Shed low-priority events (for example, analytics pings) with a growing delay&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;429&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt; of roughly 5–26 s&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tripped&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;S &amp;gt; 0.90&lt;/td&gt;
&lt;td&gt;Reject all non-critical events; keep accepting critical ones until the hard limit&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;429&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt; of roughly 26–80 s; &lt;code&gt;503&lt;/code&gt; once the hard limit is hit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These are starting points, not universal constants. Tune watermarks against measured drain rates and your senders’ retry behavior.&lt;/p&gt;

&lt;p&gt;Export the state machine and its inputs (queue depth, drain rate, admitted vs. rejected counts per priority, current &lt;code&gt;Retry-After&lt;/code&gt;) as Prometheus or OpenTelemetry metrics. Alert on transitions, not only on levels, because a system that flaps between Normal and Degraded is a tuning problem you want to see.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting it together
&lt;/h2&gt;

&lt;p&gt;The three layers address different failure modes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;RAM-backed ingress&lt;/strong&gt; keeps acknowledgment latency low during bursts, in exchange for a deliberate durability decision (replicate, group-commit or accept the window) and careful sizing of &lt;code&gt;tmpfs&lt;/code&gt;, inodes and swap.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Active-active regions with vector clocks and CRDTs&lt;/strong&gt; keep ingestion available across regions without cross-continent consensus on the hot path. The cost is eventual consistency, and conflict semantics you must choose per data type.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Adaptive backpressure&lt;/strong&gt; keeps downstream failures from becoming memory exhaustion, provided the shedding policy accounts for which senders retry.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No design makes webhook ingestion lossless under all failures. What it can do is make the trade-offs explicit and measurable, so that overload degrades the least valuable traffic first and the data you’ve promised to keep survives.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/enterprise-webhook-architecture-at-scale-high-throughput-ingress-multi-region-cr" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/enterprise-webhook-architecture-at-scale-high-throughput-ingress-multi-region-cr&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
    </item>
    <item>
      <title>Zero-Trust Webhook Ingress: Enforcing Cryptographic Sender Identity with DNSSEC, DANE, and Verified mTLS</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Mon, 26 Oct 2026 02:03:58 +0000</pubDate>
      <link>https://dev.to/instawebhook/zero-trust-webhook-ingress-enforcing-cryptographic-sender-identity-with-dnssec-dane-and-verified-2lpo</link>
      <guid>https://dev.to/instawebhook/zero-trust-webhook-ingress-enforcing-cryptographic-sender-identity-with-dnssec-dane-and-verified-2lpo</guid>
      <description>&lt;p&gt;Webhooks are how many B2B SaaS integrations learn that something happened: a payment settled, a resource was provisioned, an order shipped. Each webhook endpoint is also a public HTTPS listener that accepts state-changing requests from systems you don’t control.&lt;/p&gt;

&lt;p&gt;Most webhook security still lives at the application layer: shared HMAC secrets, static tokens, or IP allowlists. These are workable, and for most providers they are all that’s offered. They also have well-known limits. This guide looks at how to push sender authentication down to the transport layer with &lt;strong&gt;mutual TLS (mTLS)&lt;/strong&gt; enforced at the edge, how &lt;strong&gt;DNSSEC&lt;/strong&gt; and &lt;strong&gt;DANE-style TLSA records&lt;/strong&gt; can serve as a tamper-evident place to publish sender key pins, and how to degrade gracefully for senders that can’t support any of this.&lt;/p&gt;

&lt;p&gt;One caveat up front. mTLS for webhooks is real and supported by some providers. Using DNSSEC-signed TLSA records to pin &lt;em&gt;client&lt;/em&gt; certificates is &lt;strong&gt;not a standardized practice&lt;/strong&gt;. It is a design pattern built from standardized parts, and this article is explicit about where the standards stop and the engineering begins.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Why Shared Secrets and IP Allowlists Fall Short
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How HMAC webhook signing works
&lt;/h3&gt;

&lt;p&gt;In the common model, the provider and consumer share a secret. For each event, the provider computes an HMAC (typically HMAC-SHA256) over the raw request body and sends it in a header:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="nf"&gt;POST&lt;/span&gt; &lt;span class="nn"&gt;/v1/webhooks/orders&lt;/span&gt; &lt;span class="k"&gt;HTTP&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="m"&gt;1.1&lt;/span&gt;
&lt;span class="na"&gt;Host&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;api.example.com&lt;/span&gt;
&lt;span class="na"&gt;X-Hub-Signature-256&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;sha256=a3f5c9e2b1...&lt;/span&gt;
&lt;span class="na"&gt;Content-Type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;application/json&lt;/span&gt;

&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nl"&gt;"event"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"order.fulfilled"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"order_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"99214"&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;GitHub, for example, signs webhook deliveries with &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;, the SHA-256 successor to the older SHA-1 &lt;code&gt;X-Hub-Signature&lt;/code&gt; header. Stripe uses a &lt;code&gt;Stripe-Signature&lt;/code&gt; header that carries a timestamp and one or more &lt;code&gt;v1&lt;/code&gt; HMAC-SHA256 signatures computed over the string &lt;code&gt;{timestamp}.{body}&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Done correctly, this gives you payload integrity and proof that the sender held the secret. The limitations are structural:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Symmetric secrets must be distributed and rotated.&lt;/strong&gt; Both sides hold the same key, so anyone who can read it can forge events, and the consumer can forge events “from” the provider. Rotation without dropping deliveries needs an overlap window in which two secrets are accepted. Stripe’s header format supports multiple signatures for this reason.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Replay protection depends on the scheme.&lt;/strong&gt; A signature over only the body can be replayed indefinitely. Stripe signs a timestamp together with the body, and its official libraries reject events outside a default tolerance of five minutes. GitHub’s signature covers only the payload, so receivers need other controls, such as deduplicating on the delivery ID. Timestamp checks in turn require reasonably synchronized clocks, and clock drift is a common cause of spurious signature failures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The signature identifies a secret, not a network or cryptographic identity.&lt;/strong&gt; It says “someone with this key sent this,” nothing more. It is checked after your edge, TLS terminator, and HTTP parser have already accepted and processed the request.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Why IP allowlists are a weak identity signal
&lt;/h3&gt;

&lt;p&gt;Many providers publish source IP ranges. GitHub exposes its hook delivery ranges through its meta API, and Stripe publishes lists in its documentation. Allowlisting them is reasonable &lt;strong&gt;defense in depth&lt;/strong&gt;, but a poor primary control:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lists change.&lt;/strong&gt; Providers add and remove egress ranges, and consumers have to track the changes or drop legitimate traffic.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP says nothing about which tenant sent the request.&lt;/strong&gt; An allowlisted range identifies the provider’s platform at best. It cannot tell you which customer or workload on that platform originated the request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shared egress widens the trust boundary.&lt;/strong&gt; Anything that can send traffic from the allowlisted range inherits the trust you granted.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Spoofing the source address of a full TCP/TLS exchange is hard in practice, so the real weakness is coarse granularity rather than easy forgery. Use allowlists to narrow exposure, not to establish identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. mTLS: Authenticating the Sender During the Handshake
&lt;/h2&gt;

&lt;p&gt;With mutual TLS, the sender presents an X.509 client certificate during the TLS handshake, and the receiver verifies it before any HTTP request is processed. Compared with HMAC headers:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The sender proves possession of a &lt;strong&gt;private key that never leaves it&lt;/strong&gt;, rather than a secret both parties know.&lt;/li&gt;
&lt;li&gt;Authentication happens &lt;strong&gt;before the request body is accepted&lt;/strong&gt;, so unauthenticated senders are rejected at the handshake or edge.&lt;/li&gt;
&lt;li&gt;Identity is a certificate (subject, SAN, fingerprint), which maps cleanly to a partner record.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is already offered by some webhook senders. Truv documents mTLS for webhook delivery, including delivery using a client-supplied certificate. Google’s Dialogflow supports mTLS when calling a customer’s webhook server: Dialogflow presents a client certificate, and the webhook server verifies it. Support is still the exception, so plan on a mix of sender capabilities (see Section 5).&lt;/p&gt;

&lt;h3&gt;
  
  
  A significant change: public CAs and client authentication
&lt;/h3&gt;

&lt;p&gt;Many teams used to run mTLS with certificates from public CAs, relying on certificates that carried both the &lt;code&gt;serverAuth&lt;/code&gt; and &lt;code&gt;clientAuth&lt;/code&gt; Extended Key Usages. That is ending. Driven by Chrome Root Program policy, public CAs are removing the &lt;code&gt;clientAuth&lt;/code&gt; EKU from newly issued certificates through 2026. Let’s Encrypt, for instance, stopped including it in certificates issued from mid-May 2026 (with an extended deadline for some users of its TLS-client profile), and other large CAs followed similar schedules. Failures tend to appear quietly when a certificate renews and no longer carries &lt;code&gt;clientAuth&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The practical consequence for webhook ingress is that you should &lt;strong&gt;not design around public-CA client certificates&lt;/strong&gt;. Use a private CA (yours or the partner’s), or pin individual partner certificates or public keys directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Where DNSSEC and DANE Fit (and Where They Don’t)
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What the standards define
&lt;/h3&gt;

&lt;p&gt;DANE (DNS-Based Authentication of Named Entities) is defined in RFC 6698, with operational guidance in RFC 7671. It lets a domain publish &lt;strong&gt;TLSA&lt;/strong&gt; records in DNS that bind a TLS server’s certificate or public key to a service name, and it depends on &lt;strong&gt;DNSSEC&lt;/strong&gt; to make those records tamper-evident. DNSSEC signs DNS data using DNSKEY and RRSIG records and links zones through DS records, forming a chain of trust from the DNS root zone down through the TLD (such as &lt;code&gt;.com&lt;/code&gt;) to the domain.&lt;/p&gt;

&lt;p&gt;A TLSA record is published at a name built from the &lt;strong&gt;port, protocol, and server hostname&lt;/strong&gt;, for example &lt;code&gt;_443._tcp.webhooks.example.com&lt;/code&gt;, and has four fields:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;_443._tcp.webhooks.example.com. IN TLSA 3 1 1 (
    a1b2c3d4e5f67890123456789abcdef0123456789abcdef0123456789abcdef0 )
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Certificate usage &lt;code&gt;3&lt;/code&gt; (DANE-EE):&lt;/strong&gt; the end-entity certificate or key itself is the trust anchor. No public CA is needed, so self-signed or privately issued certificates work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Selector &lt;code&gt;1&lt;/code&gt;:&lt;/strong&gt; match the SubjectPublicKeyInfo (SPKI) rather than the whole certificate, so the certificate can be re-issued without changing the record as long as the key pair stays the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Matching type &lt;code&gt;1&lt;/code&gt;:&lt;/strong&gt; compare a SHA-256 digest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Association data:&lt;/strong&gt; the digest itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;RFC 7671 recommends publishing records for both the current key and the next key ahead of a rollover, so key changes don’t cause validation failures while DNS caches expire.&lt;/p&gt;

&lt;p&gt;DANE is widely deployed for &lt;strong&gt;server authentication in SMTP&lt;/strong&gt;, defined in RFC 7672. For HTTPS, major browsers do not support it. This is why most web traffic still relies on the public CA ecosystem.&lt;/p&gt;

&lt;h3&gt;
  
  
  The gap for webhooks
&lt;/h3&gt;

&lt;p&gt;Standard DANE authenticates a &lt;strong&gt;server&lt;/strong&gt; by looking up TLSA records at a name derived from the server’s hostname. In webhook delivery, the sender is the &lt;strong&gt;TLS client&lt;/strong&gt;. A client sends no SNI naming itself, so there is no standardized way for a receiver to derive a DNS name from the handshake and check the client’s certificate against it. Any scheme that uses TLSA records for client identity is therefore a custom convention, not an interoperable standard.&lt;/p&gt;

&lt;p&gt;A workable version of the pattern looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;At onboarding, each partner gives you a DNS name they control (for example &lt;code&gt;webhook-client.partner.example&lt;/code&gt;) and agrees to publish its client-key pin there as a TLSA-format record, in a DNSSEC-signed zone.&lt;/li&gt;
&lt;li&gt;A &lt;strong&gt;scheduled sync job&lt;/strong&gt;, not the request path, queries that name using a DNSSEC-validating resolver, rejects results that do not validate, and writes the resulting fingerprints into your edge key-value store.&lt;/li&gt;
&lt;li&gt;The edge worker checks incoming client certificates against that store.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This gives you a partner-controlled place to rotate keys without a manual exchange for every rotation, plus tamper-evidence from DNSSEC. Know its costs before committing:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Partner adoption is limited.&lt;/strong&gt; DNSSEC signing is far from universal. Verisign reported a signing rate of about 4.3% for second-level domains in &lt;code&gt;.com&lt;/code&gt; and 5.3% in &lt;code&gt;.net&lt;/code&gt; in 2023, and a Q3 2025 report on DNSSEC validation put the global average validation rate at roughly 35%. Many partners will not be able to publish signed records.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You are trusting a validator.&lt;/strong&gt; Unless you run DNSSEC validation yourself, your trust rests on the resolver that performs it and on the path to it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Operational risk shifts to DNS.&lt;/strong&gt; A broken DS record or expired signatures can invalidate a partner’s pin set. Decide in advance whether the sync job fails closed (keep last-known-good pins for a bounded period) or open.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;It is non-standard.&lt;/strong&gt; Document the convention, version it, and expect each partner to need explanation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For many organizations, a simpler &lt;strong&gt;out-of-band registry of partner certificate fingerprints&lt;/strong&gt;, populated at onboarding and updated through an authenticated portal or API, provides most of the benefit with less moving machinery. DNSSEC-published pins are best treated as an optional enhancement for partners who can support them.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Enforcing mTLS at the Edge
&lt;/h2&gt;

&lt;p&gt;Terminating mTLS at an edge platform lets you reject unauthenticated senders before traffic reaches your origin, and lets you update trusted CAs and pinned certificates without redeploying application servers.&lt;/p&gt;

&lt;p&gt;On Cloudflare, for example, mTLS can be enabled per hostname and enforced with API Shield. Client certificate details are exposed to Workers in &lt;code&gt;request.cf.tlsClientAuth&lt;/code&gt;, with fields such as &lt;code&gt;certPresented&lt;/code&gt;, &lt;code&gt;certVerified&lt;/code&gt;, &lt;code&gt;certRevoked&lt;/code&gt;, &lt;code&gt;certFingerprintSHA256&lt;/code&gt;, &lt;code&gt;certSubjectDN&lt;/code&gt;, and &lt;code&gt;certSerial&lt;/code&gt;. The edge can also forward the certificate to the origin, either through Cloudflare-specific headers (&lt;code&gt;Cf-Client-Cert-Sha256&lt;/code&gt;, &lt;code&gt;Cf-Client-Cert-Der-Base64&lt;/code&gt;) or through headers following RFC 9440, which standardizes &lt;code&gt;Client-Cert&lt;/code&gt; and &lt;code&gt;Client-Cert-Chain&lt;/code&gt; headers for conveying client certificates from a TLS-terminating proxy to a backend. Other CDNs and gateways offer comparable features, but their field names and capabilities differ, so check your provider’s documentation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Worker pattern
&lt;/h3&gt;

&lt;p&gt;The worker below is a sketch of the pattern, written for Cloudflare Workers. Field value formats should be checked against the current documentation before use.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Edge gatekeeper: accept webhooks only from verified, pinned partner certificates.&lt;/span&gt;
&lt;span class="c1"&gt;// env.PARTNERS is a KV namespace mapping "fp:&amp;lt;sha256-fingerprint&amp;gt;" -&amp;gt; partner record.&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TRUST_HEADERS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-verified-sender-id&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-sender-cert-fingerprint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-ingress-auth-method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Method Not Allowed&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;Allow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;POST&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cf&lt;/span&gt;&lt;span class="p"&gt;?.&lt;/span&gt;&lt;span class="nx"&gt;tlsClientAuth&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;// 1. A certificate must have been presented and validated by the edge&lt;/span&gt;
    &lt;span class="c1"&gt;//    against the CA(s) you configured. Verify the exact value formats&lt;/span&gt;
    &lt;span class="c1"&gt;//    for these fields in your platform's documentation.&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certPresented&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certVerified&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;SUCCESS&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certRevoked&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Pin: the certificate must match a known partner.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;certFingerprintSHA256&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toLowerCase&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;partner&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PARTNERS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`fp:&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;partner&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Forbidden&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 3. Strip any client-supplied copies of our trust headers, then set our own.&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Headers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;TRUST_HEADERS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Verified-Sender-ID&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;partner&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Sender-Cert-Fingerprint&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fingerprint&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;set&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;X-Ingress-Auth-Method&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;mtls-pinned&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;request&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;headers&lt;/span&gt; &lt;span class="p"&gt;}));&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Points worth noting in this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The edge already checks validity.&lt;/strong&gt; Chain, expiry, and revocation status are evaluated by the platform when it verifies the client certificate, so the worker reads the result instead of re-parsing dates. Revocation behavior depends on the platform and how you configured it, so confirm what your provider checks and how quickly revocations take effect.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pinning adds a second control.&lt;/strong&gt; Chain validation says “a CA you trust issued this.” The fingerprint lookup says “this is a specific partner we onboarded.” If you use a private CA scoped to a single partner, the fingerprint check can be relaxed to a subject or SAN check.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strip inbound trust headers.&lt;/strong&gt; Clients can send their own &lt;code&gt;Client-Cert&lt;/code&gt;-style or custom headers. Cloudflare’s documentation explicitly warns about this. Remove any such headers before setting your own, as the worker does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Protect the origin path.&lt;/strong&gt; The origin must accept traffic only from the edge (for example, via authenticated origin pulls, a private tunnel, or network restrictions). Otherwise anyone who reaches the origin directly can forge the assertion headers. For higher assurance, have the edge sign the identity headers or pass a short-lived token the origin verifies.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Application-layer checks still matter.&lt;/strong&gt; mTLS authenticates the sender’s connection. It does not make a payload idempotent or protect against a compromised partner. Keep schema validation, idempotency keys, and replay detection.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What edge verification does and doesn’t buy you
&lt;/h3&gt;

&lt;p&gt;Moving authentication to the edge means unauthenticated traffic is dropped before it consumes origin resources, and trusted-certificate changes become a configuration update at the edge instead of an origin redeploy. Claims that it removes all cryptographic load from the origin or “eliminates” attack surface overreach. The origin still terminates its own TLS (or trusts an edge tunnel), parses requests, and handles business logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Tiered Fallback for Senders That Can’t Do mTLS
&lt;/h2&gt;

&lt;p&gt;Most webhook senders will not support client certificates, let alone DNSSEC-published pins. A realistic ingress layer treats trust as tiers and routes accordingly:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tier&lt;/th&gt;
&lt;th&gt;Mechanism&lt;/th&gt;
&lt;th&gt;What it establishes&lt;/th&gt;
&lt;th&gt;Handling&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;1: Pinned mTLS&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;mTLS with a pinned partner certificate or key (optionally DNSSEC-published)&lt;/td&gt;
&lt;td&gt;Sender proves possession of a private key tied to an onboarded partner&lt;/td&gt;
&lt;td&gt;Normal processing; strongest identity signal&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;2: Signed with replay protection&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;HMAC signature over timestamp and body, per-partner secrets, optional provider IP ranges&lt;/td&gt;
&lt;td&gt;Sender knew a shared secret; replay limited to the tolerance window&lt;/td&gt;
&lt;td&gt;Normal processing with signature, timestamp, and dedupe checks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;3: Legacy&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Static token or HMAC without timestamp&lt;/td&gt;
&lt;td&gt;Weakest; vulnerable to leaked secrets and replay&lt;/td&gt;
&lt;td&gt;Isolated queue, tighter rate limits, tracked for migration&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Implementation notes for the fallback paths:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Route by what the sender can do, not by what it claims.&lt;/strong&gt; Decide the tier from the verified handshake result and the partner registry, not from request headers the sender controls.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enforce replay windows where the scheme allows.&lt;/strong&gt; If the signature covers a timestamp, reject stale events. Stripe’s default tolerance is five minutes, and tighter windows are possible if clocks are well synchronized. Where the signature has no timestamp, deduplicate on the sender’s delivery or event ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use IP allowlists as an additional filter only.&lt;/strong&gt; Combine provider-published ranges with signature checks, and refresh the lists automatically since they change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Emit telemetry per tier.&lt;/strong&gt; Log which tier accepted each event and which partners remain on Tier 3, so migration progress is measurable and you can raise it with vendors.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Static shared secrets and IP allowlists remain the baseline in the webhook ecosystem, and they work when combined with timestamped signatures, secret rotation, and replay protection. Where a partner can support it, mTLS at the edge, with certificates pinned per partner and a private CA instead of a public one, moves sender authentication into the handshake and rejects unauthenticated traffic before it reaches your application.&lt;/p&gt;

&lt;p&gt;DNSSEC and TLSA records can add a tamper-evident, partner-controlled way to publish and rotate those pins. This is a custom pattern, not a standard, and DNSSEC adoption among partners is limited, so treat it as an optional layer on top of a straightforward partner registry. Around all of it, keep a tiered fallback so that senders who cannot meet the highest bar are still accepted under compensating controls instead of being cut off.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;RFC 6698, &lt;em&gt;The DNS-Based Authentication of Named Entities (DANE) Transport Layer Security (TLS) Protocol: TLSA&lt;/em&gt;: &lt;a href="https://www.rfc-editor.org/rfc/rfc6698" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc6698&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 7671, &lt;em&gt;The DANE Protocol: Updates and Operational Guidance&lt;/em&gt;: &lt;a href="https://www.rfc-editor.org/rfc/rfc7671" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc7671&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 7672, &lt;em&gt;SMTP Security via Opportunistic DANE TLS&lt;/em&gt;: &lt;a href="https://www.rfc-editor.org/rfc/rfc7672" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc7672&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 9440, &lt;em&gt;Client-Cert HTTP Header Field&lt;/em&gt;: &lt;a href="https://www.rfc-editor.org/rfc/rfc9440" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc9440&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloudflare Docs, &lt;em&gt;mTLS with Workers&lt;/em&gt;: &lt;a href="https://developers.cloudflare.com/learning-paths/mtls/mtls-workers" rel="noopener noreferrer"&gt;https://developers.cloudflare.com/learning-paths/mtls/mtls-workers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloudflare Docs, &lt;em&gt;Enable mTLS&lt;/em&gt;: &lt;a href="https://developers.cloudflare.com/ssl/client-certificates/enable-mtls/" rel="noopener noreferrer"&gt;https://developers.cloudflare.com/ssl/client-certificates/enable-mtls/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Truv Docs, &lt;em&gt;Security and mTLS&lt;/em&gt;: &lt;a href="https://docs.truv.com/api-reference/security" rel="noopener noreferrer"&gt;https://docs.truv.com/api-reference/security&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Google Cloud, &lt;em&gt;Dialogflow CX mutual TLS authentication&lt;/em&gt;: &lt;a href="https://docs.cloud.google.com/dialogflow/cx/docs/concept/mtls" rel="noopener noreferrer"&gt;https://docs.cloud.google.com/dialogflow/cx/docs/concept/mtls&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stripe Docs, &lt;em&gt;Webhooks&lt;/em&gt;: &lt;a href="https://docs.stripe.com/webhooks" rel="noopener noreferrer"&gt;https://docs.stripe.com/webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/zero-trust-webhook-ingress-enforcing-cryptographic-sender-identity-with-dnssec-d" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/zero-trust-webhook-ingress-enforcing-cryptographic-sender-identity-with-dnssec-d&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Zero-Allocation Webhook Parsers: Optimizing Go and Rust Ingress Buffers with SIMD</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Sun, 25 Oct 2026 04:24:19 +0000</pubDate>
      <link>https://dev.to/instawebhook/zero-allocation-webhook-parsers-optimizing-go-and-rust-ingress-buffers-with-simd-2olg</link>
      <guid>https://dev.to/instawebhook/zero-allocation-webhook-parsers-optimizing-go-and-rust-ingress-buffers-with-simd-2olg</guid>
      <description>&lt;p&gt;Webhook ingestion is a common bottleneck at the edge of event-driven systems. Payment processors, source-control platforms and IoT fleets can all send bursts of small JSON events to a single endpoint. Handlers built on general-purpose frameworks typically spend much of their CPU time on allocation and on building object trees for payloads that only need to be routed.&lt;/p&gt;

&lt;p&gt;This article covers where that cost comes from, what SIMD-accelerated JSON parsing can and cannot do about it, and how to structure a Go or Rust ingress gateway around it. It also covers the limits of “zero allocation”, which is better treated as a target than as a guarantee.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where Webhook Ingestion Spends Its Time
&lt;/h2&gt;

&lt;p&gt;Most webhook endpoints are designed like ordinary REST endpoints. At a few hundred requests per second, that is fine. Under a burst, the per-request overhead adds up.&lt;/p&gt;

&lt;p&gt;A typical handler does the following:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Reads the body into a freshly allocated buffer.&lt;/strong&gt; In Go, the standard &lt;code&gt;net/http&lt;/code&gt; server allocates a &lt;code&gt;Request&lt;/code&gt; and header map for every request, and &lt;code&gt;io.ReadAll&lt;/code&gt; grows a slice as it reads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Copies bytes into strings.&lt;/strong&gt; Converting &lt;code&gt;[]byte&lt;/code&gt; to &lt;code&gt;string&lt;/code&gt; in Go copies the data in the general case, although the compiler can avoid the copy in a few narrow cases such as map lookups and comparisons. Rust’s &lt;code&gt;String::from_utf8&lt;/code&gt; or &lt;code&gt;to_owned&lt;/code&gt; does the same.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Builds a full document tree.&lt;/strong&gt; Unmarshaling into &lt;code&gt;map[string]interface{}&lt;/code&gt; or &lt;code&gt;serde_json::Value&lt;/code&gt; allocates for every object, array and string, even when the handler only needs one field.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verifies the signature and routes.&lt;/strong&gt; Webhook signatures cover the exact bytes the sender transmitted, so the HMAC must be computed over the raw body. Re-serialized JSON will not verify.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;As a back-of-the-envelope figure, 50,000 requests per second at an average of 2 KB each is about 100 MB/s of payload. That is a modest amount of network traffic. The cost comes from the per-request allocations and tree building that sit on top of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  What this means for Go and for Rust
&lt;/h3&gt;

&lt;p&gt;Go’s collector is non-generational and concurrent (tri-color mark and sweep). It does not stop the world for long. It has two brief stop-the-world phases per cycle, which the runtime keeps in the sub-millisecond range. The main costs of a high allocation rate are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;CPU spent on background marking, which uses roughly 25% of CPU during a cycle.&lt;/li&gt;
&lt;li&gt;Mark assists, where goroutines that allocate quickly are conscripted into GC work.&lt;/li&gt;
&lt;li&gt;More frequent cycles as the heap grows and shrinks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For latency-sensitive services, even sub-millisecond pauses can be worth monitoring. A sustained p99 GC pause above roughly 10 ms is a sign that the collector is struggling with heap size.&lt;/p&gt;

&lt;p&gt;Rust has no garbage collector, so the issue there is different. Every &lt;code&gt;String&lt;/code&gt;, &lt;code&gt;Vec&lt;/code&gt; and &lt;code&gt;HashMap&lt;/code&gt; is a call into the allocator. Under high concurrency that means allocator contention, poor cache locality and per-request latency that depends on allocator state. The remedies are the same in spirit: borrow from the input buffer instead of copying, and reuse buffers instead of allocating new ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. What SIMD Brings to JSON Parsing
&lt;/h2&gt;

&lt;p&gt;SIMD (Single Instruction, Multiple Data) lets one instruction operate on a whole vector register. The common register widths are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Instruction set&lt;/th&gt;
&lt;th&gt;Register width&lt;/th&gt;
&lt;th&gt;Typical platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ARM NEON&lt;/td&gt;
&lt;td&gt;128 bits (16 bytes)&lt;/td&gt;
&lt;td&gt;aarch64&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVX2&lt;/td&gt;
&lt;td&gt;256 bits (32 bytes)&lt;/td&gt;
&lt;td&gt;x86-64 since about 2013&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AVX-512&lt;/td&gt;
&lt;td&gt;512 bits (64 bytes)&lt;/td&gt;
&lt;td&gt;Some recent x86-64 server CPUs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The reference design is &lt;strong&gt;simdjson&lt;/strong&gt;, by Geoff Langdale and Daniel Lemire. Their paper, &lt;em&gt;Parsing Gigabytes of JSON per Second&lt;/em&gt;, describes it as the first standard-compliant JSON parser to process gigabytes per second on a single core using commodity processors. It can use a quarter or fewer instructions than a state-of-the-art conventional parser such as RapidJSON.&lt;/p&gt;

&lt;p&gt;The design has two stages:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Stage 1 builds a structural index.&lt;/strong&gt; It processes the input in 64-byte blocks. It uses vector comparisons to find quotes, backslashes and structural characters (&lt;code&gt;{ } [ ] : ,&lt;/code&gt;) and produces bitmasks. This step also works out which bytes are inside strings and validates UTF-8.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stage 2 walks the index and builds a “tape”.&lt;/strong&gt; This is a flat array describing the document structure, not a tree of heap objects.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A few of the claims commonly made about SIMD parsing need correcting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It does not process a block “in a single CPU cycle”. It uses a small number of vector instructions per block, with no data-dependent branches in stage 1.&lt;/li&gt;
&lt;li&gt;It is not tied to AVX-512. simdjson’s reference implementation targets several instruction sets. The Rust port &lt;code&gt;simd-json&lt;/code&gt; supports AVX2, SSE4.2, NEON and WebAssembly SIMD128. &lt;code&gt;simdjson-go&lt;/code&gt; requires AVX2 and CLMUL, and uses AVX-512 only to speed up stage 1 where available.&lt;/li&gt;
&lt;li&gt;SIMD speeds up &lt;em&gt;parsing and validation&lt;/em&gt;. It does not remove the cost of reading from the socket, TLS, HTTP framing or signature verification.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For webhooks, the biggest win is often not raw parsing speed. A tape or on-demand parser lets you read one field, such as an event type, without constructing a full object model of a payload you are only going to forward.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Go: &lt;code&gt;simdjson-go&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;github.com/minio/simdjson-go&lt;/code&gt; is a pure-Go port of simdjson, with no cgo. According to its README:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It runs at roughly 40–60% of the speed of the C++ simdjson on average.&lt;/li&gt;
&lt;li&gt;It is about 10× faster than &lt;code&gt;encoding/json&lt;/code&gt;, in the project’s benchmarks.&lt;/li&gt;
&lt;li&gt;It is a validating parser, so malformed input is rejected rather than silently accepted.&lt;/li&gt;
&lt;li&gt;It needs a CPU with both AVX2 and CLMUL. That means Intel Haswell (2013) or later, or AMD Ryzen/EPYC (2017) or later.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two practical cautions apply before adopting it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Check maintenance status.&lt;/strong&gt; The latest tagged release on pkg.go.dev, v0.4.5, was published in March 2023. At least one fork (DataDog’s) has been archived. Review the repository’s current activity before committing to it in a production gateway.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The 10× figure is a benchmark result, not a promise.&lt;/strong&gt; It depends on payload shape and on how much of the document you read. Measure with your own payloads.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Reusing parser state
&lt;/h3&gt;

&lt;p&gt;&lt;code&gt;simdjson.Parse&lt;/code&gt; accepts a previously used &lt;code&gt;*ParsedJson&lt;/code&gt; to reuse, which avoids reallocating the tape and string buffers on each request. The sketch below shows the shape of a handler that reads a body into a pooled buffer, verifies the signature, and then extracts the event type. It is illustrative. Check the method names against the version of the package you pin.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;ingress&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/hmac"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/sha256"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/hex"&lt;/span&gt;
    &lt;span class="s"&gt;"io"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/minio/simdjson-go"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;maxBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c"&gt;// 1 MiB; reject anything larger&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;scratch&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;
    &lt;span class="n"&gt;pj&lt;/span&gt;   &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;simdjson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParsedJson&lt;/span&gt; &lt;span class="c"&gt;// reused between requests&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;scratch&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;make&lt;/span&gt;&lt;span class="p"&gt;([]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;simdjson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SupportedCPU&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"unsupported CPU"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusInternalServerError&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;scratch&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;pool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Read into the pooled buffer, bounded by maxBody.&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;NewBuffer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Copy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LimitReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxBody&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;maxBody&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"bad body"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusRequestEntityTooLarge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// 1. Verify the signature over the raw bytes, before parsing.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Hub-Signature-256"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// 2. Parse, reusing the previous ParsedJson where possible.&lt;/span&gt;
        &lt;span class="n"&gt;pj&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;simdjson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pj&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid JSON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pj&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pj&lt;/span&gt;

        &lt;span class="c"&gt;// 3. Walk the tape to read only the fields needed for routing,&lt;/span&gt;
        &lt;span class="c"&gt;//    then enqueue the raw body and return 2xx quickly.&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusAccepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sha256="&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add &lt;code&gt;"bytes"&lt;/code&gt; to the imports if you use this pattern. A few notes on what the sketch does and does not achieve:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It reduces allocations on the body-reading and parsing paths. It is &lt;strong&gt;not&lt;/strong&gt; allocation-free end to end. &lt;code&gt;net/http&lt;/code&gt; still allocates per request, and &lt;code&gt;hmac.New&lt;/code&gt; allocates unless you also pool the MAC. Servers that avoid the standard library’s per-request allocations, such as &lt;code&gt;fasthttp&lt;/code&gt;, trade API convenience for lower overhead.&lt;/li&gt;
&lt;li&gt;Verify with &lt;code&gt;go test -bench . -benchmem&lt;/code&gt; and look at &lt;code&gt;allocs/op&lt;/code&gt;. Treat that number, not the design intent, as the measure of success.&lt;/li&gt;
&lt;li&gt;If GC cost remains a concern, &lt;code&gt;GOGC&lt;/code&gt; and &lt;code&gt;GOMEMLIMIT&lt;/code&gt; (available since Go 1.19) are separate levers. &lt;code&gt;GOMEMLIMIT&lt;/code&gt; sets a soft memory limit, and combining it with a tuned &lt;code&gt;GOGC&lt;/code&gt; can reduce collection frequency. It trades memory for CPU.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Rust: Borrowing from the Input Buffer
&lt;/h2&gt;

&lt;p&gt;Two crates matter here, and they are easy to confuse:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;simd-json&lt;/code&gt;&lt;/strong&gt; is a Rust port of simdjson with &lt;code&gt;serde&lt;/code&gt; compatibility. It supports AVX2, SSE4.2, NEON and WASM SIMD128, with a native fallback. It offers borrowed and owned DOM value types and a lower-overhead tape API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;simdjson-rust&lt;/code&gt;&lt;/strong&gt; provides bindings to the C++ simdjson library. As of this writing, the latest version listed on docs.rs is a &lt;code&gt;0.4.0-alpha&lt;/code&gt; pre-release.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For production use, &lt;code&gt;simd-json&lt;/code&gt; is the more established choice.&lt;/p&gt;

&lt;p&gt;An important constraint follows from how &lt;code&gt;simd-json&lt;/code&gt; works: &lt;strong&gt;it parses in place and modifies the input buffer.&lt;/strong&gt; Its documentation states that the input must be &lt;code&gt;&amp;amp;mut [u8]&lt;/code&gt; because strings are unescaped in place. This affects design in two ways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parsed strings borrow from your buffer, so the borrow checker ties their lifetime to it. That is where the zero-copy benefit comes from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You must verify the HMAC before parsing.&lt;/strong&gt; After parsing, the buffer no longer holds the bytes the sender signed.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mac&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;sha2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;simd_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;prelude&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HmacSha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hmac&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Sha256&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;Ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Vec&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// reused across requests&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;Ingress&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;Self&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="nf"&gt;.to_vec&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Vec&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;with_capacity&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;capacity&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="cd"&gt;/// Verifies the signature, then reads the event type.&lt;/span&gt;
    &lt;span class="cd"&gt;/// Returns an owned `String` for the type only to keep the example&lt;/span&gt;
    &lt;span class="cd"&gt;/// simple; a hot path would route on a borrowed `&amp;amp;str` instead.&lt;/span&gt;
    &lt;span class="k"&gt;pub&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;sig_hex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;'static&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// 1. Verify over the raw bytes, before the parser mutates anything.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig_hex&lt;/span&gt;&lt;span class="nf"&gt;.trim_start_matches&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sha256="&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"bad signature encoding"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HmacSha256&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"bad key"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="nf"&gt;.update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="nf"&gt;.verify_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"signature mismatch"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// constant-time&lt;/span&gt;

        &lt;span class="c1"&gt;// 2. Copy into the reusable buffer; simd-json will modify it.&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buf&lt;/span&gt;&lt;span class="nf"&gt;.clear&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
        &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buf&lt;/span&gt;&lt;span class="nf"&gt;.extend_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// 3. Parse in place and read one field.&lt;/span&gt;
        &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;simd_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;to_borrowed_value&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.map_err&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="s"&gt;"invalid JSON"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;value&lt;/span&gt;
            &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;t&lt;/span&gt;&lt;span class="nf"&gt;.as_str&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
            &lt;span class="nf"&gt;.map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;str&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;to_owned&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.ok_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"missing event type"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Again, this is a sketch. Pin crate versions and check the API against them. Some caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;BorrowedValue&lt;/code&gt; is a DOM.&lt;/strong&gt; It borrows string data from the buffer, but it still allocates for objects and arrays. For a routing-only workload, the tape API reduces that overhead further. Benchmark both against your payloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The copy into &lt;code&gt;self.buf&lt;/code&gt; is deliberate.&lt;/strong&gt; If your network layer already gives you an owned, mutable buffer, you can verify and parse in place and skip it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Build configuration matters.&lt;/strong&gt; SIMD crates select instruction sets at compile time or runtime. Check how your target and build flags interact with the crate’s feature detection, and test on the actual production CPU architecture.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Heap allocation is not eliminated.&lt;/strong&gt; Allocating nothing at all in a Rust async server also requires choices in the HTTP stack. Count allocations with a profiler or a counting allocator rather than assuming.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  5. Performance: What Is Actually Known
&lt;/h2&gt;

&lt;p&gt;Be skeptical of any single headline multiplier for “SIMD vs standard”, including those in earlier drafts of this article. Throughput and latency depend on payload size, document structure, how much of the document you read, CPU generation and the rest of the stack.&lt;/p&gt;

&lt;p&gt;What the primary sources support:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The simdjson authors report that it processes gigabytes of JSON per second on a single core. An early README figure on one benchmark file (&lt;code&gt;twitter.json&lt;/code&gt;, Skylake) was about 2.2 GB/s, against about 0.10 GB/s for nlohmann/json. That is a different language and a different CPU generation from your service.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simdjson-go&lt;/code&gt; reports roughly 10× the speed of &lt;code&gt;encoding/json&lt;/code&gt; and 40–60% of the C++ simdjson’s speed.&lt;/li&gt;
&lt;li&gt;The simdjson paper reports using a quarter or fewer of the instructions of a state-of-the-art conventional parser.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Parsing is only one stage of the request. If your handler spends most of its time in TLS, the kernel or downstream I/O, a faster parser will not change overall throughput much. Profile first, and measure P99 and P99.9 latency under a realistic burst profile.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Gateway Architecture
&lt;/h2&gt;

&lt;p&gt;A parser is only one component. The sender-facing requirements from the major webhook providers shape the rest of the design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; expects a 2xx response within 10 seconds, or it terminates the connection and treats the delivery as failed. Its documentation recommends putting payloads on a queue and processing them asynchronously. It signs deliveries with HMAC-SHA256 in &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;, recommends a constant-time comparison, and sets &lt;code&gt;X-GitHub-Event&lt;/code&gt; and &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; headers for routing and replay protection. Missed deliveries should be redelivered once your server is back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; signs the raw request body with a timestamp in the &lt;code&gt;Stripe-Signature&lt;/code&gt; header (an HMAC-SHA256 over the timestamp, a dot, and the body). In live mode it retries failed deliveries with exponential backoff for up to three days. A retry is not a new event, so handlers should deduplicate on the event ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These behaviors suggest the following design:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Read the raw body with a size limit.&lt;/strong&gt; Reject oversized requests early.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify the signature first.&lt;/strong&gt; Use a constant-time comparison. This discards forged traffic before any parsing cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parse minimally.&lt;/strong&gt; Extract only what routing needs, such as event type and ID.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Persist, then acknowledge.&lt;/strong&gt; Write the raw body to a durable queue or log, return 2xx, and do the real work in workers. Acknowledging before the event is durably stored risks losing it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate.&lt;/strong&gt; Use &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; or the Stripe event ID as an idempotency key.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Apply backpressure deliberately.&lt;/strong&gt; A 429 or 5xx response only helps if the sender retries. Stripe retries non-2xx responses, but GitHub treats a failed delivery as failed until someone redelivers it. For senders without automatic retries, shedding load means losing events unless you reconcile later.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;For network I/O, Go’s runtime uses the operating system’s readiness-based poller (epoll on Linux), so goroutine-per-connection code is already multiplexed. Rust services typically use an async runtime such as Tokio. &lt;code&gt;io_uring&lt;/code&gt;-based runtimes exist in the Rust ecosystem, but whether they help depends on the workload and kernel version. Benchmark before adopting one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;SIMD-accelerated parsers such as &lt;code&gt;simdjson-go&lt;/code&gt; and &lt;code&gt;simd-json&lt;/code&gt; can cut parsing cost substantially, especially when you only need a few fields. Combined with pooled buffers and borrowed data, they reduce allocation pressure in both Go and Rust. A few points hold regardless of language:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“Zero allocation” is a goal to verify with &lt;code&gt;-benchmem&lt;/code&gt;, allocation counters and profiles, not something a library name guarantees.&lt;/li&gt;
&lt;li&gt;Verify HMAC signatures on the raw bytes before parsing, especially with &lt;code&gt;simd-json&lt;/code&gt;, which mutates its input.&lt;/li&gt;
&lt;li&gt;Go’s GC is mostly concurrent, with short pauses. The cost of heavy allocation shows up as CPU overhead and mark assists as much as pause time.&lt;/li&gt;
&lt;li&gt;Check CPU requirements and library maintenance before building on a parser. &lt;code&gt;simdjson-go&lt;/code&gt; requires AVX2 and CLMUL, and its last tagged release is from 2023.&lt;/li&gt;
&lt;li&gt;Durability and idempotency matter more than parser speed for reliability: persist before acknowledging, and deduplicate on delivery IDs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Langdale, G. and Lemire, D., &lt;em&gt;Parsing Gigabytes of JSON per Second&lt;/em&gt; (arXiv:1902.08318; VLDB Journal, 2019).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;minio/simdjson-go&lt;/code&gt; README and pkg.go.dev listing (performance, AVX2/CLMUL requirement, v0.4.5).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simd-lite/simd-json&lt;/code&gt; README (supported instruction sets, in-place parsing, borrowed values).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;simdjson-rust&lt;/code&gt; on docs.rs (bindings to C++ simdjson, pre-release status).&lt;/li&gt;
&lt;li&gt;Go runtime documentation and GC guide (&lt;code&gt;go.dev/doc/gc-guide&lt;/code&gt;; &lt;code&gt;GOGC&lt;/code&gt;, &lt;code&gt;GOMEMLIMIT&lt;/code&gt;, concurrent mark and sweep).&lt;/li&gt;
&lt;li&gt;GitHub Docs: &lt;em&gt;Validating webhook deliveries&lt;/em&gt; and &lt;em&gt;Best practices for using webhooks&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;Stripe documentation on webhook signatures and retry behavior.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/zero-allocation-webhook-parsers-optimizing-go-and-rust-ingress-buffers-with-simd" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/zero-allocation-webhook-parsers-optimizing-go-and-rust-ingress-buffers-with-simd&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Unicast vs. Anycast for Webhook Ingress: Cutting Handshake Latency for Global Senders</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Sat, 24 Oct 2026 02:01:01 +0000</pubDate>
      <link>https://dev.to/instawebhook/unicast-vs-anycast-for-webhook-ingress-cutting-handshake-latency-for-global-senders-57k0</link>
      <guid>https://dev.to/instawebhook/unicast-vs-anycast-for-webhook-ingress-cutting-handshake-latency-for-global-senders-57k0</guid>
      <description>&lt;p&gt;Webhook publishers such as Stripe, GitHub, Shopify and Slack deliver event notifications to endpoints all over the world. Each delivery has to resolve DNS, open a TCP connection, complete a TLS handshake and send an HTTP request before your server sees a single byte of payload. For a receiver far from the sender, those handshake round trips are the largest part of the network cost.&lt;/p&gt;

&lt;p&gt;Publishers also give you very little time to respond. GitHub &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;expects a 2xx response within 10 seconds&lt;/a&gt; and terminates the connection otherwise. Shopify has &lt;a href="https://shopify.dev/docs/apps/build/webhooks/subscribe/https" rel="noopener noreferrer"&gt;a one-second connection timeout and a five-second timeout for the whole request&lt;/a&gt;, and Slack’s Events API expects a response within three seconds. Stripe retries failed deliveries &lt;a href="https://docs.stripe.com/workbench/webhooks" rel="noopener noreferrer"&gt;for up to three days with exponential backoff in live mode&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;This article covers what each handshake step costs, how BGP Anycast shortens the path, and where TCP Fast Open (TFO) and TLS 1.3 0-RTT help or hurt. It also corrects some commonly repeated claims about all three.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The anatomy of webhook ingress latency
&lt;/h2&gt;

&lt;p&gt;A fresh delivery from a publisher to your endpoint goes through these steps:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DNS lookup → TCP handshake → TLS handshake → HTTP POST → response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;Cost in round trips (RTTs) to your endpoint&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;DNS resolution&lt;/td&gt;
&lt;td&gt;Depends on the sender’s resolver and cache, not on your endpoint’s distance&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TCP three-way handshake&lt;/td&gt;
&lt;td&gt;1 RTT before the client can send data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS 1.2 full handshake&lt;/td&gt;
&lt;td&gt;2 RTTs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;TLS 1.3 full handshake&lt;/td&gt;
&lt;td&gt;1 RTT (&lt;a href="https://datatracker.ietf.org/doc/html/rfc8446" rel="noopener noreferrer"&gt;RFC 8446&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP request and first response byte&lt;/td&gt;
&lt;td&gt;1 RTT, plus your processing time&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Take a sender in Dublin and an endpoint in Virginia, where the round trip is typically somewhere around 70–80 ms. Use 75 ms as an illustrative figure and measure your own paths.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;TLS 1.3:&lt;/strong&gt; TCP (75 ms) plus TLS (75 ms) is 150 ms of setup. The first response byte arrives about 225 ms after the sender starts connecting.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;TLS 1.2:&lt;/strong&gt; setup is 225 ms and the first response byte arrives at about 300 ms.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These figures exclude DNS and server processing time.&lt;/p&gt;

&lt;p&gt;Handshake latency is real, but it is usually not what causes webhook timeouts. A few hundred milliseconds of setup is small against timeouts of 3 to 10 seconds. Timeouts and retry storms are mostly caused by slow application handlers, which section 7 addresses. Shorter handshakes help most with tight limits such as Shopify’s one-second connection timeout, and with the volume of cold connections you absorb during bursts.&lt;/p&gt;

&lt;p&gt;Connection reuse also matters. Shopify’s delivery system &lt;a href="https://shopify.dev/docs/apps/build/webhooks/subscribe/https" rel="noopener noreferrer"&gt;uses HTTP Keep-Alive to reuse connections to the same host&lt;/a&gt;. Where a sender reuses connections, the handshake cost is paid only on cold connections, not on every event.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Unicast vs. Anycast
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Unicast
&lt;/h3&gt;

&lt;p&gt;With unicast, the endpoint’s IP address maps to one location, such as a single region or load balancer cluster. Every sender, wherever it is, sends packets across the Internet to that location. A sender 50 km away and one 8,000 km away both end up at the same place.&lt;/p&gt;

&lt;h3&gt;
  
  
  BGP Anycast
&lt;/h3&gt;

&lt;p&gt;With anycast, several sites (points of presence, or PoPs) announce the same IP prefix over BGP. Each network along the way picks a path to the prefix according to its own routing policy. That decision usually depends first on local preference, then on AS-path length. The result is that senders generally reach a topologically close PoP. A few clarifications:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;“Nearest” means topologically nearest, not geographically nearest.&lt;/strong&gt; Routing policy and peering decide the path, so a sender can land on a PoP that is not the closest one by distance.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prefix length:&lt;/strong&gt; the common rule of thumb is that &lt;a href="https://bgpfilterguide.nlnog.net/guides/small_prefixes" rel="noopener noreferrer"&gt;routes more specific than a /24 (IPv4) or /48 (IPv6) should not be expected to work globally&lt;/a&gt;, because many networks filter them. An anycast service therefore announces at least a /24 (IPv4) or /48 (IPv6), not a /32. You need your own address space and AS number for this, or you can use a provider that already operates an anycast network.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stability:&lt;/strong&gt; a &lt;a href="https://ant.isi.edu/blog/?p=1007" rel="noopener noreferrer"&gt;USC/ISI study of anycast DNS services&lt;/a&gt; found that anycast works well for the vast majority of vantage points. About 1% saw frequent shifts between anycast sites. The study found TCP-level flipping &lt;a href="https://ant-ip4.ant.isi.edu/bib/Wei18a.html" rel="noopener noreferrer"&gt;much rarer than UDP flipping, in roughly 0.15% of vantage point and service combinations&lt;/a&gt;, though it does occur. If a route shifts mid-connection, the TCP connection breaks. Webhook deliveries are short-lived HTTP requests, so exposure is small, but senders should retry. Stripe, GitHub and Shopify all retry or support redelivery, as does your own fallback logic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Anycast IPs vs. anycast DNS
&lt;/h3&gt;

&lt;p&gt;The title of this article’s original draft conflates two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Anycast DNS&lt;/strong&gt; means your authoritative nameservers use anycast. This speeds up name resolution.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Anycast IP ingress&lt;/strong&gt; means the webhook endpoint’s own address is anycast. This shortens the TCP and TLS round trips.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;You can use either or both. A third option is DNS-based steering, such as latency-based or geo-aware DNS, where each region has its own unicast IP. It is simpler to operate than BGP and has no flip risk, but it depends on the sender’s resolver location and on DNS caching. Managed products such as CDNs and global accelerators give you anycast without running BGP yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Withdrawing routes when a PoP is unhealthy
&lt;/h2&gt;

&lt;p&gt;An anycast PoP that keeps announcing its prefix while its local proxy is dead will blackhole the traffic that BGP sends it. Each PoP therefore needs to withdraw its route when it is unhealthy. &lt;a href="https://github.com/Exa-Networks/exabgp" rel="noopener noreferrer"&gt;ExaBGP&lt;/a&gt; is one option. It reads route announcements from a helper process, so a health-check loop can drive announcements directly.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/bin/bash&lt;/span&gt;
&lt;span class="c"&gt;# /opt/anycast/announce.sh — emits ExaBGP text-API commands on stdout&lt;/span&gt;
&lt;span class="nv"&gt;PREFIX&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"198.51.100.0/24"&lt;/span&gt;   &lt;span class="c"&gt;# documentation prefix; use your own&lt;/span&gt;
&lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"withdrawn"&lt;/span&gt;

&lt;span class="k"&gt;while &lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;do
  if &lt;/span&gt;curl &lt;span class="nt"&gt;-fsS&lt;/span&gt; &lt;span class="nt"&gt;--max-time&lt;/span&gt; 2 http://127.0.0.1:8080/healthz &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"announced"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"announce route &lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt; next-hop self"&lt;/span&gt;
      &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"announced"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  else
    if&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$state&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"withdrawn"&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
      &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"withdraw route &lt;/span&gt;&lt;span class="nv"&gt;$PREFIX&lt;/span&gt;&lt;span class="s2"&gt; next-hop self"&lt;/span&gt;
      &lt;span class="nv"&gt;state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"withdrawn"&lt;/span&gt;
    &lt;span class="k"&gt;fi
  fi
  &lt;/span&gt;&lt;span class="nb"&gt;sleep &lt;/span&gt;2
&lt;span class="k"&gt;done&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two operational notes. Add hysteresis so a flapping health check does not cause route flaps. And make sure a shared failure, such as a bad deploy, cannot withdraw every PoP at once. BGP convergence takes time, so withdrawal is not instantaneous.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. TCP Fast Open (TFO)
&lt;/h2&gt;

&lt;p&gt;TFO (&lt;a href="https://datatracker.ietf.org/doc/html/rfc7413" rel="noopener noreferrer"&gt;RFC 7413&lt;/a&gt;) lets a client send data in the SYN packet on repeat connections, saving one round trip. The first connection gets a cookie from the server. Later connections present the cookie and carry data in the SYN. The RFC is an experimental one, and the practical caveats are significant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The sender must opt in.&lt;/strong&gt; The server can only enable its side. If the publisher’s HTTP client does not use TFO, you gain nothing. None of the provider documentation reviewed for this article mentions TFO support, so do not assume it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Middleboxes.&lt;/strong&gt; Some network devices drop TCP packets carrying options they do not understand. Operators report &lt;a href="https://gitea.hillion.co.uk/JakeHillion/storj/src/branch/main/docs/blueprints/tcp-fastopen.md" rel="noopener noreferrer"&gt;connections that appear to die when TFO is used&lt;/a&gt;, so clients commonly fall back to a regular handshake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates.&lt;/strong&gt; Data in a SYN can occasionally be delivered more than once, so the application must tolerate duplicate requests, which idempotent webhook handling already requires.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Enabling TFO on a Linux ingress node
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight conf"&gt;&lt;code&gt;&lt;span class="c"&gt;# /etc/sysctl.d/99-tfo-ingress.conf
# Bitmask: 1 = client, 2 = server, 3 = both
&lt;/span&gt;&lt;span class="n"&gt;net&lt;/span&gt;.&lt;span class="n"&gt;ipv4&lt;/span&gt;.&lt;span class="n"&gt;tcp_fastopen&lt;/span&gt; = &lt;span class="m"&gt;3&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;NGINX also needs TFO enabled on the listen socket:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;fastopen=256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;fastopen=256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To check whether senders actually use it, read the kernel counters. &lt;code&gt;ss&lt;/code&gt; does not show a TFO marker.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;nstat &lt;span class="nt"&gt;-az&lt;/span&gt; TcpExtTCPFastOpenPassive TcpExtTCPFastOpenPassiveFail &lt;span class="se"&gt;\&lt;/span&gt;
          TcpExtTCPFastOpenListenOverflow TcpExtTCPFastOpenCookieReqd
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A non-zero &lt;code&gt;TCPFastOpenPassive&lt;/code&gt; count means clients are completing fast-open connections to you. If it stays at zero, your senders are not using TFO.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. TLS 1.3 and 0-RTT resumption
&lt;/h2&gt;

&lt;p&gt;TLS 1.3 completes a full handshake in one round trip. With a session ticket from an earlier connection, a client can also send application data in its first flight. This is &lt;strong&gt;0-RTT early data&lt;/strong&gt; (&lt;a href="https://datatracker.ietf.org/doc/html/rfc8446#section-2.3" rel="noopener noreferrer"&gt;RFC 8446, section 2.3&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Early data has two limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;It is replayable.&lt;/strong&gt; An attacker who captures the encrypted early data can resend it, and the server cannot cryptographically tell the copy from the original. The data also lacks forward secrecy.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Major CDNs treat POST as unsafe for early data.&lt;/strong&gt; Cloudflare &lt;a href="https://blog.cloudflare.com/even-faster-connection-establishment-with-quic-0-rtt-resumption" rel="noopener noreferrer"&gt;does not enable 0-RTT by default and always rejects requests that are obviously not idempotent, such as POST or PUT&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Webhook deliveries are POST requests, so 0-RTT is the riskiest optimisation here. It is only appropriate if your handler is strictly idempotent. &lt;a href="https://datatracker.ietf.org/doc/html/rfc8470" rel="noopener noreferrer"&gt;RFC 8470&lt;/a&gt; defines the &lt;code&gt;Early-Data: 1&lt;/code&gt; header, which a proxy adds to forwarded early-data requests, and the &lt;code&gt;425 Too Early&lt;/code&gt; status code that a server returns to refuse them. The original draft’s advice to accept 0-RTT for “idempotent methods (POST ...)” was wrong, because POST is not idempotent by definition.&lt;/p&gt;

&lt;h3&gt;
  
  
  NGINX configuration
&lt;/h3&gt;

&lt;p&gt;NGINX supports &lt;code&gt;ssl_early_data&lt;/code&gt; with OpenSSL 1.1.1 or later, and the NGINX documentation warns that &lt;a href="https://nginx.org/en/docs/http/ngx_http_ssl_module.html#var_ssl_early_data" rel="noopener noreferrer"&gt;early data requests are subject to replay attacks and should be handled at the application layer using &lt;code&gt;$ssl_early_data&lt;/code&gt;&lt;/a&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight nginx"&gt;&lt;code&gt;&lt;span class="k"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;fastopen=256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;listen&lt;/span&gt; &lt;span class="s"&gt;[::]:443&lt;/span&gt; &lt;span class="s"&gt;ssl&lt;/span&gt; &lt;span class="s"&gt;fastopen=256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;http2&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                          &lt;span class="c1"&gt;# nginx 1.25.1+; older versions use "listen ... http2"&lt;/span&gt;
    &lt;span class="kn"&gt;server_name&lt;/span&gt; &lt;span class="s"&gt;webhooks.example.com&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;ssl_certificate&lt;/span&gt;     &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/fullchain.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_certificate_key&lt;/span&gt; &lt;span class="n"&gt;/etc/letsencrypt/live/example.com/privkey.pem&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Keep TLS 1.2: Stripe documents TLS 1.2 or higher as its requirement,&lt;/span&gt;
    &lt;span class="c1"&gt;# and a TLS 1.3-only endpoint would shut out clients that stop at 1.2.&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_protocols&lt;/span&gt; &lt;span class="s"&gt;TLSv1.2&lt;/span&gt; &lt;span class="s"&gt;TLSv1.3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="c1"&gt;# Off by default. Enable only on a dedicated server block whose&lt;/span&gt;
    &lt;span class="c1"&gt;# handlers are idempotent, and always forward the signal downstream.&lt;/span&gt;
    &lt;span class="kn"&gt;ssl_early_data&lt;/span&gt; &lt;span class="no"&gt;on&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="kn"&gt;location&lt;/span&gt; &lt;span class="n"&gt;/webhook&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_set_header&lt;/span&gt; &lt;span class="s"&gt;Early-Data&lt;/span&gt; &lt;span class="nv"&gt;$ssl_early_data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
        &lt;span class="kn"&gt;proxy_pass&lt;/span&gt; &lt;span class="s"&gt;http://webhook_backend&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The original draft set &lt;code&gt;ssl_protocols TLSv1.3&lt;/code&gt; only. That is a risk for a public webhook endpoint, because any sender that cannot negotiate TLS 1.3 will fail to deliver. Enabling both versions and testing each publisher’s delivery is safer.&lt;/p&gt;

&lt;p&gt;In an anycast fleet, session resumption only works if any PoP can decrypt a ticket issued by another. This means sharing session ticket keys across PoPs and rotating them regularly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Replay defence
&lt;/h3&gt;

&lt;p&gt;If you accept early data on webhook endpoints:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Deduplicate by event ID.&lt;/strong&gt; Use Stripe’s &lt;code&gt;evt_…&lt;/code&gt; ID or GitHub’s &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; header with an atomic insert such as Redis &lt;code&gt;SET key 1 NX EX &amp;lt;ttl&amp;gt;&lt;/code&gt;. GitHub says &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;redelivery keeps the same &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; value&lt;/a&gt;. Stripe retries for up to three days, so the TTL should be at least that (259,200 seconds) for Stripe events.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a shared dedupe store.&lt;/strong&gt; In an anycast fleet a replayed request can land on a different PoP, so a per-PoP cache does not stop a cross-PoP replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify signatures.&lt;/strong&gt; This establishes authenticity, but a replay of a valid signed request still has a valid signature. Signature timestamps limit the replay window but do not remove it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return &lt;code&gt;425 Too Early&lt;/code&gt;&lt;/strong&gt; from handlers that cannot guarantee idempotency when &lt;code&gt;Early-Data: 1&lt;/code&gt; is present.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  6. Putting the numbers together
&lt;/h2&gt;

&lt;p&gt;The table shows time to first response byte, excluding DNS and server processing time. The 15 ms anycast RTT is an illustrative assumption, not a measurement.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stack&lt;/th&gt;
&lt;th&gt;RTT&lt;/th&gt;
&lt;th&gt;Calculation&lt;/th&gt;
&lt;th&gt;Time to first response byte&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Unicast, TLS 1.2&lt;/td&gt;
&lt;td&gt;75 ms&lt;/td&gt;
&lt;td&gt;TCP 1 + TLS 2 + request 1 = 4 RTT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~300 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unicast, TLS 1.3&lt;/td&gt;
&lt;td&gt;75 ms&lt;/td&gt;
&lt;td&gt;TCP 1 + TLS 1 + request 1 = 3 RTT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~225 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anycast, TLS 1.3&lt;/td&gt;
&lt;td&gt;15 ms&lt;/td&gt;
&lt;td&gt;3 RTT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~45 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anycast + TFO, TLS 1.3&lt;/td&gt;
&lt;td&gt;15 ms&lt;/td&gt;
&lt;td&gt;2 RTT (ClientHello rides in the SYN)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~30 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Anycast + TFO + TLS 1.3 0-RTT&lt;/td&gt;
&lt;td&gt;15 ms&lt;/td&gt;
&lt;td&gt;1 RTT (request in first flight)&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~15 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unicast + TFO + TLS 1.3 0-RTT&lt;/td&gt;
&lt;td&gt;75 ms&lt;/td&gt;
&lt;td&gt;1 RTT&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;~75 ms&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The last two rows show that the techniques are complementary. Anycast shrinks the RTT itself, while TFO and 0-RTT reduce how many RTTs you pay. Even the best case is one RTT, not zero. The earlier claim of “~0 ms connection overhead” overlooks that the request must still reach the server and the response must come back.&lt;/p&gt;

&lt;p&gt;These gains also require sender cooperation. TFO and 0-RTT only apply if the publisher’s client uses them, and TFO and 0-RTT early data are both replayable. Anycast needs no sender support, so it is the most dependable of the three.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Acknowledge at the edge, process later
&lt;/h2&gt;

&lt;p&gt;Fast ingress only helps if the application behind it is also fast. If the PoP must wait for a distant database before replying, you have moved the delay rather than removed it. Provider documentation says the same: &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt; and &lt;a href="https://shopify.dev/docs/apps/build/webhooks/subscribe/https" rel="noopener noreferrer"&gt;Shopify&lt;/a&gt; both recommend queuing payloads and responding quickly, with processing done asynchronously.&lt;/p&gt;

&lt;p&gt;A resilient design looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Validate and acknowledge at the PoP.&lt;/strong&gt; Verify the signature against the raw request body, such as the &lt;code&gt;Stripe-Signature&lt;/code&gt; header, then durably enqueue the event and return a 2xx. Return the exact status the sender requires, because Shopify requires 200 while GitHub accepts any 2xx.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Buffer in a replicated queue.&lt;/strong&gt; Use something like Apache Kafka or Amazon Kinesis, or a managed edge queue such as Cloudflare Queues, so events are persisted before you acknowledge them.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep PoP-to-origin connections warm.&lt;/strong&gt; The PoP can hold persistent connections to the origin, so the long-haul handshake is paid once and not once per webhook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Process idempotently.&lt;/strong&gt; All three publishers can deliver an event more than once. Shopify explicitly notes that &lt;a href="https://shopify.dev/docs/apps/build/webhooks/verify-deliveries" rel="noopener noreferrer"&gt;duplicate deliveries can occur after network timeouts or retries&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconcile.&lt;/strong&gt; If you cannot afford to miss events, periodically compare against the publisher’s API. Shopify recommends this, and Stripe lets you query missed events.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  8. Measure before and after
&lt;/h2&gt;

&lt;p&gt;You can measure handshake costs from any sender-like location with &lt;code&gt;curl&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-so&lt;/span&gt; /dev/null &lt;span class="nt"&gt;--tlsv1&lt;/span&gt;.3 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s1"&gt;'dns=%{time_namelookup}s tcp=%{time_connect}s tls=%{time_appconnect}s ttfb=%{time_starttransfer}s\n'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  https://webhooks.example.com/healthz
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it from several regions against both your unicast and anycast addresses. Add &lt;code&gt;--tcp-fastopen&lt;/code&gt; to test TFO from a client that supports it. Then compare the results with your ingress logs and the kernel counters from section 4 to see which senders benefit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;For globally distributed webhook senders, anycast gives the largest and most dependable latency improvement, because it shortens every round trip and needs no cooperation from the sender. Pair it with route withdrawal on health-check failure, TLS 1.3 alongside 1.2 compatibility, and an edge that acknowledges quickly and queues the work. Add TFO where your senders support it. Enable TLS 1.3 0-RTT only on endpoints whose handlers are strictly idempotent, with a shared deduplication store, and treat it as a niche optimisation rather than a default for POST webhooks.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/unicast-vs-anycast-for-webhook-ingress-cutting-handshake-latency-for-global-send" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/unicast-vs-anycast-for-webhook-ingress-cutting-handshake-latency-for-global-send&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Anycast Routing for Global Webhook Ingress: Minimizing Latency and Packet Loss for Worldwide Senders</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Fri, 23 Oct 2026 02:30:46 +0000</pubDate>
      <link>https://dev.to/instawebhook/anycast-routing-for-global-webhook-ingress-minimizing-latency-and-packet-loss-for-worldwide-senders-2aa4</link>
      <guid>https://dev.to/instawebhook/anycast-routing-for-global-webhook-ingress-minimizing-latency-and-packet-loss-for-worldwide-senders-2aa4</guid>
      <description>&lt;p&gt;Modern SaaS ecosystems rely heavily on webhooks. When a payment succeeds on &lt;strong&gt;Stripe&lt;/strong&gt;, an order is created on &lt;strong&gt;Shopify&lt;/strong&gt;, or a push lands on &lt;strong&gt;GitHub&lt;/strong&gt;, the platform sends an asynchronous HTTP &lt;code&gt;POST&lt;/code&gt; request to an endpoint you control. The sender usually expects a quick &lt;code&gt;2xx&lt;/code&gt; response, and it treats anything else, including a slow response, as a failed delivery.&lt;/p&gt;

&lt;p&gt;Scaling webhook ingestion globally is therefore a problem of physics and transport reliability. If your endpoint lives in a single region, every sender outside that region pays for distance on every connection. This guide explains how BGP Anycast shortens that distance, where it helps, and where it doesn’t.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------------------+
|                         UNICAST INGRESS (illustrative)                            |
|                                                                                   |
|  Sender (Tokyo) ---[ TCP + TLS handshake over a long path ]---&amp;gt; Origin (Virginia) |
+-----------------------------------------------------------------------------------+

+-----------------------------------------------------------------------------------+
|                         ANYCAST EDGE INGRESS (illustrative)                       |
|                                                                                   |
|  Sender (Tokyo) ---[ short handshake ]---&amp;gt; Nearby PoP                             |
|                                               |                                   |
|                                  [ warm connections / private backbone ]          |
|                                               v                                   |
|                                          Origin / Queue                           |
+-----------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What Webhook Senders Expect
&lt;/h2&gt;

&lt;p&gt;Before designing ingress, it helps to know what the biggest providers require. These figures come from their public documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; expects a &lt;code&gt;2xx&lt;/code&gt; response within &lt;a href="https://docs.github.com/en/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;10 seconds&lt;/a&gt;. A slower response is treated as a failed delivery, and you can redeliver failed webhooks later. GitHub lowered this limit from 30 seconds &lt;a href="https://developer.github.com/changes/2017-09-12-changes-to-maximum-webhook-timeout-period/" rel="noopener noreferrer"&gt;in 2017&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify&lt;/strong&gt; &lt;a href="https://shopify.dev/docs/apps/build/webhooks/troubleshooting" rel="noopener noreferrer"&gt;fails a delivery&lt;/a&gt; if your app doesn’t respond within five seconds. It retries failed deliveries up to eight times over about four hours, and it removes the subscription if failures persist.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; &lt;a href="https://stripe.com/docs/webhooks" rel="noopener noreferrer"&gt;recommends&lt;/a&gt; returning a &lt;code&gt;2xx&lt;/code&gt; quickly, before any complex logic that could cause a timeout. In live mode it retries delivery for up to three days with exponential backoff. In sandbox it retries three times over a few hours. Stripe doesn’t publish an exact timeout value, so don’t design around a specific number.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of these providers tells you to acknowledge first and process later. Anycast ingress applies that advice at the network layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Unicast Ingress for Global Webhooks
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Handshake Latency Scales with Distance
&lt;/h3&gt;

&lt;p&gt;A unicast IP routes traffic to one location. If your ingestion service runs in a single region such as &lt;code&gt;us-east-1&lt;/code&gt; and the sender dispatches from Tokyo, every new connection pays the full round-trip time (RTT) several times before any payload is processed.&lt;/p&gt;

&lt;p&gt;Before the request can be sent, a new HTTPS connection needs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One RTT&lt;/strong&gt; for the TCP handshake.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;One RTT&lt;/strong&gt; for the TLS 1.3 handshake. TLS 1.2 needs &lt;a href="https://www.catchpoint.com/http2-vs-http3/tls1-2-vs-1-3" rel="noopener noreferrer"&gt;two&lt;/a&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Client (Tokyo) &amp;lt;------------------ ~160 ms RTT (illustrative) ------------------&amp;gt; Origin (Virginia)

1. TCP SYN ---------------------------------------------------------------&amp;gt;
2. &amp;lt;---------------------------------------------------------- TCP SYN-ACK
3. TCP ACK + TLS ClientHello ----------------------------------------------&amp;gt;
4. &amp;lt;----------------------------------- TLS ServerHello, Certificate, Finished
5. TLS Finished + HTTP POST /webhook ---------------------------------------&amp;gt;
6. &amp;lt;------------------------------------------------------- HTTP 2xx response
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At an illustrative 160 ms RTT, the arithmetic looks like this:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Step&lt;/th&gt;
&lt;th&gt;TLS 1.3&lt;/th&gt;
&lt;th&gt;TLS 1.2&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;TCP + TLS handshake complete&lt;/td&gt;
&lt;td&gt;~320 ms&lt;/td&gt;
&lt;td&gt;~480 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;First response byte (best case, zero processing time)&lt;/td&gt;
&lt;td&gt;~480 ms&lt;/td&gt;
&lt;td&gt;~640 ms&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;These numbers are far below a 5-second or 10-second timeout on a healthy path. The real danger is variance: loss, congestion, and slow handlers stack on top of that baseline. A sender that sees a failure retries, and a large burst of retries can add load to an endpoint that is already struggling. Providers use exponential backoff to soften this, but your endpoint still absorbs the duplicates.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Loss Hurts More on Long Paths
&lt;/h3&gt;

&lt;p&gt;Traffic over the public internet crosses multiple autonomous systems (AS) with varying congestion. Two effects make loss more costly on long paths:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Recovery time scales with RTT.&lt;/strong&gt; Loss in the middle of a transfer is typically recovered after roughly one RTT, so a 160 ms path recovers far more slowly than a 10 ms one. Loss-based congestion control such as CUBIC also shrinks the congestion window after loss, and on long paths it takes longer to grow back.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handshake loss is the most expensive kind.&lt;/strong&gt; If a SYN is lost, &lt;a href="https://www.rfc-editor.org/rfc/rfc6298" rel="noopener noreferrer"&gt;RFC 6298&lt;/a&gt; sets the initial retransmission timeout at one second. A single dropped SYN can add a full second before the connection even starts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Longer Paths Mean More Exposure
&lt;/h3&gt;

&lt;p&gt;The farther a connection travels over networks you don’t control, the more opportunities there are for congestion, rerouting, and jitter. Internet paths are also often asymmetric, meaning the forward and return routes differ. That is normal, and it applies to anycast too. Anycast doesn’t remove path problems. It shortens the portion of the path where they can hurt a new connection.&lt;/p&gt;

&lt;h2&gt;
  
  
  How BGP Anycast Works
&lt;/h2&gt;

&lt;p&gt;With &lt;strong&gt;anycast&lt;/strong&gt;, the same IP prefix is announced from multiple physical locations (Points of Presence, or PoPs). Each network on the internet picks a route to that prefix using BGP, so traffic from a given sender typically flows to whichever announcing location its network prefers.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                      Sender (US-West)          Sender (Europe)
                            |                         |
                     (BGP-selected route)      (BGP-selected route)
                            v                         v
                  +------------------+      +------------------+
                  | PoP: San Jose    |      | PoP: Frankfurt   |
                  | 198.51.100.0/24  |      | 198.51.100.0/24  |
                  +--------+---------+      +--------+---------+
                           \                       /
                            \    private / warm   /
                             v    connections    v
                          +--------------------------+
                          |  Central origin / queue  |
                          +--------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;em&gt;(198.51.100.0/24 is a documentation-only prefix and is not routable on the real internet.)&lt;/em&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  “Nearest” Means Nearest in BGP, Not in Kilometers
&lt;/h3&gt;

&lt;p&gt;BGP selects routes using policy first, then attributes such as AS-path length. It knows nothing about geography. Most of the time the chosen PoP is close to the sender, but not always. A sender on a network with unusual peering can land at a distant PoP. Anycast catchments are therefore something to measure and tune, not something to assume.&lt;/p&gt;

&lt;p&gt;Operators influence catchments by choosing where to announce, which transit providers and exchange points (IXPs) to peer with, and by using techniques such as AS-path prepending or BGP communities.&lt;/p&gt;

&lt;h3&gt;
  
  
  Terminate Connections at the Edge
&lt;/h3&gt;

&lt;p&gt;Instead of completing TCP and TLS across an ocean, the sender completes them with the nearest PoP. If the sender is 10 ms from that PoP, the handshake costs roughly 20 ms instead of several hundred.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Local handshake:&lt;/strong&gt; TCP and TLS 1.3 complete in about two short RTTs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Local receipt:&lt;/strong&gt; The edge reads the &lt;code&gt;POST&lt;/code&gt; body over a short path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Backbone forwarding:&lt;/strong&gt; The edge hands the event to your systems over pre-established, long-lived connections on a path you control. This might be a private backbone or a provider network such as AWS’s.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Loss Recovery Happens Over a Shorter Segment
&lt;/h3&gt;

&lt;p&gt;The sender-to-edge leg is still the public internet, but it is shorter. Because retransmissions are paced by RTT, recovery on a 10 ms path is much faster than on a 160 ms path. The long-haul leg moves onto infrastructure with more predictable behavior. It can still suffer loss, but it is no longer the part the sender’s timeout clock is watching.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Anycast vs. Running Your Own
&lt;/h3&gt;

&lt;p&gt;Announcing your own anycast prefix requires an ASN, provider-independent address space (a /24 is the smallest IPv4 prefix generally accepted in the global routing table), and BGP relationships at every site. Many teams use a managed service instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Cloudflare&lt;/strong&gt; runs its network on anycast. It was connected to &lt;a href="https://blog.cloudflare.com/cloudflare-connected-in-over-300-cities/" rel="noopener noreferrer"&gt;more than 300 cities&lt;/a&gt; as of its 2023 announcement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AWS Global Accelerator&lt;/strong&gt; provides &lt;a href="https://aws.amazon.com/global-accelerator/features" rel="noopener noreferrer"&gt;two static anycast IPv4 addresses&lt;/a&gt; announced from multiple AWS edge locations. Traffic enters the AWS network near the sender and travels to your regional endpoint over AWS’s backbone.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Check where your actual senders dispatch from before investing. If most of your webhook volume originates in one or two regions, a regional endpoint near those regions may deliver most of the benefit at lower cost.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture of an Anycast-Fronted Webhook Ingress
&lt;/h2&gt;

&lt;p&gt;An anycast ingress stack has three layers: &lt;strong&gt;edge routing&lt;/strong&gt;, &lt;strong&gt;edge processing&lt;/strong&gt;, and &lt;strong&gt;internal transport and buffering&lt;/strong&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;+-----------------------------------------------------------------------------------+
| 1. ANYCAST EDGE ROUTING                                                           |
|    - Prefix announced via BGP (e.g., BIRD or ExaBGP)                              |
|    - L4 load balancing inside the PoP (e.g., Maglev-style consistent hashing,     |
|      IPVS, or XDP/DPDK-based balancers)                                           |
+-----------------------------------------------------------------------------------+
                                       |
                                       v
+-----------------------------------------------------------------------------------+
| 2. EDGE PROCESSING                                                                |
|    - TLS termination, HTTP/1.1 + HTTP/2 (HTTP/3 optional)                         |
|    - Signature verification (optional)                                            |
|    - Durable enqueue, then 2xx acknowledgment                                     |
+-----------------------------------------------------------------------------------+
                                       |
                                       v
+-----------------------------------------------------------------------------------+
| 3. TRANSPORT AND BUFFER                                                           |
|    - Persistent connections over private links or a provider backbone             |
|    - Durable queue (Kafka, Redpanda, SQS, etc.), consumed asynchronously          |
+-----------------------------------------------------------------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Layer 1: BGP at the Edge
&lt;/h3&gt;

&lt;p&gt;Each PoP runs a BGP daemon such as &lt;a href="https://bird.network.cz/" rel="noopener noreferrer"&gt;BIRD&lt;/a&gt; or &lt;a href="https://github.com/Exa-Networks/exabgp" rel="noopener noreferrer"&gt;ExaBGP&lt;/a&gt; that announces the anycast prefix to upstream providers and peers. A minimal BIRD 2 sketch looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Illustrative BIRD 2 sketch. All ASNs and prefixes are placeholders.
protocol static anycast_vip {
    ipv4;
    # Anycast service address, bound to a loopback interface on the PoP's servers
    route 198.51.100.0/24 via "lo";
}

protocol bgp upstream_1 {
    local as 65001;
    neighbor 192.0.2.1 as 64512;

    ipv4 {
        import none;
        export filter {
            if net = 198.51.100.0/24 then accept;
            reject;
        };
    };
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In production you would use your own public ASN and prefix, add session authentication, prefix limits, and RPKI validation, and tune announcements per upstream.&lt;/p&gt;

&lt;h3&gt;
  
  
  Layer 2: Fast-Ack Ingestion
&lt;/h3&gt;

&lt;p&gt;To keep the sender’s clock short, the edge should do as little as possible before acknowledging:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Terminate TLS.&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Optionally verify the signature&lt;/strong&gt; using a cached secret, so obviously forged traffic is rejected cheaply. Each provider signs differently:

&lt;ul&gt;
&lt;li&gt;GitHub sends &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;, an HMAC-SHA256 of the raw body prefixed with &lt;code&gt;sha256=&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Shopify sends &lt;code&gt;X-Shopify-Hmac-Sha256&lt;/code&gt;, a base64-encoded HMAC-SHA256 of the raw body.&lt;/li&gt;
&lt;li&gt;Stripe sends a &lt;code&gt;Stripe-Signature&lt;/code&gt; header containing a timestamp and signature(s), computed over the timestamp and the payload. Its libraries also enforce a timestamp tolerance.In every case, verify against the &lt;strong&gt;raw, unparsed body&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Durably enqueue&lt;/strong&gt; the event.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return a &lt;code&gt;2xx&lt;/code&gt;.&lt;/strong&gt; The acknowledgment should never come before the event is durably stored.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Example: Edge Ingress Handler for GitHub Webhooks
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;timingSafeEqual&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;node:crypto&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kr"&gt;interface&lt;/span&gt; &lt;span class="nx"&gt;WebhookRequest&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// raw, unparsed body&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Application-defined: durably writes to a queue near this PoP.&lt;/span&gt;
&lt;span class="kr"&gt;declare&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;publishToQueue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Record&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;receivedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kc"&gt;undefined&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleGitHubWebhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;WebhookRequest&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;Response&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;x-hub-signature-256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GITHUB_WEBHOOK_SECRET&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;401&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 1. Verify the HMAC over the raw body using a constant-time comparison&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;expected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256=&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;createHmac&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;sha256&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;digest&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;hex&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;signature&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;expected&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;timingSafeEqual&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Invalid signature&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;403&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Durably enqueue before acknowledging&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;ok&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;publishToQueue&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;rawBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;receivedAt&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="na"&gt;pop&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;POP_REGION_CODE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;ok&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// A non-2xx makes the sender retry, which is what you want if nothing was stored&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;Queue unavailable&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;503&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Acknowledge; heavy processing happens asynchronously downstream&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Response&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;accepted&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;}),&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;202&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two caveats apply to this design:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Where the queue lives matters.&lt;/strong&gt; If every PoP must synchronously write to one distant queue, you have moved the long round trip from before the acknowledgment to inside it. Write to a regional or PoP-local durable buffer and replicate or forward asynchronously, or accept the cross-region write latency as a known cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates are normal.&lt;/strong&gt; Senders retry on timeouts and ambiguous failures, and Shopify’s documentation notes that duplicate deliveries can occur. Deduplicate downstream, using identifiers such as GitHub’s &lt;code&gt;X-GitHub-Delivery&lt;/code&gt; header or the event ID in a Stripe payload.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Anycast vs. Unicast: What Changes
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Dimension&lt;/th&gt;
&lt;th&gt;Single-region unicast&lt;/th&gt;
&lt;th&gt;Anycast edge ingress&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Connection setup cost&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full RTT to the origin region for TCP plus TLS (e.g., ~320 ms at 160 ms RTT with TLS 1.3)&lt;/td&gt;
&lt;td&gt;RTT to the nearest PoP (often tens of milliseconds or less, depending on catchment)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ingress points&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;One region&lt;/td&gt;
&lt;td&gt;Many PoPs; the count depends on your provider or deployment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Sensitivity to long-haul loss&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;High: recovery scales with the full RTT&lt;/td&gt;
&lt;td&gt;Lower for the sender-facing segment; the backbone leg is under your control&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Failover&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;DNS-based failover, bounded by TTLs and client caching&lt;/td&gt;
&lt;td&gt;BGP withdrawal, typically taking tens of seconds to converge and sometimes minutes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DDoS absorption&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Concentrated at one location&lt;/td&gt;
&lt;td&gt;Traffic is spread across PoPs by BGP catchment&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operational complexity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low&lt;/td&gt;
&lt;td&gt;Higher: BGP operations, per-PoP health, catchment monitoring&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Operational Challenges and Mitigations
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Route Changes Can Break In-Flight Connections
&lt;/h3&gt;

&lt;p&gt;Anycast sends each packet toward whichever PoP the network currently prefers. If routing shifts mid-connection, subsequent packets can arrive at a different PoP that has no state for that TCP connection, and it answers with a reset.&lt;/p&gt;

&lt;p&gt;How common is this? A measurement study by &lt;a href="https://ant.isi.edu/bib/Wei18a.html" rel="noopener noreferrer"&gt;Wei and Heidemann&lt;/a&gt; covering more than 9,000 vantage points and 11 anycast services found that about 1% of vantage points saw frequent flips between anycast sites. TCP connections were disrupted far less often, in about 0.15% of vantage point and service combinations. Those results come from DNS root services, so your own measurements may differ.&lt;/p&gt;

&lt;p&gt;Practical mitigations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Keep connections short.&lt;/strong&gt; Webhook deliveries are brief request/response exchanges, so the exposure window is small. A rare reset simply causes a sender retry, which your deduplication already handles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use consistent hashing inside each PoP.&lt;/strong&gt; Maglev-style balancing keeps a connection on the same backend server within a PoP. Within-PoP ECMP does not help when traffic moves &lt;em&gt;between&lt;/em&gt; PoPs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider cross-PoP forwarding only if you need it.&lt;/strong&gt; Encapsulating and forwarding misdirected packets to the PoP that owns the connection (for example, over GRE or GUE tunnels) is possible, but it adds significant complexity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t count on QUIC to solve this.&lt;/strong&gt; QUIC connection IDs support migration, but it only helps if the sender’s HTTP client speaks HTTP/3. A server that receives packets for a connection it doesn’t know still cannot continue it without extra routing infrastructure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lean on idempotency and retries.&lt;/strong&gt; Providers already retry failed deliveries, so a robust consumer treats a rare reset as just another duplicate.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Health Checks and Route Withdrawal
&lt;/h3&gt;

&lt;p&gt;If a PoP’s application fails while BGP keeps announcing the prefix, traffic still arrives and is dropped. Withdraw the route when the local service is unhealthy.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Run health checks that cover the ingress service, its link to the queue, and upstream connectivity.&lt;/li&gt;
&lt;li&gt;Have the checker withdraw the announcement (ExaBGP’s process API is commonly used for this) when checks fail, and re-announce when they recover.&lt;/li&gt;
&lt;li&gt;Add hysteresis so a flapping service doesn’t cause a flapping route. Frequent announcements and withdrawals can trigger route-flap damping at upstream providers.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Be realistic about timing. Withdrawing a route isn’t instant, because the news must propagate across the internet. &lt;a href="https://lists.ausnog.net/pipermail/ausnog/2018-February/040584.html" rel="noopener noreferrer"&gt;Measurements&lt;/a&gt; discussed by APNIC’s Geoff Huston put typical BGP convergence at tens of seconds, and older academic studies recorded &lt;a href="https://www.cs.memphis.edu/~lanwang/paper/infocom2002.pdf" rel="noopener noreferrer"&gt;minutes&lt;/a&gt; in bad cases. Vendors that advertise anycast failover &lt;a href="https://ayedo.de/en/posts/failover-ohne-dns-wie-anycast-bgp-die-rto-unter-30-sekunden-drucken/" rel="noopener noreferrer"&gt;in under 30 seconds&lt;/a&gt; are describing good-case behavior. Sub-second failover is not a safe assumption.&lt;/p&gt;

&lt;p&gt;During that window, senders will see timeouts or resets and rely on their retry policies. Stripe’s three days and Shopify’s eight attempts over four hours are far longer than any realistic convergence period.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt; PoP A: healthy ---- announces prefix ----&amp;gt; stays in service
 PoP B: unhealthy -- withdraws prefix ----&amp;gt; traffic shifts to other PoPs
                                             as the withdrawal propagates
                                             (tens of seconds typical; can be longer)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Edge-to-Origin Backhaul
&lt;/h3&gt;

&lt;p&gt;Terminating at the edge solves the sender-facing leg. The data still has to reach your origin.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Keep &lt;strong&gt;persistent, pre-established connections&lt;/strong&gt; from each PoP to your origin or queue so forwarding never pays a fresh handshake.&lt;/li&gt;
&lt;li&gt;Where possible, use a private or provider backbone, such as AWS’s network behind Global Accelerator or a dedicated interconnect, rather than the public internet.&lt;/li&gt;
&lt;li&gt;Make backhaul failures survivable. The edge should buffer or fail over when the link to the origin degrades, and it should return a &lt;code&gt;5xx&lt;/code&gt; if it truly cannot store the event, so the sender retries.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When Anycast Is (and Isn’t) Worth It
&lt;/h2&gt;

&lt;p&gt;Anycast ingress is a strong fit when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Senders are geographically diverse, or you receive webhooks from many sources worldwide.&lt;/li&gt;
&lt;li&gt;Providers enforce tight timeouts, such as Shopify’s five seconds.&lt;/li&gt;
&lt;li&gt;You already need a global edge for DDoS absorption or for static IPs.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It is less compelling when:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Nearly all senders dispatch from a single region you can colocate with.&lt;/li&gt;
&lt;li&gt;Your real bottleneck is slow handler code, not the network. Moving to a queue-then-process pattern fixes that regardless of anycast.&lt;/li&gt;
&lt;li&gt;You don’t have the operational capacity to run BGP at multiple sites and would need to depend on a managed provider anyway.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Webhook senders enforce short acknowledgment windows: five seconds for Shopify and ten for GitHub, with Stripe asking for a quick &lt;code&gt;2xx&lt;/code&gt; and not publishing a number. A single-region endpoint makes distant senders pay several long round trips just to connect, and it exposes them to loss recovery that scales with distance.&lt;/p&gt;

&lt;p&gt;BGP anycast moves the sender’s handshake to a nearby PoP, shortens the segment where loss recovery happens, and lets you move events to your origin over connections you control. It does not give you instant failover or immunity from route changes. The realistic goal is to accept events locally and durably, acknowledge quickly, deduplicate downstream, and treat the occasional reset or slow convergence as a normal retry.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/anycast-routing-for-global-webhook-ingress-minimizing-latency-and-packet-loss-fo" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/anycast-routing-for-global-webhook-ingress-minimizing-latency-and-packet-loss-fo&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Zero-Copy Webhook Parsing: Optimizing Rust and Go Ingress Pipelines for 100k RPS</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Thu, 22 Oct 2026 02:11:40 +0000</pubDate>
      <link>https://dev.to/instawebhook/zero-copy-webhook-parsing-optimizing-rust-and-go-ingress-pipelines-for-100k-rps-4hgl</link>
      <guid>https://dev.to/instawebhook/zero-copy-webhook-parsing-optimizing-rust-and-go-ingress-pipelines-for-100k-rps-4hgl</guid>
      <description>&lt;p&gt;Imagine a webhook ingestion service, such as a durable intake layer like &lt;a href="https://instawebhook.com" rel="noopener noreferrer"&gt;InstaWebhook&lt;/a&gt; that accepts events, stores them and queues delivery work outside the request path. Suppose it has to absorb bursts of 100,000 requests per second (RPS). At that rate, small per-request costs add up quickly. A parser that makes 15 allocations per request produces 1.5 million allocations per second, which the allocator, and in Go the garbage collector, has to absorb.&lt;/p&gt;

&lt;p&gt;This article is about the parsing step of the pipeline. It shows how to verify signatures before parsing, borrow from the request buffer in Rust with &lt;code&gt;serde&lt;/code&gt;, cut allocations in Go with &lt;code&gt;easyjson&lt;/code&gt;, and what has changed in Go’s standard library as of Go 1.27. The 100k RPS figure is a design target for illustration, not a benchmark result. Measure your own payloads before drawing conclusions.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where the Allocations Come From
&lt;/h2&gt;

&lt;p&gt;A conventional parse lifecycle has several hidden costs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Buffering the body.&lt;/strong&gt; The request body has to be read into a contiguous buffer. In Go, &lt;code&gt;io.ReadAll&lt;/code&gt; grows a fresh slice on every call. In Rust frameworks, the body is collected into a buffer as well.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-field allocations.&lt;/strong&gt; A naive decoder allocates an owned &lt;code&gt;String&lt;/code&gt; (Rust) or &lt;code&gt;string&lt;/code&gt; (Go) for every key and value it keeps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reflection and interface boxing (Go).&lt;/strong&gt; Go’s &lt;code&gt;encoding/json&lt;/code&gt; historically inspects struct fields and types at runtime, which costs CPU and allocations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Parsing before authenticating.&lt;/strong&gt; If you build objects from an unauthenticated body, you spend CPU and memory on traffic you were going to reject anyway.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can see the allocation effect in the benchmarks published by the &lt;code&gt;easyjson&lt;/code&gt; project, which compare it against &lt;code&gt;encoding/json&lt;/code&gt;. These numbers come from the project’s README, were measured with Go 1.6 (2016), and use a Twitter API sample. The “small” case is an 80-byte slice of that sample and the “regular” case is the full 13 kB document.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Unmarshal benchmark&lt;/th&gt;
&lt;th&gt;Library&lt;/th&gt;
&lt;th&gt;Speed&lt;/th&gt;
&lt;th&gt;Allocs/op&lt;/th&gt;
&lt;th&gt;Bytes/op&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Small (80 B)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;encoding/json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9.7 MB/s&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;720&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Small (80 B)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;easyjson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;67 MB/s&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular (13 kB)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;encoding/json&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;22 MB/s&lt;/td&gt;
&lt;td&gt;218&lt;/td&gt;
&lt;td&gt;10,229&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Regular (13 kB)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;easyjson&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;125 MB/s&lt;/td&gt;
&lt;td&gt;128&lt;/td&gt;
&lt;td&gt;9,794&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;At 100k RPS, 14 allocations per request means 1.4 million allocations per second, and 218 allocations per request means about 21.8 million. The table also shows that the regular-sized document still costs &lt;code&gt;easyjson&lt;/code&gt; 128 allocations. Code generation reduces allocations but does not eliminate them, which is why the string-handling options below matter.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Verify First, Parse Second
&lt;/h2&gt;

&lt;p&gt;Every major webhook provider signs deliveries with an HMAC, so you can reject bad traffic before any JSON is parsed. The schemes differ in detail:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; sends &lt;code&gt;X-Hub-Signature-256&lt;/code&gt;, formatted &lt;code&gt;sha256=&amp;lt;hex&amp;gt;&lt;/code&gt;, computed as HMAC-SHA256 over the raw body. GitHub’s documentation recommends a constant-time comparison. The header is absent if no secret was configured on the webhook.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; sends a &lt;code&gt;Stripe-Signature&lt;/code&gt; header containing a timestamp (&lt;code&gt;t=&lt;/code&gt;) and one or more &lt;code&gt;v1=&lt;/code&gt; signatures. The signed string is the timestamp, a literal &lt;code&gt;.&lt;/code&gt;, and the raw body. A five-minute timestamp tolerance is the common default for rejecting replays.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify&lt;/strong&gt; sends &lt;code&gt;X-Shopify-Hmac-Sha256&lt;/code&gt;, an HMAC-SHA256 of the raw body encoded as base64 rather than hex.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Two rules follow from this. Always compute the HMAC over the exact bytes received, never over a re-serialized object, because re-encoding changes the bytes and breaks the signature. And always compare signatures in constant time. In Rust, the &lt;code&gt;hmac&lt;/code&gt; crate’s &lt;code&gt;verify_slice&lt;/code&gt; does this. In Go, use &lt;code&gt;hmac.Equal&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Rust: Borrowing from the Request Buffer
&lt;/h2&gt;

&lt;p&gt;Serde supports zero-copy deserialization. If a struct field is &lt;code&gt;&amp;amp;'a str&lt;/code&gt;, &lt;code&gt;serde_json&lt;/code&gt; can return a slice that points directly into the input buffer instead of allocating a &lt;code&gt;String&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There is one important catch. A string can only be borrowed if it contains no escape sequences. If a JSON string contains something like &lt;code&gt;\n&lt;/code&gt; or &lt;code&gt;\"&lt;/code&gt;, the decoder must rewrite it, so it cannot hand back a pointer into the original bytes. With a bare &lt;code&gt;&amp;amp;str&lt;/code&gt; field, deserialization then fails with an error. Serde cannot detect this at compile time, so the failure appears at runtime on real traffic.&lt;/p&gt;

&lt;p&gt;For untrusted input such as webhooks, use &lt;code&gt;Cow&amp;lt;'a, str&amp;gt;&lt;/code&gt; with &lt;code&gt;#[serde(borrow)]&lt;/code&gt; instead. It borrows when it can and falls back to an owned &lt;code&gt;String&lt;/code&gt; only when unescaping is required. Both the &lt;code&gt;Cow&lt;/code&gt; type and the attribute are needed, because serde defaults to the owned variant otherwise. (&lt;code&gt;#[serde(borrow)]&lt;/code&gt; is implicit for &lt;code&gt;&amp;amp;str&lt;/code&gt; and &lt;code&gt;&amp;amp;[u8]&lt;/code&gt; fields, but not for &lt;code&gt;Cow&lt;/code&gt;.)&lt;/p&gt;

&lt;p&gt;This also means axum’s standard &lt;code&gt;Json&lt;/code&gt; extractor is not the right tool for borrowed types, because it requires owned data. Take the body as &lt;code&gt;Bytes&lt;/code&gt; and call &lt;code&gt;serde_json::from_slice&lt;/code&gt; yourself, or use the &lt;code&gt;JsonDeserializer&lt;/code&gt; extractor from &lt;code&gt;axum-extra&lt;/code&gt;, which exists specifically for zero-copy deserialization.&lt;/p&gt;

&lt;p&gt;The payload below is illustrative. Real GitHub, Stripe and Shopify events are larger and nested.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight rust"&gt;&lt;code&gt;&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;std&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="nn"&gt;borrow&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;

&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;
    &lt;span class="nn"&gt;body&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nn"&gt;extract&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;DefaultBodyLimit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;http&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="nn"&gt;routing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;post&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;hmac&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Hmac&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Mac&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;serde&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Deserialize&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;sha2&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sha256&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;use&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;sync&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;HmacSha256&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Hmac&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Sha256&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Debug,&lt;/span&gt; &lt;span class="nd"&gt;Deserialize)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(borrow)]&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(borrow)]&lt;/span&gt;
    &lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nd"&gt;#[serde(borrow)]&lt;/span&gt;
    &lt;span class="n"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Cow&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nv"&gt;'a&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[derive(Clone)]&lt;/span&gt;
&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;Arc&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Sender&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;verify_github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;u8&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hex_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="nf"&gt;.strip_prefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"sha256="&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;hex&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;decode&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hex_sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;HmacSha256&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new_from_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;false&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="nf"&gt;.update&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="nf"&gt;.verify_slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="nf"&gt;.is_ok&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// constant-time comparison&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;ingest_webhook&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nf"&gt;State&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="n"&gt;State&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;AppState&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;HeaderMap&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;StatusCode&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// 1. Authenticate the raw bytes before parsing anything.&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;headers&lt;/span&gt;
        &lt;span class="nf"&gt;.get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"x-hub-signature-256"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;.and_then&lt;/span&gt;&lt;span class="p"&gt;(|&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="p"&gt;|&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="nf"&gt;.to_str&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="nf"&gt;.ok&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nf"&gt;verify_github&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;UNAUTHORIZED&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// 2. Hand off a cheap, reference-counted handle to the same buffer.&lt;/span&gt;
    &lt;span class="c1"&gt;//    Bytes::clone() bumps a refcount; it does not copy the payload.&lt;/span&gt;
    &lt;span class="k"&gt;match&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="py"&gt;.queue&lt;/span&gt;&lt;span class="nf"&gt;.try_send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(())&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;ACCEPTED&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="nf"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nn"&gt;StatusCode&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;SERVICE_UNAVAILABLE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// queue full: apply backpressure&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;Receiver&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;while&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Some&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="nf"&gt;.recv&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// The parsed struct borrows from `body` and lives only inside this scope.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nf"&gt;Ok&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;serde_json&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;from_slice&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;WebhookPayload&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nn"&gt;tracing&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nd"&gt;info!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="py"&gt;.action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"event received"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;#[tokio::main]&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;mpsc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;channel&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10_000&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;spawn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;worker&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rx&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;state&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;AppState&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;Arc&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;b"replace-with-your-webhook-secret"&lt;/span&gt;&lt;span class="nf"&gt;.as_slice&lt;/span&gt;&lt;span class="p"&gt;()),&lt;/span&gt;
        &lt;span class="n"&gt;queue&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;tx&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;};&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Router&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;new&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="nf"&gt;.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"/webhook"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nf"&gt;post&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ingest_webhook&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="nf"&gt;.layer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nn"&gt;DefaultBodyLimit&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1024&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c1"&gt;// cap request bodies at 1 MiB&lt;/span&gt;
        &lt;span class="nf"&gt;.with_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;state&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;listener&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;tokio&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;TcpListener&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;bind&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"0.0.0.0:8080"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="nn"&gt;axum&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;serve&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;listener&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="k"&gt;.await&lt;/span&gt;&lt;span class="nf"&gt;.unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details in this code matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Lifetimes.&lt;/strong&gt; A struct with a borrowed field cannot outlive the buffer it borrows from. Because it cannot be sent across tasks on its own, send the &lt;code&gt;Bytes&lt;/code&gt; handle instead. &lt;code&gt;Bytes&lt;/code&gt; is a cheaply cloneable, reference-counted buffer, so queueing it does not copy the payload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Body limits.&lt;/strong&gt; Axum’s &lt;code&gt;Bytes&lt;/code&gt; extractor refuses bodies larger than 2 MB by default. Set an explicit limit that matches your largest legitimate payload, as shown above.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Hot-path logging.&lt;/strong&gt; Avoid &lt;code&gt;println!&lt;/code&gt; on the hot path. Use a structured logger such as &lt;code&gt;tracing&lt;/code&gt; with sampling.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Scope of the claim.&lt;/strong&gt; “Zero-copy” here applies to string fields during parsing. The framework still buffers the body, the HMAC helper here decodes the hex signature into a small &lt;code&gt;Vec&lt;/code&gt;, and escaped strings fall back to owned &lt;code&gt;String&lt;/code&gt;s. Verify allocation counts with a profiler such as &lt;code&gt;dhat&lt;/code&gt; rather than assuming zero.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Go: Generated Parsers with &lt;code&gt;easyjson&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;encoding/json&lt;/code&gt; has traditionally relied on runtime reflection. &lt;code&gt;easyjson&lt;/code&gt; avoids that by generating &lt;code&gt;UnmarshalEasyJSON&lt;/code&gt; and &lt;code&gt;MarshalEasyJSON&lt;/code&gt; methods at build time. Its README claims it outperforms &lt;code&gt;encoding/json&lt;/code&gt; by a factor of 4–5x and other JSON packages by 2–3x. It also generates standard &lt;code&gt;MarshalJSON&lt;/code&gt;/&lt;code&gt;UnmarshalJSON&lt;/code&gt; methods, so types remain compatible with &lt;code&gt;encoding/json&lt;/code&gt;, but you only get the speedup by calling &lt;code&gt;easyjson.Unmarshal&lt;/code&gt; directly.&lt;/p&gt;

&lt;p&gt;Some practical details:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The generator does not work on &lt;code&gt;package main&lt;/code&gt; files, so put your types in a separate package.&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;//go:generate easyjson -all webhook.go&lt;/code&gt; and run &lt;code&gt;go generate&lt;/code&gt;. This produces &lt;code&gt;webhook_easyjson.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;By default, string fields are copied out of the input buffer, which costs allocations. A field tagged &lt;code&gt;json:"actor,nocopy"&lt;/code&gt; makes the string refer to the original buffer memory instead. That is only safe if the decoded object does not outlive the buffer. The README notes this works well for short-lived objects, and strings that need unescaping are processed normally.&lt;/li&gt;
&lt;li&gt;A &lt;code&gt;string&lt;/code&gt; field with the &lt;code&gt;intern&lt;/code&gt; option is deduplicated, which helps for fields that take only a few distinct values, such as an action name.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;easyjson&lt;/code&gt; uses &lt;code&gt;unsafe&lt;/code&gt; for no-copy conversions during unmarshaling. You can build without it using the &lt;code&gt;easyjson_nounsafe&lt;/code&gt; build tag.&lt;/li&gt;
&lt;li&gt;Its own limitations are documented: object keys are case-sensitive, there is no true streaming, and the parser does only the minimum work needed to skip unmatched values rather than fully validating them.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// webhook.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;

&lt;span class="c"&gt;//go:generate easyjson -all webhook.go&lt;/span&gt;

&lt;span class="k"&gt;type&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt; &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;EventID&lt;/span&gt;   &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"event_id"`&lt;/span&gt;
    &lt;span class="n"&gt;Action&lt;/span&gt;    &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"action,intern"`&lt;/span&gt;
    &lt;span class="n"&gt;Timestamp&lt;/span&gt; &lt;span class="kt"&gt;uint64&lt;/span&gt; &lt;span class="s"&gt;`json:"timestamp"`&lt;/span&gt;
    &lt;span class="n"&gt;Actor&lt;/span&gt;     &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="s"&gt;`json:"actor"`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight go"&gt;&lt;code&gt;&lt;span class="c"&gt;// handler.go&lt;/span&gt;
&lt;span class="k"&gt;package&lt;/span&gt; &lt;span class="n"&gt;webhook&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="s"&gt;"bytes"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/hmac"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/sha256"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/hex"&lt;/span&gt;
    &lt;span class="s"&gt;"net/http"&lt;/span&gt;
    &lt;span class="s"&gt;"strings"&lt;/span&gt;
    &lt;span class="s"&gt;"sync"&lt;/span&gt;

    &lt;span class="s"&gt;"github.com/mailru/easyjson"&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;maxBody&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="m"&gt;1&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="m"&gt;20&lt;/span&gt; &lt;span class="c"&gt;// 1 MiB&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;bufPool&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sync&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Pool&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="n"&gt;any&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nb"&gt;new&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="kt"&gt;string&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="kt"&gt;bool&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"sha256="&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;strings&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HasPrefix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hex&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DecodeString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prefix&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="no"&gt;false&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sha256&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;New&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;hmac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Equal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;got&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Sum&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="no"&gt;nil&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="c"&gt;// constant-time comparison&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="kt"&gt;byte&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HandlerFunc&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ResponseWriter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;bufPool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;bytes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Reset&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;defer&lt;/span&gt; &lt;span class="n"&gt;bufPool&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Put&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="c"&gt;// Bound the body size and reuse a pooled buffer instead of io.ReadAll.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ReadFrom&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxBytesReader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;maxBody&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"payload too large or unreadable"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusRequestEntityTooLarge&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Bytes&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

        &lt;span class="c"&gt;// Authenticate the raw bytes first.&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;validSignature&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Header&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"X-Hub-Signature-256"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid signature"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusUnauthorized&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;Payload&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;easyjson&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unmarshal&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt; &lt;span class="n"&gt;err&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="no"&gt;nil&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"invalid JSON"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusBadRequest&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;return&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;

        &lt;span class="c"&gt;// Strings in p are copies (no "nocopy" tags), so p is safe to use&lt;/span&gt;
        &lt;span class="c"&gt;// after the buffer returns to the pool. If you add "nocopy", it is not.&lt;/span&gt;
        &lt;span class="n"&gt;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;WriteHeader&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StatusAccepted&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The pooled buffer cuts the per-request allocation of the body slice. That is useful, but be careful: the moment you tag a field &lt;code&gt;nocopy&lt;/code&gt;, you must finish using the decoded object before the deferred &lt;code&gt;Put&lt;/code&gt; runs.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. What Changed in Go Recently
&lt;/h2&gt;

&lt;p&gt;Two Go releases change the picture for this kind of service:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Green Tea garbage collector (Go 1.26).&lt;/strong&gt; It was an experiment in Go 1.25 and is enabled by default in Go 1.26. The release notes say it improves marking and scanning of small objects and expect a 10–40% reduction in GC overhead for programs that lean heavily on the garbage collector. That helps allocation-heavy parsers, but it does not remove the benefit of allocating less.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;encoding/json/v2&lt;/code&gt; (Go 1.27).&lt;/strong&gt; Go 1.27 was released on August 19, 2026. It makes &lt;code&gt;encoding/json/v2&lt;/code&gt; and &lt;code&gt;encoding/json/jsontext&lt;/code&gt; generally available, and &lt;code&gt;encoding/json&lt;/code&gt; itself is now backed by the v2 implementation, with the stated goal of faster unmarshaling. The v2 API has stricter defaults than v1, such as rejecting duplicate object keys and invalid UTF-8, so don’t assume switching is behavior-neutral. One independent benchmark found &lt;code&gt;encoding/json/v2&lt;/code&gt; unmarshaling 1.5x to 2.3x faster than the original implementation, with mixed marshaling results depending on the workload.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The practical consequence is that the headline gap between &lt;code&gt;easyjson&lt;/code&gt; and the standard library may be smaller today than the 2016-era README numbers suggest. Benchmark &lt;code&gt;easyjson&lt;/code&gt; against Go 1.27’s &lt;code&gt;encoding/json&lt;/code&gt; and &lt;code&gt;encoding/json/v2&lt;/code&gt; on your actual payloads before adding a code generator to your build.&lt;/p&gt;

&lt;p&gt;If you are considering &lt;code&gt;json-iterator/go&lt;/code&gt; as a reflection-free alternative, note that it is not actually reflection-free. It builds and caches per-type decoders using reflection (through &lt;code&gt;reflect2&lt;/code&gt;), and its repository was archived on December 15, 2025. For a new service, &lt;code&gt;easyjson&lt;/code&gt; or the standard library are safer choices.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Measure It Yourself
&lt;/h2&gt;

&lt;p&gt;Treat any numbers in this article, including the ones above, as starting points. To get figures that apply to your service:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use a realistic payload.&lt;/strong&gt; Benchmark with real, anonymized webhook bodies from the providers you integrate with, not a synthetic struct.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Count allocations.&lt;/strong&gt; In Go, run &lt;code&gt;go test -bench . -benchmem&lt;/code&gt; and watch &lt;code&gt;allocs/op&lt;/code&gt;. In Rust, use a profiler like &lt;code&gt;dhat&lt;/code&gt; or a counting allocator in tests.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Load test end to end.&lt;/strong&gt; Parsing is one stage. Network I/O, TLS, signature verification, queue writes and storage often dominate once parsing is fast.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch tail latency.&lt;/strong&gt; Compare p99 latency and GC pause or allocator behavior under sustained load, not only averages.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Key Takeaways
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Authenticate raw bytes before parsing.&lt;/strong&gt; It is cheaper, safer, and the only way to get the signature right, since all three major providers sign the exact body they send.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Rust, use &lt;code&gt;Cow&amp;lt;'a, str&amp;gt;&lt;/code&gt; with &lt;code&gt;#[serde(borrow)]&lt;/code&gt; for untrusted JSON.&lt;/strong&gt; Bare &lt;code&gt;&amp;amp;str&lt;/code&gt; fields fail at runtime on any escaped string.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enqueue &lt;code&gt;Bytes&lt;/code&gt;, not parsed structs.&lt;/strong&gt; A reference-counted buffer handle crosses task boundaries cheaply.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;In Go, &lt;code&gt;easyjson&lt;/code&gt; reduces allocations but does not remove them.&lt;/strong&gt; Its &lt;code&gt;nocopy&lt;/code&gt; and &lt;code&gt;intern&lt;/code&gt; tags cut them further, at the price of buffer-lifetime discipline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Re-benchmark on current Go.&lt;/strong&gt; Go 1.26’s default GC and Go 1.27’s v2-backed &lt;code&gt;encoding/json&lt;/code&gt; both change the baseline.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set explicit body-size limits&lt;/strong&gt; in both languages, and measure with your own traffic.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://pkg.go.dev/github.com/mailru/easyjson" rel="noopener noreferrer"&gt;easyjson README and benchmarks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/webhooks/securing" rel="noopener noreferrer"&gt;GitHub: Validating webhook deliveries&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/spring-web/latest/spring_web/extractor/struct.DefaultBodyLimit.html" rel="noopener noreferrer"&gt;axum &lt;code&gt;DefaultBodyLimit&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.rs/crate/axum-extra/latest/source/src/extract/json_deserializer.rs" rel="noopener noreferrer"&gt;axum-extra &lt;code&gt;JsonDeserializer&lt;/code&gt; (zero-copy JSON extractor)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://yossarian.net/til/post/serde-s-borrowing-can-be-treacherous/" rel="noopener noreferrer"&gt;serde’s borrowing can be treacherous&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.googlesource.com/website/+/cd9f0a846a136f7b452395772638018114795f37/_content/doc/go1.26.md" rel="noopener noreferrer"&gt;Go 1.26 release notes (Green Tea GC)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.googlesource.com/website/+/refs/heads/master/_content/blog/go1.27.md" rel="noopener noreferrer"&gt;Go 1.27 release announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://go.googlesource.com/website/+show/33b532a2f57ccceaeaf826d86ca10b069ce5cd9e/_content/blog/jsonv2-exp.md" rel="noopener noreferrer"&gt;Go’s experimental JSON v2 announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://lemire.me/blog" rel="noopener noreferrer"&gt;The new Go JSON API: twice as fast, or 1.5x slower?&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/zero-copy-webhook-parsing-optimizing-rust-and-go-ingress-pipelines-for-100k-rps" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/zero-copy-webhook-parsing-optimizing-rust-and-go-ingress-pipelines-for-100k-rps&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
  </channel>
</rss>
