<?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>Ingress Security &amp; Cryptographic Verification: Enterprise Webhook Hardening Guide</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Fri, 06 Nov 2026 02:20:06 +0000</pubDate>
      <link>https://dev.to/instawebhook/ingress-security-cryptographic-verification-enterprise-webhook-hardening-guide-2ecf</link>
      <guid>https://dev.to/instawebhook/ingress-security-cryptographic-verification-enterprise-webhook-hardening-guide-2ecf</guid>
      <description>&lt;p&gt;Webhooks are how platforms such as Stripe, GitHub and Shopify tell your systems that something happened: a payment cleared, a repository changed, an order shipped. Each one is an unsolicited HTTP request that your infrastructure has to authenticate before acting on it. Every webhook therefore has two security questions: &lt;strong&gt;is this payload really from the sender (ingress verification)&lt;/strong&gt;, and, if you are the sender, &lt;strong&gt;is this delivery really reaching the intended customer (outbound path integrity)&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;This guide covers both. It looks at how to prepare webhook signature verification for post-quantum cryptography with an ML-DSA (FIPS 204) dual-signature verifier, and how senders can reduce the risk of BGP hijacking and DNS spoofing with RPKI, DNSSEC and DANE. It also says where each control stops helping, because several popular claims about these technologies are overstated.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Information is current as of November 2026.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Where Webhook Signatures Stand Today
&lt;/h2&gt;

&lt;p&gt;The major providers’ documented schemes are still symmetric HMAC-SHA256, and the details differ:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stripe&lt;/strong&gt; sends a &lt;code&gt;Stripe-Signature&lt;/code&gt; header of the form &lt;code&gt;t=&amp;lt;timestamp&amp;gt;,v1=&amp;lt;hex&amp;gt;&lt;/code&gt;. The signed string is &lt;code&gt;&amp;lt;timestamp&amp;gt;.&amp;lt;raw body&amp;gt;&lt;/code&gt;, and the official libraries reject timestamps outside a default tolerance of five minutes. During secret rotation the header can carry several &lt;code&gt;v1&lt;/code&gt; values.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GitHub&lt;/strong&gt; sends &lt;code&gt;X-Hub-Signature-256: sha256=&amp;lt;hex&amp;gt;&lt;/code&gt;, computed over the raw body only. There is no signed timestamp, so receivers should deduplicate on the delivery ID to limit replay.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Shopify&lt;/strong&gt; sends a base64-encoded HMAC-SHA256 in &lt;code&gt;X-Shopify-Hmac-SHA256&lt;/code&gt;, with no timestamp in the signed data.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;HMAC-SHA256 is not broken, and it is not threatened by quantum computers in any practical sense. Quantum algorithms give only a quadratic speedup against symmetric primitives. The post-quantum standards bodies’ deprecation schedules target public-key algorithms (RSA, ECDSA, EdDSA, Diffie-Hellman), not SHA-2 or AES-256.&lt;/p&gt;

&lt;p&gt;HMAC does have structural limits at enterprise scale:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Secret distribution and rotation.&lt;/strong&gt; Every sender–receiver pair needs its own shared secret, and every receiver has to store it. A leak at either end lets an attacker mint valid signatures.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No non-repudiation.&lt;/strong&gt; Because both parties hold the key, a signature cannot prove to a third party which side created it. That matters if you archive webhooks as audit evidence.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Receiver-side forgery.&lt;/strong&gt; A compromised receiver can fabricate “valid” events as easily as verify them.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Asymmetric signatures fix these problems: the sender holds a private key, and receivers hold only a public key. Today that means Ed25519 or ECDSA, which are exactly the algorithms a future quantum computer would break. This is the real argument for post-quantum signatures in webhooks, and it is a narrower claim than “HMAC is obsolete”.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Post-Quantum Signatures for Webhooks: The Honest Threat Model
&lt;/h2&gt;

&lt;p&gt;In August 2024, NIST published its first three finalized post-quantum standards:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FIPS 203:&lt;/strong&gt; ML-KEM (derived from CRYSTALS-Kyber), a key-encapsulation mechanism&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FIPS 204:&lt;/strong&gt; ML-DSA (derived from CRYSTALS-Dilithium), a lattice-based signature scheme&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FIPS 205:&lt;/strong&gt; SLH-DSA (derived from SPHINCS+), a stateless hash-based signature scheme&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;NIST’s transition plan, IR 8547, was finalized on October 27, 2026. It deprecates quantum-vulnerable public-key algorithms after 2030 and disallows them after 2035. The final version removes the draft’s distinction between key sizes, so RSA, ECDSA and EdDSA are deprecated at every approved strength. NIST has said the 2035 disallowance is not intended to cover hybrid modes that pair an approved post-quantum algorithm with a classical one. US federal directives set earlier targets for high-value assets: post-quantum key establishment by the end of 2030 and post-quantum signatures by the end of 2031.&lt;/p&gt;

&lt;p&gt;Two points keep this in proportion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Signature forgery needs a quantum computer at verification time.&lt;/strong&gt; A webhook signed today with ECDSA cannot be forged retroactively by a machine that appears in 2035 and used to authenticate a request that was accepted in 2026. The “harvest now, decrypt later” threat applies mainly to confidentiality (TLS key exchange), not to short-lived authentication tokens. Signatures that must stay verifiable for years, such as archived audit trails, are the exception.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compliance deadlines are the practical driver.&lt;/strong&gt; If you sell to regulated or government customers, you will be asked about your cryptographic inventory and migration plan well before a quantum computer exists.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sensible goal is therefore &lt;strong&gt;cryptographic agility&lt;/strong&gt;: build verification so that the algorithm is a configuration choice, then migrate on your own schedule.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Designing a Dual-Signature Verification Proxy
&lt;/h2&gt;

&lt;p&gt;A dual-signature verifier sits at your ingress, whether as a gateway plugin or as middleware in front of your services. It authenticates each request against whichever signature schemes the sender supplies, applies a policy, and forwards only verified requests.&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 (raw body + headers)
                            │
                            ▼
              ┌───────────────────────────┐
              │  Dual-signature verifier  │
              │  1. check timestamp       │
              │  2. build signed message  │
              └───────┬───────────┬───────┘
                      ▼           ▼
             HMAC-SHA256      ML-DSA-65
             (shared secret)  (sender public key)
                      │           │
                      └─────┬─────┘
                            ▼
                    Policy evaluation
          (accept either / require both / require PQ)
                            │
                            ▼
              Forward to internal service
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An illustrative request during a migration window looks like this. The header names are examples, because no industry standard for dual webhook signatures currently exists:&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;/webhooks/events&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;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="na"&gt;Webhook-Timestamp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;1767225600&lt;/span&gt;
&lt;span class="na"&gt;Webhook-Signature-HMAC&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;9f8a...4b2e&lt;/span&gt;
&lt;span class="na"&gt;Webhook-Signature-MLDSA65&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;MEUCIQ...(base64, about 4.4 kB)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Three design rules matter more than the algorithms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Verify the raw bytes.&lt;/strong&gt; Parsing and re-serializing JSON before verification breaks signatures and opens parser-differential attacks.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Sign a timestamp together with the body&lt;/strong&gt; (as Stripe does) and enforce a tolerance window. Also deduplicate on an event or delivery ID, because a timestamp window only narrows replay and does not eliminate it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a domain-separation context for ML-DSA.&lt;/strong&gt; FIPS 204 lets the signer bind a context string of up to 255 bytes into the signature, which prevents a signature made for one purpose from being replayed for another.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Choosing a policy
&lt;/h3&gt;

&lt;p&gt;The policy you pick determines what the dual signature actually buys you:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Policy&lt;/th&gt;
&lt;th&gt;Passes when&lt;/th&gt;
&lt;th&gt;Use it for&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AcceptEither&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HMAC or ML-DSA is valid&lt;/td&gt;
&lt;td&gt;Early migration and telemetry. This adds no forgery resistance beyond HMAC alone: anyone who can strip the ML-DSA header falls back to HMAC.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequireBoth&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;HMAC and ML-DSA are both valid&lt;/td&gt;
&lt;td&gt;Hybrid assurance. An attacker must compromise both the shared secret and the signing key.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RequirePQ&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;ML-DSA is valid&lt;/td&gt;
&lt;td&gt;End state, once all senders have migrated.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  4. Implementation in Go
&lt;/h2&gt;

&lt;p&gt;Go 1.27, released in August 2026, ships &lt;code&gt;crypto/mldsa&lt;/code&gt; in the standard library. It implements FIPS 204 with &lt;code&gt;MLDSA44&lt;/code&gt;, &lt;code&gt;MLDSA65&lt;/code&gt; and &lt;code&gt;MLDSA87&lt;/code&gt; parameter sets, and &lt;code&gt;Verify&lt;/code&gt; accepts an &lt;code&gt;Options&lt;/code&gt; value carrying the context string. The following verifier uses only the standard library:&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;webhookverify&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/mldsa"&lt;/span&gt;
    &lt;span class="s"&gt;"crypto/sha256"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/base64"&lt;/span&gt;
    &lt;span class="s"&gt;"encoding/hex"&lt;/span&gt;
    &lt;span class="s"&gt;"errors"&lt;/span&gt;
    &lt;span class="s"&gt;"strconv"&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;type&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="kt"&gt;int&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;AcceptEither&lt;/span&gt; &lt;span class="n"&gt;Policy&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;iota&lt;/span&gt; &lt;span class="c"&gt;// migration: HMAC or ML-DSA&lt;/span&gt;
    &lt;span class="n"&gt;RequireBoth&lt;/span&gt;                &lt;span class="c"&gt;// hybrid: HMAC and ML-DSA&lt;/span&gt;
    &lt;span class="n"&gt;RequirePQ&lt;/span&gt;                  &lt;span class="c"&gt;// end state: ML-DSA only&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c"&gt;// Domain-separation context bound into every ML-DSA signature.&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;pqContext&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"webhook-v1"&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;ErrBadTimestamp&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;"invalid timestamp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrStale&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;"timestamp outside tolerance window"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;ErrInvalidSignature&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;"signature verification failed"&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;Verifier&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;HMACSecret&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;// may be nil once HMAC is retired&lt;/span&gt;
    &lt;span class="n"&gt;PQKey&lt;/span&gt;      &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;mldsa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PublicKey&lt;/span&gt; &lt;span class="c"&gt;// parsed once with mldsa.NewPublicKey(mldsa.MLDSA65(), raw)&lt;/span&gt;
    &lt;span class="n"&gt;Tolerance&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;Duration&lt;/span&gt;    &lt;span class="c"&gt;// e.g. 5 * time.Minute&lt;/span&gt;
    &lt;span class="n"&gt;Policy&lt;/span&gt;     &lt;span class="n"&gt;Policy&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Verify checks the raw request body against the timestamp and signature headers.&lt;/span&gt;
&lt;span class="c"&gt;// The signed message is "&amp;lt;timestamp&amp;gt;.&amp;lt;raw body&amp;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;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Verifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Verify&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;tsHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmacHex&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pqBase64&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;now&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;Time&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;ts&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;strconv&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ParseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tsHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="m"&gt;64&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="n"&gt;ErrBadTimestamp&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&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;Sub&lt;/span&gt;&lt;span class="p"&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;Unix&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ts&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tolerance&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;age&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Tolerance&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;ErrStale&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="nb"&gt;append&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;tsHeader&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;body&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;hmacOK&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkHMAC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;hmacHex&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;pqOK&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;checkPQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pqBase64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Policy&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;AcceptEither&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hmacOK&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="n"&gt;pqOK&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;case&lt;/span&gt; &lt;span class="n"&gt;RequireBoth&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;hmacOK&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;pqOK&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;case&lt;/span&gt; &lt;span class="n"&gt;RequirePQ&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;pqOK&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="p"&gt;}&lt;/span&gt;
    &lt;span class="c"&gt;// Log which check failed internally; do not tell the caller.&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;ErrInvalidSignature&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;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Verifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;checkHMAC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&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;sigHex&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;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;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HMACSecret&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;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;sigHex&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;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;HMACSecret&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;msg&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;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="n"&gt;got&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Verifier&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;checkPQ&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;msg&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;sigB64&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;if&lt;/span&gt; &lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PQKey&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;sig&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;base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;StdEncoding&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;sigB64&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="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;mldsa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Verify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PQKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;msg&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;mldsa&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Options&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;pqContext&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Also cap the request body size before reading it, and cache the parsed public key rather than parsing it on every request.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. The Operational Cost: Sizes, Headers and Key Distribution
&lt;/h2&gt;

&lt;p&gt;ML-DSA verification is not computationally heavy, but the objects are large. The FIPS 204 sizes for ML-DSA-65 are below. The ECDSA figures are the raw encodings used by JOSE’s ES256.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;HMAC-SHA256&lt;/th&gt;
&lt;th&gt;ECDSA P-256&lt;/th&gt;
&lt;th&gt;ML-DSA-65&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Verification key&lt;/td&gt;
&lt;td&gt;Shared secret&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;1,952 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature / tag&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;3,309 bytes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Signature as base64&lt;/td&gt;
&lt;td&gt;44 characters&lt;/td&gt;
&lt;td&gt;88 characters&lt;/td&gt;
&lt;td&gt;4,412 characters&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Benchmark verification on your own hardware rather than relying on published microsecond figures, because results vary widely by CPU, library and implementation. Signature size is the more predictable cost.&lt;/p&gt;

&lt;p&gt;A 4.4 kB header fits comfortably in most defaults, but not in all of them. Per-line and total limits vary: nginx’s &lt;code&gt;large_client_header_buffers&lt;/code&gt; defaults to four buffers of 8 KB (and a single header line must fit within one buffer), Apache’s &lt;code&gt;LimitRequestFieldSize&lt;/code&gt; defaults to 8,190 bytes, and Envoy’s &lt;code&gt;max_request_headers_kb&lt;/code&gt; defaults to 60 KiB. Older or constrained intermediaries, such as embedded proxies and legacy application servers, may have much lower limits. Test the full path from sender to verifier before you rely on a header-carried ML-DSA signature. If it does not fit, a signature in the request body envelope or a trailer is an alternative.&lt;/p&gt;

&lt;h3&gt;
  
  
  Publishing public keys
&lt;/h3&gt;

&lt;p&gt;Receivers need a way to fetch and rotate sender public keys. In May 2026 the IETF published &lt;strong&gt;RFC 9964&lt;/strong&gt;, which defines how ML-DSA keys and signatures are expressed in JOSE and COSE. It introduces a new key type, &lt;code&gt;AKP&lt;/code&gt; (Algorithm Key Pair), where the algorithm is named in &lt;code&gt;alg&lt;/code&gt; and the public key is carried base64url-encoded in &lt;code&gt;pub&lt;/code&gt;. A published JWKS for an ML-DSA-65 key therefore looks like this, not like the &lt;code&gt;OKP&lt;/code&gt; type used for Ed25519:&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;"keys"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kty"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"AKP"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"alg"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ML-DSA-65"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"kid"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webhooks-2026-11"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"pub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"&amp;lt;base64url, 1,952 raw bytes, about 2.6 kB encoded&amp;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="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;p&gt;Receivers should cache keys by &lt;code&gt;kid&lt;/code&gt;, refresh them on a schedule or on an unknown &lt;code&gt;kid&lt;/code&gt; (with rate limiting), and never fetch the JWKS inside the request-verification path.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. Outbound Path Integrity: BGP Hijacking and DNS Spoofing
&lt;/h2&gt;

&lt;p&gt;If you &lt;em&gt;send&lt;/em&gt; webhooks, your concern is different: you are making an outbound HTTPS request to a customer-controlled URL, and you want to be sure it reaches that customer.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this threat is real
&lt;/h3&gt;

&lt;p&gt;The best-known illustration is the April 24, 2018 incident. Attackers announced more-specific BGP prefixes covering Amazon Route 53 name servers for about two hours, redirected DNS queries to a rogue DNS server, and pointed &lt;code&gt;myetherwallet.com&lt;/code&gt; at a phishing site, draining roughly $150,000 in Ethereum. AWS stated that neither it nor Route 53 had been hacked or compromised. The phishing site used a self-signed certificate that browsers flagged, which is why the attack was only partly successful.&lt;/p&gt;

&lt;p&gt;A harder variant targets certificate issuance. If an attacker hijacks routes to a victim’s server, a Certificate Authority performing a single-vantage-point domain check may issue the attacker a valid certificate for the victim’s name, and TLS then validates against the attacker’s server. This was demonstrated by Princeton researchers and prompted the industry’s response.&lt;/p&gt;

&lt;h3&gt;
  
  
  What has already improved
&lt;/h3&gt;

&lt;p&gt;The CA/Browser Forum’s Ballot SC-067 requires &lt;strong&gt;Multi-Perspective Issuance Corroboration (MPIC)&lt;/strong&gt;: CAs must check domain control from several network locations and compare the results. It took effect for publicly trusted TLS certificates on March 15, 2025, with enforcement strengthening through September 2025 and further requirements scheduled for 2026. Let’s Encrypt had already been using multi-perspective validation in production since 2020. This sharply reduces the “hijack the route, win the certificate” attack against public CAs, but it does not eliminate the underlying routing problem.&lt;/p&gt;

&lt;h3&gt;
  
  
  Defense layers and their limits
&lt;/h3&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;What it protects&lt;/th&gt;
&lt;th&gt;What it does &lt;em&gt;not&lt;/em&gt; do&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;RPKI route origin validation (ROV)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Detects announcements where the origin AS is not authorized by a ROA for that prefix&lt;/td&gt;
&lt;td&gt;Does nothing for prefixes without ROAs. It does not stop forged-origin hijacks that keep the legitimate AS at the end of the path, and it does not validate the full AS path.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DNSSEC&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Authenticates DNS answers, so spoofed records are rejected&lt;/td&gt;
&lt;td&gt;Does nothing to routing, and only works for zones that are signed.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;DANE (TLSA records)&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Pins which certificate or key a host may present, taken from DNSSEC-signed data&lt;/td&gt;
&lt;td&gt;Not supported by web browsers. It is practical only for software you control, such as a webhook dispatcher.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Mutual TLS or signed payloads&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Lets the receiver prove identity independently of the path&lt;/td&gt;
&lt;td&gt;Requires customer cooperation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Adoption is improving: IPinfo reported in October 2026 that roughly 70% of routed prefixes were RPKI-valid, up from about 54% a year earlier (figures vary by methodology and by region). Many networks still do not publish ROAs, and ROV only helps where networks along the path actually reject invalid routes.&lt;/p&gt;

&lt;p&gt;The practical consequence is that &lt;strong&gt;DANE with DNSSEC gives a sender the strongest guarantee&lt;/strong&gt;, because it ties the expected key to the domain name rather than to the network path. If a hijacker diverts your connection, the rogue server cannot present a key matching the DNSSEC-signed TLSA record, and the connection fails. RPKI checks are useful defense in depth, mainly for detecting anomalies and limiting the exposure window, but they should not be your only control.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Check route origin validity with RPKI
&lt;/h3&gt;

&lt;p&gt;Routinator, an open-source RPKI validator, exposes an HTTP API (default &lt;code&gt;127.0.0.1:8323&lt;/code&gt;) with a validity endpoint for a given origin AS and prefix. It also speaks the RPKI-to-Router protocol (RTR, default port 3323) to routers, which is a separate interface from the HTTP API.&lt;/p&gt;

&lt;p&gt;The check needs a prefix and an origin AS that you observe, for example from your own BGP feed or a route collector such as RIPE RIS or RouteViews. It does not take a bare IP address and an “expected” AS.&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;requests&lt;/span&gt;

&lt;span class="n"&gt;ROUTINATOR&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://127.0.0.1:8323&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;rpki_state&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;origin_asn&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;prefix&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="sh"&gt;"""&lt;/span&gt;&lt;span class="s"&gt;Return &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;valid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;invalid&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt; or another state (e.g. not-found) from a local Routinator.&lt;/span&gt;&lt;span class="sh"&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;requests&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="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ROUTINATOR&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;/api/v1/validity/AS&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;origin_asn&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;prefix&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&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="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;raise_for_status&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;validated_route&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;validity&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;state&lt;/span&gt;&lt;span class="sh"&gt;"&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="c1"&gt;# Policy sketch:
#   invalid   -&amp;gt; do not connect; alert
#   valid     -&amp;gt; proceed
#   not found -&amp;gt; proceed, but rely on DANE/mTLS and log it
#   error     -&amp;gt; decide per your risk tolerance (fail open vs closed)
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: Pin certificates with DNSSEC and DANE
&lt;/h3&gt;

&lt;p&gt;DANE is defined in RFC 6698, with operational guidance in RFC 7671. A TLSA record published at &lt;code&gt;_443._tcp.&amp;lt;host&amp;gt;&lt;/code&gt; states which certificate or public key a server must present. The common “DANE-EE” form &lt;code&gt;3 1 1&lt;/code&gt; means: usage 3 (end-entity certificate, no CA trust required), selector 1 (SubjectPublicKeyInfo), matching type 1 (SHA-256).&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.api.example.com. IN TLSA 3 1 1 &amp;lt;SHA-256 of the server's SPKI, hex&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sender below resolves the TLSA record, requires the resolver to report DNSSEC validation, completes the TLS handshake, and compares the presented key to the pinned hash &lt;strong&gt;on the same connection&lt;/strong&gt; that then carries the payload. Checking on one connection and sending on another would leave a gap in which the destination could change. The connection still uses normal certificate validation, which is stricter than DANE-EE requires but harmless as defense in depth.&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;hashlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;http.client&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ssl&lt;/span&gt;

&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dns.flags&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;dns.resolver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;x509&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;cryptography.hazmat.primitives&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;serialization&lt;/span&gt;


&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;PathValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Exception&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;def&lt;/span&gt; &lt;span class="nf"&gt;tlsa_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&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;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resolver_ip&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;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;127.0.0.1&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;Fetch TLSA records via a *trusted local validating resolver*.&lt;/span&gt;&lt;span class="sh"&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;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;resolver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Resolver&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;configure&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;False&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="n"&gt;nameservers&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;resolver_ip&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="nf"&gt;use_edns&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;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DO&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1232&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# request DNSSEC data
&lt;/span&gt;    &lt;span class="n"&gt;answer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;resolve&lt;/span&gt;&lt;span class="p"&gt;(&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;_&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt;._tcp.&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="si"&gt;}&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;TLSA&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="c1"&gt;# The AD bit is only meaningful if the path to the resolver is trusted
&lt;/span&gt;    &lt;span class="c1"&gt;# (loopback or a private link) and the resolver itself validates DNSSEC.
&lt;/span&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;answer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;dns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;flags&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;AD&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PathValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;TLSA answer was not DNSSEC-validated (AD flag missing)&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="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="n"&gt;usage&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="n"&gt;selector&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="n"&gt;mtype&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="n"&gt;cert&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;a&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;answer&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;post_with_dane&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&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;path&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;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;bytes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;443&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;records&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tlsa_records&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;conn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;http&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="nc"&gt;HTTPSConnection&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;port&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;ssl&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_default_context&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="mi"&gt;5&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;()&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;der&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sock&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getpeercert&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;binary_form&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;spki&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;x509&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load_der_x509_certificate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;der&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;public_key&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;public_bytes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Encoding&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DER&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;serialization&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;PublicFormat&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;SubjectPublicKeyInfo&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;digest&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;hashlib&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sha256&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;spki&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="c1"&gt;# Support only DANE-EE / SPKI / SHA-256 (3 1 1) in this sketch.
&lt;/span&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nf"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;u&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="ow"&gt;and&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;digest&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;u&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="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;c&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;records&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;PathValidationError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Server key does not match DNSSEC-signed TLSA record&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;POST&lt;/span&gt;&lt;span class="sh"&gt;"&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;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="n"&gt;headers&lt;/span&gt;&lt;span class="o"&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;Content-Type&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;application/json&lt;/span&gt;&lt;span class="sh"&gt;"&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;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getresponse&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;resp&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;resp&lt;/span&gt;&lt;span class="p"&gt;.&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;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;conn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Operational caveats:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The resolver is part of your trust base.&lt;/strong&gt; Run a validating resolver locally or on a protected link. The &lt;code&gt;AD&lt;/code&gt; flag from an untrusted resolver proves nothing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Many customers will not have DNSSEC or TLSA records.&lt;/strong&gt; Treat DANE as an opt-in tier (for example, for customers who publish TLSA records), with a defined fallback policy for everyone else.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Rotate carefully.&lt;/strong&gt; Publish the new TLSA record before switching keys, and keep both during the overlap, or deliveries will fail during rotation.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  7. Comparing Approaches
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Concern&lt;/th&gt;
&lt;th&gt;Typical today&lt;/th&gt;
&lt;th&gt;Hardened design&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Payload authenticity&lt;/td&gt;
&lt;td&gt;HMAC-SHA256 with a shared secret&lt;/td&gt;
&lt;td&gt;HMAC plus ML-DSA, moving to ML-DSA only&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-repudiation&lt;/td&gt;
&lt;td&gt;None (symmetric key)&lt;/td&gt;
&lt;td&gt;Public-key signature&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Replay protection&lt;/td&gt;
&lt;td&gt;Timestamp window (Stripe) or none (GitHub, Shopify)&lt;/td&gt;
&lt;td&gt;Signed timestamp, tolerance window and event-ID deduplication&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Algorithm change&lt;/td&gt;
&lt;td&gt;Code change per integration&lt;/td&gt;
&lt;td&gt;Configuration-driven policy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Route hijack detection&lt;/td&gt;
&lt;td&gt;None at the application layer&lt;/td&gt;
&lt;td&gt;RPKI origin check (where ROAs exist)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS integrity&lt;/td&gt;
&lt;td&gt;Unauthenticated lookups&lt;/td&gt;
&lt;td&gt;DNSSEC-validated resolution&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Endpoint identity&lt;/td&gt;
&lt;td&gt;Public CA trust (hardened by MPIC)&lt;/td&gt;
&lt;td&gt;DANE pin, plus optional mutual TLS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  8. Action Plan
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Inventory your webhook flows.&lt;/strong&gt; List every provider you receive from and every customer endpoint you send to, with the signature scheme and TLS configuration of each. A cryptographic inventory is the first step in NIST-aligned migration plans.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fix the basics first.&lt;/strong&gt; Verify HMACs over raw bytes with constant-time comparison, enforce a timestamp window where the provider signs one, deduplicate by event ID, and support multiple active secrets for rotation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make the algorithm configurable.&lt;/strong&gt; Introduce a verifier with an explicit policy like the one above, and run in &lt;code&gt;AcceptEither&lt;/code&gt; while collecting telemetry on which senders support ML-DSA.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your header limits.&lt;/strong&gt; Send a 4.4 kB signature header through every proxy, CDN and load balancer on the path before relying on it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Publish PQ keys when you send.&lt;/strong&gt; Offer ML-DSA public keys in an RFC 9964 JWKS alongside your classical keys, and run dual signatures during the transition.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tier your outbound protection.&lt;/strong&gt; Apply RPKI origin checks for observability, DNSSEC and DANE where customers publish TLSA records, and offer mutual TLS or signed payloads for high-value integrations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan the end state.&lt;/strong&gt; Move from &lt;code&gt;AcceptEither&lt;/code&gt; to &lt;code&gt;RequireBoth&lt;/code&gt;, then to &lt;code&gt;RequirePQ&lt;/code&gt; once all senders have migrated, and retire shared secrets.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;p&gt;HMAC-signed webhooks remain sound against quantum computers; the case for ML-DSA rests on asymmetric key management, non-repudiation and meeting 2030–2035 public-key deprecation timelines. Network-path attacks are real and documented, but RPKI alone only covers part of the problem. The strongest sender-side protection comes from binding the expected certificate key to the domain name with DNSSEC and DANE, with RPKI and multi-perspective certificate issuance as supporting layers. Build the verifier so the algorithm is a policy choice, and then migrate on your schedule rather than someone else’s.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;NIST FIPS 203, 204 and 205 (August 2024): &lt;a href="https://csrc.nist.gov/pubs/fips/204/final" rel="noopener noreferrer"&gt;https://csrc.nist.gov/pubs/fips/204/final&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NIST IR 8547, final report (October 27, 2026), summarized at &lt;a href="https://postquantum.com/security-pqc/nist-ir-8547-final/" rel="noopener noreferrer"&gt;https://postquantum.com/security-pqc/nist-ir-8547-final/&lt;/a&gt; and &lt;a href="https://labs.cloudsecurityalliance.org/research/csa-research-note-nist-ir8547-pqc-transition-20261030-csa-st/" rel="noopener noreferrer"&gt;https://labs.cloudsecurityalliance.org/research/csa-research-note-nist-ir8547-pqc-transition-20261030-csa-st/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 9964, ML-DSA for JOSE and COSE: &lt;a href="https://www.rfc-editor.org/rfc/rfc9964.html" rel="noopener noreferrer"&gt;https://www.rfc-editor.org/rfc/rfc9964.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Go &lt;code&gt;crypto/mldsa&lt;/code&gt; package: &lt;a href="https://pkg.go.dev/crypto/mldsa" rel="noopener noreferrer"&gt;https://pkg.go.dev/crypto/mldsa&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Webhook signature schemes for Stripe and GitHub: &lt;a href="https://blog.openreplay.com/verify-webhook-signature/" rel="noopener noreferrer"&gt;https://blog.openreplay.com/verify-webhook-signature/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 6698 (DANE) and RFC 7671 (DANE operational guidance): &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;2018 Route 53 / MyEtherWallet incident: &lt;a href="https://www.helpnetsecurity.com/2018/04/25/myetherwallet-dns-hijacking/" rel="noopener noreferrer"&gt;https://www.helpnetsecurity.com/2018/04/25/myetherwallet-dns-hijacking/&lt;/a&gt; and &lt;a href="https://www.theregister.com/2018/04/24/myetherwallet_dns_hijack/" rel="noopener noreferrer"&gt;https://www.theregister.com/2018/04/24/myetherwallet_dns_hijack/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Multi-Perspective Issuance Corroboration (Ballot SC-067): &lt;a href="https://www.securityweek.com/new-issuance-requirements-improve-https-certificate-validation/" rel="noopener noreferrer"&gt;https://www.securityweek.com/new-issuance-requirements-improve-https-certificate-validation/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Routinator HTTP API usage: &lt;a href="https://oneuptime.com/blog/post/2026-03-20-monitor-rpki-validation-ipv6/view" rel="noopener noreferrer"&gt;https://oneuptime.com/blog/post/2026-03-20-monitor-rpki-validation-ipv6/view&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/ingress-security-cryptographic-verification-enterprise-webhook-hardening-guide" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/ingress-security-cryptographic-verification-enterprise-webhook-hardening-guide&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>networking</category>
    </item>
    <item>
      <title>Architecting a Bring-Your-Own (BYO) Database Schema for Webhook Ingress: Balancing Customer Data Control and Metadata</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Thu, 05 Nov 2026 02:17:50 +0000</pubDate>
      <link>https://dev.to/instawebhook/architecting-a-bring-your-own-byo-database-schema-for-webhook-ingress-balancing-customer-data-4dd8</link>
      <guid>https://dev.to/instawebhook/architecting-a-bring-your-own-byo-database-schema-for-webhook-ingress-balancing-customer-data-4dd8</guid>
      <description>&lt;p&gt;Webhooks connect payment gateways, identity providers and SaaS platforms to the systems that act on their events. As enterprise adoption grows, two requirements pull against each other: &lt;strong&gt;data sovereignty&lt;/strong&gt; and &lt;strong&gt;operational orchestration&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Customers increasingly want sensitive payloads (PII, financial records, health data) to stay inside infrastructure they control. Webhook relays and automation platforms still need to coordinate delivery, track retries and report health. This article describes a &lt;strong&gt;Bring-Your-Own (BYO) database&lt;/strong&gt; pattern that handles both. Raw webhook bodies are persisted only in a customer-controlled PostgreSQL database. An orchestration layer sees operational metadata only.&lt;/p&gt;

&lt;p&gt;Along the way, it covers where the pattern’s guarantees stop. A pattern like this is easy to oversell, so the limits are stated plainly.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. The Architectural Dilemma: Sovereignty vs. Orchestration
&lt;/h2&gt;

&lt;p&gt;The conventional approach is a hosted webhook router. It receives provider requests, stores the bodies in a shared multi-tenant datastore, and runs processing and retries centrally. This is operationally simple, but it has costs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compliance scope:&lt;/strong&gt; If a vendor stores or processes regulated data on your behalf, it becomes part of your compliance footprint. HIPAA generally requires a business associate agreement with such a vendor, and GDPR requires a data processing agreement. PCI DSS applies to any system that stores, processes or transmits cardholder data. Keeping payloads out of a third party’s storage can shrink that footprint, but it does not remove your own obligations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trust:&lt;/strong&gt; Many security teams are reluctant to send raw event bodies to a multi-tenant system, or to share database credentials with one.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  The BYO Infrastructure Paradigm
&lt;/h3&gt;

&lt;p&gt;The pattern splits &lt;strong&gt;payload persistence&lt;/strong&gt; from &lt;strong&gt;job orchestration&lt;/strong&gt;:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Data plane (customer infrastructure):&lt;/strong&gt; A PostgreSQL database holds the raw event store, headers and business processing tables. Workers run here too.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Orchestration plane (relay or control plane):&lt;/strong&gt; It manages routing rules, delivery status, retry policy, alerting and dashboards. It works from metadata such as job IDs, status codes, timings and error classes, not payload contents.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;One constraint shapes the whole design. Providers sign the &lt;strong&gt;exact raw bytes&lt;/strong&gt; of the request body. Stripe, for example, signs the raw payload and sends the signature in a &lt;code&gt;Stripe-Signature&lt;/code&gt; header with a timestamp, and its libraries reject timestamps outside a default five-minute tolerance. Verification must therefore happen wherever the raw body is available. There are two ways to honor “the orchestrator never stores the payload”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Customer-side ingress:&lt;/strong&gt; The provider’s webhook points at an endpoint you host. It verifies the signature, writes the event to your database and reports metadata to the control plane.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pass-through relay:&lt;/strong&gt; A relay receives the request, verifies it, and forwards it to your ingress without persisting the body. The relay still sees the bytes in memory in transit, so this is a weaker guarantee than “never possesses the payload.” Describe it accurately to customers.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                  ┌───────────────────────────────┐
                  │   ORCHESTRATION PLANE         │
                  │  - Routing &amp;amp; retry policy     │
                  │  - Delivery status &amp;amp; metrics  │
                  │  - Alerting &amp;amp; dashboards      │
                  └──────────────▲────────────────┘
                                 │ metadata only
                                 │ (job_id, status, timings, error class)
┌──────────┐  signed webhook  ┌──┴────────────────────────────────┐
│ Provider │ ───────────────► │   CUSTOMER INFRASTRUCTURE         │
│ (Stripe, │                  │  - Ingress: verify signature      │
│  etc.)   │                  │  - PostgreSQL: events + jobs      │
└──────────┘                  │  - Workers: lease &amp;amp; process jobs  │
                              └───────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  2. Designing the Minimal-Privilege PostgreSQL Schema
&lt;/h2&gt;

&lt;p&gt;The schema below stores events and jobs in a dedicated schema. It uses &lt;code&gt;gen_random_uuid()&lt;/code&gt;, which is built into core PostgreSQL since version 13. The &lt;code&gt;uuid-ossp&lt;/code&gt; extension is therefore unnecessary for random UUIDs. &lt;code&gt;pgcrypto&lt;/code&gt; is only needed if you use its functions.&lt;/p&gt;

&lt;p&gt;Two design choices differ from a naive implementation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Raw body is stored as &lt;code&gt;bytea&lt;/code&gt;.&lt;/strong&gt; Signature verification needs the original bytes, and &lt;code&gt;JSONB&lt;/code&gt; does not preserve whitespace, key order or duplicate keys. Keep the original body for audit and replay. Add a &lt;code&gt;JSONB&lt;/code&gt; column if you want to query fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retries are scheduled in the row.&lt;/strong&gt; A &lt;code&gt;next_attempt_at&lt;/code&gt; column supports backoff, and a bounded &lt;code&gt;max_attempts&lt;/code&gt; stops infinite retry loops.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;IF&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;EXISTS&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- 1. Events: raw payloads live only in the customer's database&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;id&lt;/span&gt;                 &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;provider&lt;/span&gt;           &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;external_event_id&lt;/span&gt;  &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;255&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;event_type&lt;/span&gt;         &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;raw_body&lt;/span&gt;           &lt;span class="n"&gt;BYTEA&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;   &lt;span class="c1"&gt;-- exact bytes that were signed&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt;            &lt;span class="n"&gt;JSONB&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;                   &lt;span class="c1"&gt;-- parsed copy for querying&lt;/span&gt;
    &lt;span class="n"&gt;headers&lt;/span&gt;            &lt;span class="n"&gt;JSONB&lt;/span&gt;        &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;signature_verified&lt;/span&gt; &lt;span class="nb"&gt;BOOLEAN&lt;/span&gt;      &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&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;received_at&lt;/span&gt;        &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="k"&gt;CONSTRAINT&lt;/span&gt; &lt;span class="n"&gt;uk_provider_external_id&lt;/span&gt; &lt;span class="k"&gt;UNIQUE&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external_event_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_events_received_at&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;received_at&lt;/span&gt; &lt;span class="k"&gt;DESC&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;-- 2. Jobs: lease-based work queue&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;TABLE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&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;job_id&lt;/span&gt;           &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;PRIMARY&lt;/span&gt; &lt;span class="k"&gt;KEY&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;gen_random_uuid&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;event_id&lt;/span&gt;         &lt;span class="n"&gt;UUID&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;REFERENCES&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;DELETE&lt;/span&gt; &lt;span class="k"&gt;CASCADE&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;status&lt;/span&gt;           &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt;
                     &lt;span class="k"&gt;CHECK&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'leased'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'failed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s1"&gt;'dead_letter'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;

    &lt;span class="n"&gt;lease_holder_id&lt;/span&gt;  &lt;span class="nb"&gt;VARCHAR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;128&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
    &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next_attempt_at&lt;/span&gt;  &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;

    &lt;span class="n"&gt;attempt_count&lt;/span&gt;    &lt;span class="nb"&gt;INT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&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;max_attempts&lt;/span&gt;     &lt;span class="nb"&gt;INT&lt;/span&gt;  &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_error&lt;/span&gt;       &lt;span class="nb"&gt;TEXT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;

    &lt;span class="n"&gt;created_at&lt;/span&gt;       &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt;       &lt;span class="n"&gt;TIMESTAMPTZ&lt;/span&gt; &lt;span class="k"&gt;NOT&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt; &lt;span class="k"&gt;DEFAULT&lt;/span&gt; &lt;span class="n"&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;-- Partial index keeps the polling set small as completed jobs accumulate&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_jobs_claimable&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&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;next_attempt_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="k"&gt;IN&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'pending'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_jobs_event_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&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;event_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Enforcing Least Privilege with Roles
&lt;/h3&gt;

&lt;p&gt;Use separate roles for ingress and workers. PostgreSQL supports column-level &lt;code&gt;UPDATE&lt;/code&gt; grants, so workers can be limited to the lease-related columns.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress_writer&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;ROLE&lt;/span&gt; &lt;span class="n"&gt;webhook_worker&lt;/span&gt; &lt;span class="n"&gt;LOGIN&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;REVOKE&lt;/span&gt; &lt;span class="k"&gt;ALL&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt; &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="k"&gt;PUBLIC&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;USAGE&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="k"&gt;SCHEMA&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress_writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook_worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Ingress: insert events and enqueue jobs; no reads, updates or deletes&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress_writer&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Workers: read events, and update only lease and result columns on jobs&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;webhook_worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;   &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;webhook_worker&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;GRANT&lt;/span&gt; &lt;span class="k"&gt;UPDATE&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;lease_holder_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_expires_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next_attempt_at&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
              &lt;span class="n"&gt;attempt_count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;last_error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="k"&gt;TO&lt;/span&gt; &lt;span class="n"&gt;webhook_worker&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;TRUNCATE&lt;/code&gt;, &lt;code&gt;DELETE&lt;/code&gt;, &lt;code&gt;DROP&lt;/code&gt; and DDL are separate privileges that none of these grants include. A role that is not the table owner cannot alter the schema. Note that the ingress role needs &lt;code&gt;INSERT&lt;/code&gt; only. If you use &lt;code&gt;INSERT ... ON CONFLICT DO NOTHING&lt;/code&gt;, PostgreSQL also requires &lt;code&gt;SELECT&lt;/code&gt; on the columns in the conflict target, so grant column-level &lt;code&gt;SELECT (provider, external_event_id)&lt;/code&gt; as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Job Leasing with &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;
&lt;/h2&gt;

&lt;p&gt;Workers claim jobs directly from the customer database, with no separate broker such as Redis.&lt;/p&gt;

&lt;p&gt;PostgreSQL added &lt;code&gt;SKIP LOCKED&lt;/code&gt; in version 9.5. With it, rows that cannot be locked immediately are skipped instead of waited on. The PostgreSQL documentation warns that this yields an inconsistent view of the data, which makes it unsuitable for general-purpose queries. It is intended for queue-like tables, where each consumer should receive a different subset of available rows.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Claim up to 5 jobs: pending and due, or leased with an expired lease&lt;/span&gt;
&lt;span class="k"&gt;WITH&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="k"&gt;AS&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="k"&gt;SELECT&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt;
    &lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;
    &lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;attempt_count&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;
      &lt;span class="k"&gt;AND&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="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;next_attempt_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
         &lt;span class="k"&gt;OR&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt;  &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
      &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;ORDER&lt;/span&gt; &lt;span class="k"&gt;BY&lt;/span&gt; &lt;span class="n"&gt;next_attempt_at&lt;/span&gt;
    &lt;span class="k"&gt;LIMIT&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
    &lt;span class="k"&gt;FOR&lt;/span&gt; &lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;SKIP&lt;/span&gt; &lt;span class="n"&gt;LOCKED&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt;           &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lease_holder_id&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&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;-- worker identity&lt;/span&gt;
    &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;interval&lt;/span&gt; &lt;span class="s1"&gt;'5 minutes'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;attempt_count&lt;/span&gt;    &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempt_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;updated_at&lt;/span&gt;       &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;FROM&lt;/span&gt; &lt;span class="n"&gt;candidates&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;c&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;job_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;.&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;j&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;attempt_count&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 then reads the event body by &lt;code&gt;event_id&lt;/code&gt;. Keeping the claim and the payload read separate means a claim never copies large payloads through the queue path.&lt;/p&gt;

&lt;p&gt;Run the claim as a single statement, as above, or inside one transaction. If the row lock is taken in one transaction and the update happens in another, the lock is released at the first commit and another worker can claim the same row.&lt;/p&gt;

&lt;h3&gt;
  
  
  Completing, Failing and Dead-Lettering
&lt;/h3&gt;

&lt;p&gt;Completion must verify the lease. Otherwise a slow worker whose lease expired could overwrite the result of the worker that took over.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="c1"&gt;-- Success&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'completed'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_holder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lease_holder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="c1"&gt;-- Failure: retry with backoff, or dead-letter when attempts are exhausted&lt;/span&gt;
&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;CASE&lt;/span&gt; &lt;span class="k"&gt;WHEN&lt;/span&gt; &lt;span class="n"&gt;attempt_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt; &lt;span class="k"&gt;THEN&lt;/span&gt; &lt;span class="s1"&gt;'dead_letter'&lt;/span&gt; &lt;span class="k"&gt;ELSE&lt;/span&gt; &lt;span class="s1"&gt;'pending'&lt;/span&gt; &lt;span class="k"&gt;END&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;next_attempt_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&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;interval&lt;/span&gt; &lt;span class="s1"&gt;'30 seconds'&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;power&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="n"&gt;attempt_count&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt;
    &lt;span class="n"&gt;lease_holder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;NULL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;last_error&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;job_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lease_holder_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because the claim query skips jobs with &lt;code&gt;attempt_count &amp;gt;= max_attempts&lt;/code&gt;, a job whose worker crashed on its final attempt would stay &lt;code&gt;leased&lt;/code&gt; forever. Run a periodic reaper that moves such rows to &lt;code&gt;dead_letter&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;UPDATE&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;jobs&lt;/span&gt;
&lt;span class="k"&gt;SET&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'dead_letter'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;updated_at&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;WHERE&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s1"&gt;'leased'&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;lease_expires_at&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;AND&lt;/span&gt; &lt;span class="n"&gt;attempt_count&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;max_attempts&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  What &lt;code&gt;SKIP LOCKED&lt;/code&gt; Does and Doesn’t Give You
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Less lock waiting:&lt;/strong&gt; Workers move on to unlocked rows instead of blocking behind a competitor.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Crash recovery:&lt;/strong&gt; If a worker dies, its lease expires and another worker can reclaim the job.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;At-least-once delivery, not exactly-once:&lt;/strong&gt; A worker that is slow but alive can still be processing when its lease expires. Handlers must be idempotent. Set the lease longer than your worst-case processing time, or extend it with a heartbeat.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Throughput limits:&lt;/strong&gt; A Postgres-backed queue is a good fit for moderate volumes and for teams that already run Postgres. Very high volumes may call for table partitioning, aggressive vacuuming of the jobs table, or a dedicated broker. Benchmark with your own workload.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  4. Metadata Synchronization and State Reporting
&lt;/h2&gt;

&lt;p&gt;The orchestration layer still needs to know whether deliveries are succeeding. After each job finishes, the customer-side worker or an outbox publisher sends a small status record to the control plane over HTTPS:&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;"instance_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;"cust-cluster-us-east-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;"job_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;"a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"completed"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"attempt_count"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"duration_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;142&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"error_class"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"processed_at"&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-11-05T07:43:00Z"&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;p&gt;Keep the record minimal and make the contract explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Send:&lt;/strong&gt; job ID, status, attempt count, duration, a coarse error class and timestamps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Never send:&lt;/strong&gt; payload bodies, headers, customer identifiers inside error messages, or stack traces that might echo payload fields.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat some metadata as sensitive.&lt;/strong&gt; Fields like &lt;code&gt;provider&lt;/code&gt; and &lt;code&gt;event_type&lt;/code&gt; can reveal business activity. Under GDPR, an identifier that links back to a person can itself count as personal data. If that matters to the customer, use opaque job IDs and let them opt out of event-type reporting.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To keep this reporting reliable, write the status change and a pending-sync row in the same database transaction (a transactional outbox). A publisher then ships outbox rows to the control plane and retries on failure, so a network blip never loses a status update.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Provider Behavior Your Schema Must Absorb
&lt;/h2&gt;

&lt;p&gt;Your ingress design has to cope with how providers actually deliver. Using Stripe as an example:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Retries:&lt;/strong&gt; In live mode, Stripe retries failed deliveries with exponential backoff for up to three days. In test and sandbox mode, it makes three attempts over a few hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Duplicates and ordering:&lt;/strong&gt; The same event can arrive more than once, and events are not guaranteed to arrive in order. This is why &lt;code&gt;UNIQUE (provider, external_event_id)&lt;/code&gt; matters. Insert with &lt;code&gt;ON CONFLICT DO NOTHING&lt;/code&gt;, return &lt;code&gt;2xx&lt;/code&gt; for duplicates, and avoid assuming the latest-arriving event reflects the latest state.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge quickly:&lt;/strong&gt; Stripe’s exact timeout is not publicly documented, but handlers should acknowledge fast. Persist the event and enqueue the job, then return &lt;code&gt;2xx&lt;/code&gt; and do the heavy work asynchronously. This is the main reason for the events/jobs split.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;INSERT&lt;/span&gt; &lt;span class="k"&gt;INTO&lt;/span&gt; &lt;span class="n"&gt;webhook_ingress&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;events&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external_event_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event_type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;raw_body&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;headers&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;signature_verified&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;VALUES&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="err"&gt;$&lt;/span&gt;&lt;span class="mi"&gt;6&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;ON&lt;/span&gt; &lt;span class="n"&gt;CONFLICT&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;provider&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;external_event_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;DO&lt;/span&gt; &lt;span class="k"&gt;NOTHING&lt;/span&gt;
&lt;span class="n"&gt;RETURNING&lt;/span&gt; &lt;span class="n"&gt;id&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="c1"&gt;-- If a row is returned, insert the matching job in the same transaction.&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  6. Security Hardening
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Network isolation and transport security:&lt;/strong&gt; Place the database in private subnets. Require TLS on all connections, set &lt;code&gt;ssl_min_protocol_version&lt;/code&gt; to at least TLS 1.2 (TLS 1.3 where your clients support it), and have clients connect with &lt;code&gt;sslmode=verify-full&lt;/code&gt;. For service-to-service access, use client certificates (mTLS). Certificate pinning is rarely practical for database connections, so prefer a private CA with verification.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Encryption at rest:&lt;/strong&gt; Use volume or storage encryption from your cloud provider or filesystem layer as the baseline. For field-level protection, prefer &lt;strong&gt;application-side or envelope encryption&lt;/strong&gt; with keys from a KMS.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Be careful with &lt;code&gt;pgcrypto&lt;/code&gt; column encryption:&lt;/strong&gt; &lt;code&gt;pgp_sym_encrypt()&lt;/code&gt; returns &lt;code&gt;bytea&lt;/code&gt;, so it cannot be inserted into a &lt;code&gt;JSONB&lt;/code&gt; column. It needs a &lt;code&gt;bytea&lt;/code&gt; column. Also, pgcrypto runs inside the database server, so plaintext and keys reach the server, and a key written as a literal in SQL can end up in statement logs. Disable statement logging for those queries, or encrypt in the application before the data reaches Postgres.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Verify before you store as trusted:&lt;/strong&gt; Verify the provider’s signature on the raw body, with its timestamp tolerance, before setting &lt;code&gt;signature_verified = TRUE&lt;/code&gt;. Reject anything outside the tolerance window.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Retention and deletion:&lt;/strong&gt; Add a scheduled job to purge old events and completed jobs. Because &lt;code&gt;jobs.event_id&lt;/code&gt; cascades on delete, deleting expired events removes their jobs as well. Retention limits are also a common compliance requirement.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit access:&lt;/strong&gt; Log connections and role usage (for example with &lt;code&gt;log_connections&lt;/code&gt;, and the &lt;code&gt;pgaudit&lt;/code&gt; extension where available) so customers can see exactly what the orchestration layer touched.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Summary: Centralized vs. BYO Architecture
&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;Centralized webhook storage&lt;/th&gt;
&lt;th&gt;BYO database pattern&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Payload storage&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vendor-managed, multi-tenant&lt;/td&gt;
&lt;td&gt;Customer-controlled PostgreSQL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Compliance scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vendor holds regulated data; agreements such as a BAA or DPA are typically required&lt;/td&gt;
&lt;td&gt;Smaller vendor footprint; customer keeps full responsibility for the data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Job coordination&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vendor-operated queues or brokers&lt;/td&gt;
&lt;td&gt;Database-native leasing with &lt;code&gt;FOR UPDATE SKIP LOCKED&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Metadata visibility&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Full event visibility for the vendor&lt;/td&gt;
&lt;td&gt;Operational status only, by design&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Operational burden&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Low for the customer&lt;/td&gt;
&lt;td&gt;Higher: the customer runs the database, workers and backups&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Debugging&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Vendor can inspect payloads&lt;/td&gt;
&lt;td&gt;Needs customer-side tooling, since the vendor cannot see payloads&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The BYO pattern shifts risk and responsibility rather than eliminating them. Customers get stronger control over payloads and a smaller vendor footprint. In exchange, they run more infrastructure and must build idempotent handlers. Combining least-privilege roles, lease-verified job updates and a deliberately small metadata contract gives you reliable delivery orchestration without a third party storing the raw bodies.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;PostgreSQL documentation: &lt;code&gt;SELECT&lt;/code&gt; locking clause (&lt;code&gt;FOR UPDATE&lt;/code&gt;, &lt;code&gt;NOWAIT&lt;/code&gt;, &lt;code&gt;SKIP LOCKED&lt;/code&gt;), &lt;a href="https://www.postgresql.org/docs/current/sql-select.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/current/sql-select.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL documentation: UUID type and &lt;code&gt;gen_random_uuid()&lt;/code&gt; (core since PostgreSQL 13), &lt;a href="https://www.postgresql.org/docs/current/datatype-uuid.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/current/datatype-uuid.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL documentation: &lt;code&gt;pgcrypto&lt;/code&gt;, &lt;a href="https://www.postgresql.org/docs/current/pgcrypto.html" rel="noopener noreferrer"&gt;https://www.postgresql.org/docs/current/pgcrypto.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Stripe documentation: webhooks, signatures, retries, &lt;a href="https://docs.stripe.com/webhooks" rel="noopener noreferrer"&gt;https://docs.stripe.com/webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hookdeck, Guide to Stripe Webhooks: Features and Best Practices, &lt;a href="https://hookdeck.com/webhooks/platforms/guide-to-stripe-webhooks-features-and-best-practices" rel="noopener noreferrer"&gt;https://hookdeck.com/webhooks/platforms/guide-to-stripe-webhooks-features-and-best-practices&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/architecting-a-bring-your-own-byo-database-schema-for-webhook-ingress-balancing" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/architecting-a-bring-your-own-byo-database-schema-for-webhook-ingress-balancing&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>High-Throughput &amp; Post-Quantum Webhook Ingress Engineering</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Wed, 04 Nov 2026 02:18:53 +0000</pubDate>
      <link>https://dev.to/instawebhook/high-throughput-post-quantum-webhook-ingress-engineering-1foi</link>
      <guid>https://dev.to/instawebhook/high-throughput-post-quantum-webhook-ingress-engineering-1foi</guid>
      <description>&lt;p&gt;High-volume webhook ingestion sits where network performance, security enforcement and multi-tenant data architecture meet. An event gateway that handles very high request rates has to keep noisy neighbors from starving other tenants and resist low-and-slow denial-of-service attacks. It also has to plan for the post-quantum cryptography transition that regulators are now putting on a calendar.&lt;/p&gt;

&lt;p&gt;This article covers four pillars of a modern ingress design: post-quantum signature verification, early packet filtering with XDP and AF_XDP, tenant-aware sharding and queueing, and stateful defenses against slow-client attacks. Each section notes where the common “textbook” claims overreach, so you know what to benchmark in your own environment.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. Post-Quantum Webhook Signatures: ML-DSA and Hybrid Crypto-Agility
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Why the clock is ticking
&lt;/h3&gt;

&lt;p&gt;Classical public-key signatures (RSA, ECDSA over P-256 or secp256k1, Ed25519) rely on problems that Shor’s algorithm would solve on a sufficiently large quantum computer. No such machine exists today, but standards bodies are already setting retirement dates. NIST published the final version of &lt;strong&gt;NIST IR 8547&lt;/strong&gt; on October 27, 2026. It deprecates quantum-vulnerable public-key algorithms after 2030 and disallows them under NIST standards and guidelines after 2035. This now applies to all parameter strengths, not just the 112-bit ones the 2024 draft singled out.&lt;/p&gt;

&lt;p&gt;HMAC-SHA256, the most common webhook signature scheme, is symmetric and is not broken by Shor’s algorithm. Grover’s algorithm only reduces its effective security margin, and a 256-bit MAC stays comfortable. Its drawback is operational rather than quantum: the sender and receiver share a secret, so every receiver that can verify can also forge. Asymmetric signatures avoid that, which is why public-key schemes remain attractive for webhooks.&lt;/p&gt;

&lt;p&gt;NIST standardized &lt;strong&gt;ML-DSA&lt;/strong&gt; (Module-Lattice-Based Digital Signature Algorithm) in &lt;strong&gt;FIPS 204&lt;/strong&gt; in August 2024. It is the standardized form of CRYSTALS-Dilithium. NIST also published FIPS 205 (SLH-DSA, a stateless hash-based signature) as a second signature option, and FN-DSA is still under development.&lt;/p&gt;

&lt;h3&gt;
  
  
  Sizes and what they cost on the wire
&lt;/h3&gt;

&lt;p&gt;ML-DSA signatures and public keys are far larger than Ed25519’s. The sizes below come from FIPS 204. The base64 column is simple arithmetic and matters because HTTP headers carry text, not raw bytes.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Algorithm&lt;/th&gt;
&lt;th&gt;NIST security category&lt;/th&gt;
&lt;th&gt;Public key&lt;/th&gt;
&lt;th&gt;Signature&lt;/th&gt;
&lt;th&gt;Signature as base64&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Ed25519&lt;/strong&gt; (classical)&lt;/td&gt;
&lt;td&gt;n/a (about 128-bit classical)&lt;/td&gt;
&lt;td&gt;32 bytes&lt;/td&gt;
&lt;td&gt;64 bytes&lt;/td&gt;
&lt;td&gt;88 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-44&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Category 2&lt;/td&gt;
&lt;td&gt;1,312 bytes&lt;/td&gt;
&lt;td&gt;2,420 bytes&lt;/td&gt;
&lt;td&gt;3,228 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-65&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Category 3&lt;/td&gt;
&lt;td&gt;1,952 bytes&lt;/td&gt;
&lt;td&gt;3,309 bytes&lt;/td&gt;
&lt;td&gt;4,412 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;ML-DSA-87&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Category 5&lt;/td&gt;
&lt;td&gt;2,592 bytes&lt;/td&gt;
&lt;td&gt;4,627 bytes&lt;/td&gt;
&lt;td&gt;6,172 chars&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ML-DSA-65 is commonly recommended as the general-purpose default. ML-DSA-87 is the parameter set the NSA’s CNSA 2.0 requires for national security systems. Verification speed depends heavily on the implementation and hardware, so benchmark your own library rather than trusting generic per-signature timings.&lt;/p&gt;

&lt;h3&gt;
  
  
  A dual-header verification pipeline
&lt;/h3&gt;

&lt;p&gt;No widely adopted webhook specification defines post-quantum signature headers today, so the header names below are illustrative. The practical way to migrate without breaking existing senders is to let them send both a classical and a post-quantum signature over the same canonical message (typically timestamp, delivery ID and raw body, which also gives you replay protection).&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                   Incoming HTTP Request
                             │
            ┌────────────────┴────────────────┐
            ▼                                 ▼
   [X-Signature-Classical]           [X-Signature-PQC]
   (Ed25519 / HMAC-SHA256)           (ML-DSA-65)
            │                                 │
            ▼                                 ▼
   ┌─────────────────┐               ┌─────────────────┐
   │ Verify classical│               │   Verify PQC    │
   └────────┬────────┘               └────────┬────────┘
            │ (pass)                          │ (pass)
            └────────────────┬────────────────┘
                             ▼
                  Apply policy, forward upstream
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Dual headers are not the only way to do hybrids. The IETF LAMPS working group’s &lt;em&gt;Composite ML-DSA&lt;/em&gt; draft defines single combined signature algorithms that pair ML-DSA with RSA, ECDSA, Ed25519 or Ed448, aimed at X.509/PKIX use. When I checked its status, it had reached the RFC Editor queue. NIST’s transition guidance also accommodates dual and composite signatures as long as at least one component is NIST-approved.&lt;/p&gt;

&lt;h3&gt;
  
  
  Engineering considerations
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Header limits.&lt;/strong&gt; Many proxies cap header sizes, and ML-DSA signatures push against those caps.

&lt;ul&gt;
&lt;li&gt;NGINX’s default &lt;code&gt;large_client_header_buffers&lt;/code&gt; is &lt;code&gt;4 8k&lt;/code&gt;, and each header field must fit in one buffer. A base64 ML-DSA-87 signature (about 6.2 KB) fits, but a hex-encoded one (about 9.3 KB) does not. Raise the buffer size or use base64.&lt;/li&gt;
&lt;li&gt;Envoy’s default &lt;code&gt;max_request_headers_kb&lt;/code&gt; is 60 KiB, with a configurable maximum of 96 KiB. Oversized requests get a &lt;code&gt;431&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Every hop must agree. Upstream services with smaller limits will still reject large headers even if your edge accepts them.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Public key caching.&lt;/strong&gt; Keys of up to about 2.6 KB should be cached in process memory (for example in a concurrent map keyed by &lt;code&gt;kid&lt;/code&gt;) with a sensible TTL and an invalidation path for rotation. This avoids a remote key-set lookup on the verification hot path.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Policy modes.&lt;/strong&gt; Run the gateway in one of three modes per sender:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Migration:&lt;/strong&gt; accept if either signature verifies, and emit metrics on PQC adoption. Treat this strictly as a rollout aid. While it is on, an attacker who can break the classical scheme can still get through.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Strict hybrid:&lt;/strong&gt; both signatures must verify. This defends against a break in either algorithm.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PQC-only:&lt;/strong&gt; use this after a sender has retired the classical scheme.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prevent downgrade.&lt;/strong&gt; Once a sender has published a PQC key, require the PQC header for that sender. Otherwise an attacker can strip it and present only a classical signature.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  2. Early Packet Filtering: XDP and AF_XDP
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What XDP does and does not buy you
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;XDP (eXpress Data Path)&lt;/strong&gt; runs eBPF programs in the network driver’s receive path, before the kernel allocates an &lt;code&gt;sk_buff&lt;/code&gt;. A program can drop a packet (&lt;code&gt;XDP_DROP&lt;/code&gt;), hand it to the normal stack (&lt;code&gt;XDP_PASS&lt;/code&gt;) or redirect it (&lt;code&gt;XDP_REDIRECT&lt;/code&gt;). Cloudflare’s L4Drop system uses XDP for volumetric DDoS mitigation and has reported dropping more than 10 million packets per second on a single core. Typical iptables-style filtering is far slower per core.&lt;/p&gt;

&lt;p&gt;Two clarifications keep expectations realistic:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Filtering is where XDP shines.&lt;/strong&gt; Legitimate HTTPS webhook traffic at high request rates is usually bound by TLS, parsing and application logic rather than by per-packet kernel overhead. XDP’s biggest win is discarding junk before it consumes those resources.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AF_XDP delivers raw Ethernet frames to user space, not HTTP requests.&lt;/strong&gt; An AF_XDP socket bypasses the kernel stack, so there is no TCP or TLS processing. If you redirect port-443 traffic to an AF_XDP socket, your application must supply its own TCP and TLS implementation. Most gateways instead use XDP only for filtering and rate limiting, then &lt;code&gt;XDP_PASS&lt;/code&gt; surviving traffic to the normal kernel stack and a conventional proxy.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AF_XDP also depends on the driver. In &lt;code&gt;XDP_DRV&lt;/code&gt; mode with zero-copy support, frames land directly in user-space memory (the &lt;strong&gt;UMEM&lt;/strong&gt;). Otherwise the kernel falls back to a mode with an extra copy. A UMEM has a fill ring and a completion ring, and a socket has RX and TX rings. The application must keep the fill ring stocked with free frames, or no packets will arrive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;┌────────────────────────────────────────────────────────┐
│                       USER SPACE                       │
│  ┌──────────────────────────────────────────────┐      │
│  │  Gateway with its own TCP/TLS (AF_XDP socket)│      │
│  └──────────────────────▲───────────────────────┘      │
│                         │ RX / fill rings over UMEM    │
├─────────────────────────┼──────────────────────────────┤
│                      KERNEL SPACE                      │
│  ┌──────────────────────┴───────────────────────┐      │
│  │ XDP program in NIC driver (drop/pass/redirect)│─────┼─► XDP_PASS → normal
│  └──────────────────────▲───────────────────────┘      │   kernel TCP stack
├─────────────────────────┼──────────────────────────────┤
│                        HARDWARE                        │
│  ┌──────────────────────┴───────────────────────┐      │
│  │                 Network Card                 │      │
│  └──────────────────────────────────────────────┘      │
└────────────────────────────────────────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  XDP filter (C)
&lt;/h3&gt;

&lt;p&gt;This program validates Ethernet, IPv4 and TCP headers with bounds checks, drops obviously malformed TCP flag combinations, and redirects traffic for the gateway port to an AF_XDP socket bound to the receiving queue. Everything else goes to the normal stack, so DNS, QUIC, ICMP and other host traffic keep working.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="cp"&gt;#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/bpf.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/if_ether.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/in.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/ip.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;linux/tcp.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;bpf/bpf_helpers.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
#include&lt;/span&gt; &lt;span class="cpf"&gt;&amp;lt;bpf/bpf_endian.h&amp;gt;&lt;/span&gt;&lt;span class="cp"&gt;
&lt;/span&gt;
&lt;span class="cp"&gt;#define GATEWAY_PORT 443
&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;__uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;type&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BPF_MAP_TYPE_XSKMAP&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;__uint&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_entries&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;64&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;__type&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;__u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;__type&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="n"&gt;__u32&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="n"&gt;xsks_map&lt;/span&gt; &lt;span class="nf"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;".maps"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="n"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"xdp"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;int&lt;/span&gt; &lt;span class="nf"&gt;xdp_webhook_ingress_filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;xdp_md&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="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;     &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;long&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="kt"&gt;long&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;data_end&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;ethhdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;eth&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;data&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="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;eth&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data_end&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;XDP_DROP&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="n"&gt;eth&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;h_proto&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;bpf_htons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ETH_P_IP&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;XDP_PASS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="cm"&gt;/* non-IPv4 handled by the kernel */&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;iphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;iph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;eth&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="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;iph&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data_end&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;XDP_DROP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="n"&gt;__u32&lt;/span&gt; &lt;span class="n"&gt;ip_hlen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;iph&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;ihl&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;         &lt;span class="cm"&gt;/* honor IP options */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ip_hlen&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="k"&gt;sizeof&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;iph&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;XDP_DROP&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="n"&gt;iph&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;protocol&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;IPPROTO_TCP&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;XDP_PASS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;                  &lt;span class="cm"&gt;/* leave UDP/ICMP etc. alone */&lt;/span&gt;

    &lt;span class="k"&gt;struct&lt;/span&gt; &lt;span class="n"&gt;tcphdr&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tcph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="n"&gt;iph&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ip_hlen&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="kt"&gt;void&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)(&lt;/span&gt;&lt;span class="n"&gt;tcph&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="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;data_end&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;XDP_DROP&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="n"&gt;tcph&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;dest&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="n"&gt;bpf_htons&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;GATEWAY_PORT&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;XDP_PASS&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/* Illegal flag combination (SYN+FIN) */&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tcph&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;syn&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;tcph&lt;/span&gt;&lt;span class="o"&gt;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;fin&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;XDP_DROP&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="cm"&gt;/* Redirect to the AF_XDP socket for this RX queue; if none is
     * registered, fall back to the normal stack (flags = XDP_PASS). */&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;bpf_redirect_map&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;xsks_map&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;-&amp;gt;&lt;/span&gt;&lt;span class="n"&gt;rx_queue_index&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;XDP_PASS&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kt"&gt;char&lt;/span&gt; &lt;span class="n"&gt;_license&lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;SEC&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="s"&gt;"GPL"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you keep the kernel TCP stack, replace the final redirect with &lt;code&gt;return XDP_PASS;&lt;/code&gt; and use the program purely as a filter and rate limiter.&lt;/p&gt;

&lt;h3&gt;
  
  
  AF_XDP consumer (Rust sketch)
&lt;/h3&gt;

&lt;p&gt;The sketch below assumes bindgen-generated bindings to libxdp’s &lt;code&gt;xsk.h&lt;/code&gt;. Two details matter. First, what arrives is a raw frame, so you must walk the Ethernet, IP and TCP headers to reach any payload. Second, frames must be returned to the fill ring once you are done with them, and in zero-copy mode you cannot recycle a frame while a worker is still reading it.&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="n"&gt;slice&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;BATCH&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;64&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;XskSocket&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;umem_area&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;mut&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;ffi&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nb"&gt;c_void&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;rx_ring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xsk_ring_cons&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;fill_ring&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;xsk_ring_prod&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="cd"&gt;/// Returns the TCP payload of an Ethernet/IPv4/TCP frame, if well formed.&lt;/span&gt;
&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;tcp_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&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;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="o"&gt;&amp;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;frame&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&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;None&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="nn"&gt;u16&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_be_bytes&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;]])&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;0x0800&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;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="c1"&gt;// IPv4&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;ihl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mi"&gt;0x0f&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&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;ihl&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="mi"&gt;6&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;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;                        &lt;span class="c1"&gt;// TCP&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;ihl&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;frame&lt;/span&gt;&lt;span class="nf"&gt;.len&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;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;20&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;None&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;doff&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;4&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;doff&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="p"&gt;||&lt;/span&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="nf"&gt;.len&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;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;doff&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;None&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nf"&gt;Some&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;frame&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;tcp&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;doff&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="k"&gt;impl&lt;/span&gt; &lt;span class="n"&gt;XskSocket&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;process_rx&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="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;rx_idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&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;rcvd&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;xsk_ring_cons__peek&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;.rx_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;BATCH&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;rx_idx&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;rcvd&lt;/span&gt; &lt;span class="o"&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;return&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;fill_idx&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;u32&lt;/span&gt; &lt;span class="o"&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;reserved&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
            &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;xsk_ring_prod__reserve&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;.fill_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rcvd&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;fill_idx&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="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;rcvd&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;desc&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;*&lt;/span&gt;&lt;span class="nf"&gt;xsk_ring_cons__rx_desc&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;.rx_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rx_idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&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;frame&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="nn"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;from_raw_parts&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="nf"&gt;xsk_umem__get_data&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;.umem_area&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="py"&gt;.addr&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="k"&gt;const&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;desc&lt;/span&gt;&lt;span class="py"&gt;.len&lt;/span&gt; &lt;span class="k"&gt;as&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="p"&gt;};&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;Some&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="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;tcp_payload&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="c1"&gt;// Feed the segment to your user-space TCP/TLS stack here.&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;

            &lt;span class="c1"&gt;// Hand the frame back to the kernel (only once nothing still reads it).&lt;/span&gt;
            &lt;span class="k"&gt;if&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;reserved&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="o"&gt;*&lt;/span&gt;&lt;span class="nf"&gt;xsk_ring_prod__fill_addr&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;.fill_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fill_idx&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="py"&gt;.addr&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;unsafe&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="nf"&gt;xsk_ring_cons__release&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;.rx_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rcvd&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
            &lt;span class="nf"&gt;xsk_ring_prod__submit&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;.fill_ring&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;reserved&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;Production code must also handle partial reservations, retry and wake-up logic, and the TX and completion rings. Libraries such as libxdp or &lt;code&gt;xsk-rs&lt;/code&gt; handle much of that bookkeeping.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Tenant-Aware Sharding and Isolation
&lt;/h2&gt;

&lt;h3&gt;
  
  
  The noisy-neighbor problem
&lt;/h3&gt;

&lt;p&gt;In a shared ingestion pipeline, one tenant’s backfill can fill a common queue and delay everyone else’s events. This is &lt;strong&gt;head-of-line (HoL) blocking&lt;/strong&gt;. The fix is to isolate tenants so that a flood from one only fills that tenant’s own buffer.&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 Stream
                                      │
                                      ▼
                        ┌──────────────────────────┐
                        │  Tenant-aware Router     │
                        └─────────────┬────────────┘
           ┌──────────────────────────┼──────────────────────────┐
           ▼                          ▼                          ▼
┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│ Tenant A (dedicated)│    │ Tenant B (shared)   │    │ Shared pool queues  │
│ saturated           │    │ normal              │    │                     │
└──────────┬──────────┘    └──────────┬──────────┘    └──────────┬──────────┘
           ▼                          ▼                          ▼
┌─────────────────────┐    ┌─────────────────────┐    ┌─────────────────────┐
│ Worker group        │    │ Fair-share workers  │    │ Fair-share workers  │
└─────────────────────┘    └─────────────────────┘    └─────────────────────┘
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Consistent hashing with virtual nodes
&lt;/h3&gt;

&lt;p&gt;Consistent hashing maps tenants to queues so that adding or removing a queue only remaps a small fraction of tenants. Virtual nodes smooth out the distribution. Note that hashing alone does not solve the noisy-neighbor problem. A single hot tenant still hashes to a single queue, which is why the dedicated-queue mechanism below is needed.&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;BTreeMap&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;hash&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;Hash&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Hasher&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;collections&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nn"&gt;hash_map&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;DefaultHasher&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;ConsistentHashRing&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;BTreeMap&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nb"&gt;u64&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;String&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;impl&lt;/span&gt; &lt;span class="n"&gt;ConsistentHashRing&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="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;nodes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nn"&gt;BTreeMap&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;fn&lt;/span&gt; &lt;span class="nf"&gt;add_queue&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;queue_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;vnodes&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;for&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="o"&gt;..&lt;/span&gt;&lt;span class="n"&gt;vnodes&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;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nd"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{queue_id}-vnode-{i}"&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;.nodes&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;key&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;queue_id&lt;/span&gt;&lt;span class="nf"&gt;.to_string&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;fn&lt;/span&gt; &lt;span class="nf"&gt;get_queue&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="k"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;Option&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&amp;amp;&lt;/span&gt;&lt;span class="nb"&gt;String&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;if&lt;/span&gt; &lt;span class="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.is_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;return&lt;/span&gt; &lt;span class="nb"&gt;None&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;hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;Self&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="nf"&gt;hash_key&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="k"&gt;self&lt;/span&gt;&lt;span class="py"&gt;.nodes&lt;/span&gt;
            &lt;span class="nf"&gt;.range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hash&lt;/span&gt;&lt;span class="o"&gt;..&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;.map&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;q&lt;/span&gt;&lt;span class="p"&gt;)|&lt;/span&gt; &lt;span class="n"&gt;q&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="nf"&gt;.or_else&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;.nodes&lt;/span&gt;&lt;span class="nf"&gt;.values&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="c1"&gt;// wrap around the ring&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;// NOTE: DefaultHasher's algorithm is not guaranteed stable across Rust&lt;/span&gt;
    &lt;span class="c1"&gt;// releases. For a ring shared by several services or persisted routing&lt;/span&gt;
    &lt;span class="c1"&gt;// decisions, use a fixed, specified hash (e.g. xxHash or keyed SipHash).&lt;/span&gt;
    &lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;hash_key&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;val&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;u64&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;hasher&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;DefaultHasher&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="n"&gt;val&lt;/span&gt;&lt;span class="nf"&gt;.hash&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;hasher&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;hasher&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Dedicated queues, deficit round robin and backpressure
&lt;/h3&gt;

&lt;p&gt;These policies work with brokers such as Apache Kafka, RabbitMQ or NATS JetStream. The thresholds below are starting points to tune against your own traffic, not standards.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Dedicated queues for heavy tenants.&lt;/strong&gt; When a tenant’s share of ingress stays above a threshold (for example 15% of capacity over a moving window), move it to its own queue or partition set, with its own consumer group, so its backlog cannot delay others.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Deficit round robin (DRR) scheduling.&lt;/strong&gt; DRR, introduced by Shreedhar and Varghese in 1995, gives each queue a byte “quantum” per round and carries unused credit forward. Weighted variants give larger tenants bigger quanta. Workers move on to the next tenant when a tenant exhausts its credit, so no tenant monopolizes a worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-tenant backpressure.&lt;/strong&gt; When a tenant’s queue depth crosses a high-water mark (for example 85%), respond to that tenant’s requests with &lt;code&gt;429 Too Many Requests&lt;/code&gt; and a &lt;code&gt;Retry-After&lt;/code&gt; header, both defined in HTTP standards (RFC 6585 and RFC 9110). Other tenants are unaffected. Better still, keep the ingress path thin: validate, persist to a durable log, return &lt;code&gt;2xx&lt;/code&gt; quickly, and do the real work asynchronously, so backpressure is a last resort rather than the normal path.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  4. Stateful Defenses Against Slow-Client Attacks
&lt;/h2&gt;

&lt;p&gt;Webhook endpoints accept &lt;code&gt;POST&lt;/code&gt; requests from the open internet, which makes them targets for application-layer resource exhaustion.&lt;/p&gt;

&lt;h3&gt;
  
  
  Attack mechanics
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Slowloris-style attacks.&lt;/strong&gt; The client opens many connections and sends headers (or body bytes) extremely slowly, never finishing the request. Each connection holds a socket, a file descriptor and some memory until a timeout fires, so a modest number of connections can exhaust a server’s capacity.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Unbounded chunked bodies.&lt;/strong&gt; With &lt;code&gt;Transfer-Encoding: chunked&lt;/code&gt; there is no &lt;code&gt;Content-Length&lt;/code&gt; up front, so a client can keep streaming chunks. A proxy that buffers the body without a cumulative cap can run out of memory or disk.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;HTTP/2 brings related abuse patterns, such as the Rapid Reset attack disclosed in 2023 (CVE-2023-44487). Those need protocol-specific limits on concurrent streams and stream resets on top of what follows.&lt;/p&gt;

&lt;h3&gt;
  
  
  Decision model
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;                      Raw TCP Stream Connection
                                  │
                                  ▼
               ┌─────────────────────────────────────┐
               │   Connection tracking (per socket)  │
               └──────────────────┬──────────────────┘
                                  │
         ┌────────────────────────┴────────────────────────┐
         ▼                                                 ▼
 [Header deadline]                                 [Total body size]
 Headers complete within N seconds?                Cumulative bytes ≤ limit?
         │                                                 │
   no ──► 408 / close                               no ──► 413 / close
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Use the knobs your proxy already has
&lt;/h3&gt;

&lt;p&gt;Before writing custom code, set the built-in limits:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NGINX:&lt;/strong&gt; &lt;code&gt;client_header_timeout&lt;/code&gt; (default 60s) bounds the time to receive the full request header. &lt;code&gt;client_body_timeout&lt;/code&gt; (default 60s) applies between two successive reads of the body, not to the body as a whole, so a trickle can slip under it. &lt;code&gt;client_max_body_size&lt;/code&gt; (default 1m) is enforced for chunked bodies too and returns &lt;code&gt;413&lt;/code&gt;. Lowering the header and body timeouts to the single-digit to tens-of-seconds range is typical hardening.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Envoy:&lt;/strong&gt; the HTTP connection manager exposes &lt;code&gt;request_headers_timeout&lt;/code&gt;, &lt;code&gt;request_timeout&lt;/code&gt; and idle timeouts, along with &lt;code&gt;max_request_headers_kb&lt;/code&gt; for header size.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Minimum data rates:&lt;/strong&gt; NGINX has no direct minimum-rate directive. Apache’s &lt;code&gt;mod_reqtimeout&lt;/code&gt; supports minimum transfer rates, and a custom proxy can enforce the same idea.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Choose values from your real traffic. Legitimate webhook senders usually deliver small bodies in well under a second, so a header deadline of a few seconds and a tight body timeout are reasonable for many deployments. Senders on slow links, or with large payloads, may need more room.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: minimum-rate and size enforcement (Rust, Tokio)
&lt;/h3&gt;

&lt;p&gt;This sketch reads a request body while enforcing a per-read timeout, a minimum average rate over a rolling window, and a cumulative size cap. A real implementation would also parse HTTP framing (&lt;code&gt;Content-Length&lt;/code&gt; and chunked encoding) to know when the request is complete, and apply the rate check only while a request is still in flight.&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="n"&gt;io&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;io&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;AsyncReadExt&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;net&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TcpStream&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;time&lt;/span&gt;&lt;span class="p"&gt;::{&lt;/span&gt;&lt;span class="n"&gt;timeout&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="n"&gt;Instant&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;MAX_PAYLOAD_BYTES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;usize&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt; &lt;span class="o"&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;// 10 MB cumulative cap&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;MIN_RATE_BYTES_PER_SEC&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;=&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;// 1 KB/s average&lt;/span&gt;
&lt;span class="k"&gt;const&lt;/span&gt; &lt;span class="n"&gt;RATE_WINDOW&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="o"&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;5&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;PER_READ_TIMEOUT&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Duration&lt;/span&gt; &lt;span class="o"&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;5&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;read_body_guarded&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;stream&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;TcpStream&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="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;&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="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&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;new&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;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0u8&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="mi"&gt;8192&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;window_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&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;let&lt;/span&gt; &lt;span class="k"&gt;mut&lt;/span&gt; &lt;span class="n"&gt;window_bytes&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;=&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;loop&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;n&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;timeout&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PER_READ_TIMEOUT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;stream&lt;/span&gt;&lt;span class="nf"&gt;.read&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;buf&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="nf"&gt;Ok&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="mi"&gt;0&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;return&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;body&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;               &lt;span class="c1"&gt;// peer closed&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;Ok&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;=&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;n&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;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&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;return&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;e&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="p"&gt;{&lt;/span&gt;
                &lt;span class="k"&gt;return&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;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;new&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;ErrorKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TimedOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"read timeout"&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;if&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="nf"&gt;.len&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;MAX_PAYLOAD_BYTES&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;Err&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;new&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;ErrorKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;InvalidData&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="p"&gt;}&lt;/span&gt;
        &lt;span class="n"&gt;body&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="p"&gt;[&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;window_bytes&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="n"&gt;n&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="c1"&gt;// Check the average rate once per full window, not per read.&lt;/span&gt;
        &lt;span class="k"&gt;let&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;window_start&lt;/span&gt;&lt;span class="nf"&gt;.elapsed&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;elapsed&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;=&lt;/span&gt; &lt;span class="n"&gt;RATE_WINDOW&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;window_bytes&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;MIN_RATE_BYTES_PER_SEC&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;elapsed&lt;/span&gt;&lt;span class="nf"&gt;.as_secs&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="nf"&gt;Err&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;new&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;ErrorKind&lt;/span&gt;&lt;span class="p"&gt;::&lt;/span&gt;&lt;span class="n"&gt;TimedOut&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"client too slow"&lt;/span&gt;&lt;span class="p"&gt;));&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
            &lt;span class="n"&gt;window_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nn"&gt;Instant&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;window_bytes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;0&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;Judging each read against a fixed size, as simpler implementations do, would kill legitimate small payloads. Measuring the average over a window avoids that.&lt;/p&gt;

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

&lt;p&gt;A resilient high-throughput webhook ingress needs defenses at every layer, and each layer has a realistic scope:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Protocol layer:&lt;/strong&gt; Plan for ML-DSA (FIPS 204) alongside your existing signatures, using dual headers or composite signatures. Enforce strict verification and downgrade protection once senders migrate. NIST’s final IR 8547 sets 2030 and 2035 as the deprecation and disallowance dates for quantum-vulnerable public-key algorithms.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network layer:&lt;/strong&gt; Use XDP to drop junk and rate-limit before it reaches the kernel stack, where published results show on the order of 10 million dropped packets per second per core. Reserve AF_XDP for cases where you are prepared to bring your own TCP and TLS stack.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Routing layer:&lt;/strong&gt; Combine consistent hashing, dedicated queues for heavy tenants, deficit round robin scheduling and per-tenant backpressure so one sender cannot block the rest.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Transport layer:&lt;/strong&gt; Set strict header and body timeouts, size caps and minimum-rate checks, using your proxy’s native controls first and custom code only where gaps remain.&lt;/li&gt;
&lt;/ol&gt;

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

&lt;ul&gt;
&lt;li&gt;NIST FIPS 204, &lt;em&gt;Module-Lattice-Based Digital Signature Standard&lt;/em&gt;: &lt;a href="https://csrc.nist.gov/pubs/fips/204/final" rel="noopener noreferrer"&gt;https://csrc.nist.gov/pubs/fips/204/final&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Encryption Consulting, &lt;em&gt;What Is ML-DSA (FIPS 204)?&lt;/em&gt;: &lt;a href="https://www.encryptionconsulting.com/education-center/ml-dsa-fips-204/" rel="noopener noreferrer"&gt;https://www.encryptionconsulting.com/education-center/ml-dsa-fips-204/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Help Net Security, NIST IR 8547 roadmap coverage: &lt;a href="https://www.helpnetsecurity.com/2026/10/28/nist-post-quantum-cryptography-migration-roadmap/" rel="noopener noreferrer"&gt;https://www.helpnetsecurity.com/2026/10/28/nist-post-quantum-cryptography-migration-roadmap/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PostQuantum.com, &lt;em&gt;NIST IR 8547 final&lt;/em&gt;: &lt;a href="https://postquantum.com/security-pqc/nist-ir-8547-final/" rel="noopener noreferrer"&gt;https://postquantum.com/security-pqc/nist-ir-8547-final/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;IETF LAMPS, &lt;em&gt;Composite ML-DSA for use in X.509 PKI&lt;/em&gt;: &lt;a href="https://datatracker.ietf.org/doc/draft-ietf-lamps-pq-composite-sigs/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/draft-ietf-lamps-pq-composite-sigs/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Linux kernel documentation, &lt;em&gt;AF_XDP&lt;/em&gt;: &lt;a href="https://docs.kernel.org/networking/af_xdp.html" rel="noopener noreferrer"&gt;https://docs.kernel.org/networking/af_xdp.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Cloudflare, &lt;em&gt;L4Drop: XDP DDoS Mitigations&lt;/em&gt;: &lt;a href="https://blog.cloudflare.com/l4drop-xdp-ebpf-based-ddos-mitigations/" rel="noopener noreferrer"&gt;https://blog.cloudflare.com/l4drop-xdp-ebpf-based-ddos-mitigations/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;NGINX, &lt;code&gt;ngx_http_core_module&lt;/code&gt; documentation: &lt;a href="https://nginx.org/en/docs/http/ngx_http_core_module.html" rel="noopener noreferrer"&gt;https://nginx.org/en/docs/http/ngx_http_core_module.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Envoy, HTTP connection manager configuration: &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;RFC 6585 (HTTP 429): &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;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;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published at &lt;a href="https://instawebhook.com/blog/high-throughput-post-quantum-webhook-ingress-engineering" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/high-throughput-post-quantum-webhook-ingress-engineering&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>devops</category>
    </item>
    <item>
      <title>Building an In-Memory Circuit Breaker for Webhook Ingress: Shedding Load Without Losing Events</title>
      <dc:creator>InstaWebhook</dc:creator>
      <pubDate>Tue, 03 Nov 2026 02:10:44 +0000</pubDate>
      <link>https://dev.to/instawebhook/building-an-in-memory-circuit-breaker-for-webhook-ingress-shedding-load-without-losing-events-33dm</link>
      <guid>https://dev.to/instawebhook/building-an-in-memory-circuit-breaker-for-webhook-ingress-shedding-load-without-losing-events-33dm</guid>
      <description>&lt;p&gt;Webhooks connect cloud systems. Payment providers like Stripe, code hosts like GitHub and commerce platforms like Shopify all use them to tell your services that something changed. When a sender has a bad day, you pay for it. A bulk export, a backfill or a burst of retries can hit your ingress endpoint, and the endpoint is the first thing that fails.&lt;/p&gt;

&lt;p&gt;When a burst arrives, worker threads or goroutines block on slow downstream calls and connection pools saturate. Memory fills with queued request bodies and garbage-collection pressure pushes latency higher. Health checks start failing, the load balancer moves traffic to the remaining nodes, and those nodes tip over too.&lt;/p&gt;

&lt;p&gt;This guide describes an &lt;strong&gt;in-memory admission controller&lt;/strong&gt; for webhook ingress. It combines an adaptive concurrency limit, a CoDel-style delay signal and priority-aware shedding. It also covers what “not losing events” means when you reject a request, because that depends on how each sender retries.&lt;/p&gt;

&lt;h2&gt;
  
  
  1. How an ingress meltdown unfolds
&lt;/h2&gt;

&lt;p&gt;Webhook senders enforce short response deadlines, and each provider sets its own:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sender&lt;/th&gt;
&lt;th&gt;Documented behavior&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;Expects a 2XX within 10 seconds, otherwise it terminates the connection and counts a failed delivery. It does not redeliver automatically; 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;One-second connection timeout and five-second timeout for the whole request. Failed deliveries are retried 8 times over 4 hours.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Slack (Events API)&lt;/td&gt;
&lt;td&gt;Expects a valid response within three seconds.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stripe (live mode)&lt;/td&gt;
&lt;td&gt;Retries for up to 3 days with exponential backoff.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Because the deadlines are short, a slow endpoint produces timeouts, and timeouts produce more traffic. A sender that retries adds those retries to the stream of fresh events. A sender that doesn’t retry, like GitHub, leaves you with lost events and manual redelivery.&lt;/p&gt;

&lt;p&gt;The failure then plays out in a predictable order:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Connection saturation:&lt;/strong&gt; new connections queue in the socket backlog.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Worker starvation:&lt;/strong&gt; handlers blocked on database calls hold their threads or goroutines.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory growth:&lt;/strong&gt; request bodies pile up while waiting for a worker.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Latency amplification:&lt;/strong&gt; garbage-collection work and contention raise latency, which causes more sender timeouts.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[Upstream Sender] ──(flood / retries)──&amp;gt; [Load Balancer]
                                                │
                                                ▼
                                  [Ingress Worker Node]
                                    ├── Connection queue (full)
                                    ├── Workers (blocked)
                                    └── Memory / GC (pressure) ──&amp;gt; [meltdown]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first defense is architectural. GitHub’s documentation recommends putting a queue in front of slow work so the server can respond immediately and process the payload in the background. Shopify gives the same advice for bursts. Admission control is the second line of defense. It protects the thin “validate, enqueue, acknowledge” path itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. Why the admission decision should be local
&lt;/h2&gt;

&lt;p&gt;Resilience4j’s circuit breaker is the standard tool for &lt;em&gt;outbound&lt;/em&gt; calls. It is an in-process library and doesn’t need Redis or any other shared store. The same reasoning applies at ingress:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;No extra dependency on the hot path.&lt;/strong&gt; Consulting a shared store for every inbound request adds a network round trip and a new failure mode. If that store is down, your ingress should still be able to shed load.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The overload is local.&lt;/strong&gt; Thread pools, memory and database connections are per-node resources. A node-local signal reflects what that node can actually handle.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Senders only see HTTP.&lt;/strong&gt; They react to status codes and timing, so the cleanest signal you can give them is a fast, explicit rejection.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The trade-off is that each node decides independently. With uneven load balancing, one node may shed while another has spare capacity. Per-node metrics (section 7) show you when that happens.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. Two mechanisms: adaptive concurrency and queue delay
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Adaptive concurrency limits
&lt;/h3&gt;

&lt;p&gt;Little’s Law says that concurrency equals throughput times latency. A fixed limit such as “100 concurrent requests” goes stale as soon as latency changes. If the database slows down, 100 is too high. If it speeds up, 100 is too low.&lt;/p&gt;

&lt;p&gt;Netflix’s open-source &lt;a href="https://github.com/Netflix/concurrency-limits" rel="noopener noreferrer"&gt;concurrency-limits&lt;/a&gt; library, described in their 2018 post “Performance Under Load”, adjusts the limit from observed latency instead. Its gradient algorithm uses:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;newLimit = gradient × currentLimit + queueSize
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the library’s Gradient2 variant, the gradient is the long-term RTT divided by the current RTT, clamped to the range 0.5 to 1.0. The result is then smoothed (default factor 0.2):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;gradient = max(0.5, min(1.0, longTermRTT / currentRTT))
newLimit = gradient × currentLimit + queueSize
newLimit = currentLimit × (1 − smoothing) + newLimit × smoothing
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When latency rises above its long-term average, the gradient drops below 1 and the limit shrinks. When latency is stable, the &lt;code&gt;queueSize&lt;/code&gt; term lets the limit creep upward. It can be a fixed value or a function of the limit, such as a square root.&lt;/p&gt;

&lt;h3&gt;
  
  
  Queue delay (CoDel-style)
&lt;/h3&gt;

&lt;p&gt;Counting 5xx errors reacts late, because by the time errors appear the damage is done. A better early signal is how long requests wait for a slot.&lt;/p&gt;

&lt;p&gt;CoDel (Controlled Delay) was designed by Kathleen Nichols and Van Jacobson as an active queue management algorithm for network buffers (RFC 8289). It tracks a packet’s &lt;em&gt;sojourn time&lt;/em&gt;, the time spent in the queue. It acts only when the &lt;em&gt;minimum&lt;/em&gt; delay over a sliding window stays above a target, which allows short bursts but prevents a persistent standing queue. RFC 8289’s defaults are a 5 ms target and a 100 ms interval.&lt;/p&gt;

&lt;p&gt;Facebook adapted a variant of CoDel for request queues, as described in “Fail at Scale”. Its open-source Wangle library shows the idea. Within each interval it tracks the minimum queueing delay and declares overload if that minimum exceeded the target. While overloaded, it sheds requests that have waited longer than twice the target. This is an adaptation rather than textbook CoDel. Treat 5 ms and 100 ms as starting points and tune them against your handler’s normal service time.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. Architecture
&lt;/h2&gt;

&lt;p&gt;Requests pass through classification and then a limiter that both enforces the adaptive limit and measures queue delay:&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 request
       │
       ▼
 ┌──────────────────────────────────────────┐
 │ 1. Priority classifier                   │
 │    (route or verified partner identity)  │
 └──────────────────────────────────────────┘
       │
       ▼
 ┌──────────────────────────────────────────┐
 │ 2. Admission: slot free?                 │
 │    yes → admit; no → queue briefly, or   │
 │    shed if overloaded (by priority)      │
 └──────────────────────────────────────────┘
       │ (slot acquired)
       ▼
 ┌──────────────────────────────────────────┐
 │ 3. Thin handler: verify signature,       │
 │    enqueue durably, return 2xx           │
 └──────────────────────────────────────────┘
       │
       ▼
 Release slot → feed RTT back to the limit
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Classify by something you control, such as the endpoint path or the partner identified after signature verification. Don’t trust a priority header that arrives from the public internet, because anyone can set it.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. A working Go sketch
&lt;/h2&gt;

&lt;p&gt;The following sketch uses only the standard library. It combines a Gradient2-style limit with a Wangle-style overload flag. The parameter values are illustrative and need tuning against your own traffic.&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;"math"&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;"time"&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;Priority&lt;/span&gt; &lt;span class="kt"&gt;int&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;PriorityLow&lt;/span&gt; &lt;span class="n"&gt;Priority&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;iota&lt;/span&gt;
    &lt;span class="n"&gt;PriorityDefault&lt;/span&gt;
    &lt;span class="n"&gt;PriorityHigh&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;Config&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;InitialLimit&lt;/span&gt; &lt;span class="kt"&gt;float64&lt;/span&gt;       &lt;span class="c"&gt;// e.g. 20&lt;/span&gt;
    &lt;span class="n"&gt;MinLimit&lt;/span&gt;     &lt;span class="kt"&gt;float64&lt;/span&gt;       &lt;span class="c"&gt;// e.g. 4&lt;/span&gt;
    &lt;span class="n"&gt;MaxLimit&lt;/span&gt;     &lt;span class="kt"&gt;float64&lt;/span&gt;       &lt;span class="c"&gt;// e.g. 200&lt;/span&gt;
    &lt;span class="n"&gt;Smoothing&lt;/span&gt;    &lt;span class="kt"&gt;float64&lt;/span&gt;       &lt;span class="c"&gt;// e.g. 0.2&lt;/span&gt;
    &lt;span class="n"&gt;TargetDelay&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;Duration&lt;/span&gt; &lt;span class="c"&gt;// e.g. 5ms&lt;/span&gt;
    &lt;span class="n"&gt;Interval&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;Duration&lt;/span&gt; &lt;span class="c"&gt;// e.g. 100ms&lt;/span&gt;
    &lt;span class="n"&gt;MaxWait&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;Duration&lt;/span&gt; &lt;span class="c"&gt;// longest a request may wait for a slot, e.g. 2 * TargetDelay&lt;/span&gt;
    &lt;span class="n"&gt;MaxQueue&lt;/span&gt;     &lt;span class="kt"&gt;int&lt;/span&gt;           &lt;span class="c"&gt;// hard cap on waiting requests&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;waiter&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;ready&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="n"&gt;granted&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;type&lt;/span&gt; &lt;span class="n"&gt;Limiter&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;cfg&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;

    &lt;span class="n"&gt;mu&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;Mutex&lt;/span&gt;
    &lt;span class="n"&gt;limit&lt;/span&gt;       &lt;span class="kt"&gt;float64&lt;/span&gt;
    &lt;span class="n"&gt;inflight&lt;/span&gt;    &lt;span class="kt"&gt;int&lt;/span&gt;
    &lt;span class="n"&gt;waiters&lt;/span&gt;     &lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;waiter&lt;/span&gt;
    &lt;span class="n"&gt;shortRTT&lt;/span&gt;    &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="c"&gt;// seconds, fast EWMA&lt;/span&gt;
    &lt;span class="n"&gt;longRTT&lt;/span&gt;     &lt;span class="kt"&gt;float64&lt;/span&gt; &lt;span class="c"&gt;// seconds, slow EWMA&lt;/span&gt;
    &lt;span class="n"&gt;intervalEnd&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;Time&lt;/span&gt;
    &lt;span class="n"&gt;minDelay&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;Duration&lt;/span&gt;
    &lt;span class="n"&gt;overloaded&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;func&lt;/span&gt; &lt;span class="n"&gt;NewLimiter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt; &lt;span class="n"&gt;Config&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&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;Limiter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;InitialLimit&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Acquire returns a release func and true if the request is admitted.&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;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;Priority&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Duration&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="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inflight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inflight&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observeDelay&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&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;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shouldShed&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="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&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="no"&gt;false&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="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;waiter&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;ready&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="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="n"&gt;start&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;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;timer&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;NewTimer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxWait&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;timer&lt;/span&gt;&lt;span class="o"&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;timedOut&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="no"&gt;false&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;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ready&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;timer&lt;/span&gt;&lt;span class="o"&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;timedOut&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;observeDelay&lt;/span&gt;&lt;span class="p"&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;Since&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;timedOut&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&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;granted&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;removeWaiter&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="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="no"&gt;false&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;release&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;span class="k"&gt;func&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;shouldShed&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;p&lt;/span&gt; &lt;span class="n"&gt;Priority&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;switch&lt;/span&gt; &lt;span class="n"&gt;p&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;PriorityHigh&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxQueue&lt;/span&gt; &lt;span class="c"&gt;// only when the queue is full&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="n"&gt;PriorityDefault&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overloaded&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxQueue&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;// PriorityLow: no queueing at all once slots are exhausted&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="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// observeDelay tracks the minimum queueing delay per interval (CoDel-style).&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;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;observeDelay&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&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;Duration&lt;/span&gt;&lt;span class="p"&gt;)&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;time&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Now&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;now&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intervalEnd&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;overloaded&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minDelay&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TargetDelay&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;intervalEnd&lt;/span&gt; &lt;span class="o"&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;Add&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Interval&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="n"&gt;d&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minDelay&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;minDelay&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;d&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;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;release&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtt&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;Duration&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Lock&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Unlock&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inflight&lt;/span&gt;&lt;span class="o"&gt;--&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;updateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Seconds&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;for&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inflight&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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="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;granted&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;true&lt;/span&gt;
        &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inflight&lt;/span&gt;&lt;span class="o"&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;w&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ready&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;// updateLimit is a simplified Gradient2-style update.&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;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;updateLimit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtt&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;rtt&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;=&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="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longRTT&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longRTT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shortRTT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;rtt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rtt&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shortRTT&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;0.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;rtt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shortRTT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longRTT&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="m"&gt;0.01&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rtt&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longRTT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="n"&gt;gradient&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;Max&lt;/span&gt;&lt;span class="p"&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="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="m"&gt;1.0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;longRTT&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shortRTT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;:=&lt;/span&gt; &lt;span class="n"&gt;gradient&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&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;Sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;next&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Smoothing&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Smoothing&lt;/span&gt;
    &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;limit&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;Max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MinLimit&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;Min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cfg&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;MaxLimit&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&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;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="n"&gt;removeWaiter&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;waiter&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="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;x&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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;==&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;append&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;waiters&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="m"&gt;1&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="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="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;// Guard wraps a webhook handler with admission control.&lt;/span&gt;
&lt;span class="k"&gt;func&lt;/span&gt; &lt;span class="n"&gt;Guard&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;l&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;Limiter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;classify&lt;/span&gt; &lt;span class="k"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&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="n"&gt;Priority&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;next&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;Handler&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;Handler&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;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;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;release&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;l&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Acquire&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;classify&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;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="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;"10"&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;"service temporarily overloaded"&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;start&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;Now&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
        &lt;span class="k"&gt;defer&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;release&lt;/span&gt;&lt;span class="p"&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;Since&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="p"&gt;}()&lt;/span&gt;
        &lt;span class="n"&gt;next&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ServeHTTP&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="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;Some limits of this sketch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Waiters are served in FIFO order. Facebook’s design switches to LIFO once a queue forms, so the newest requests, which are most likely to still meet their sender’s deadline, go first.&lt;/li&gt;
&lt;li&gt;RTT is measured for every request. In production you would typically exclude requests that failed fast.&lt;/li&gt;
&lt;li&gt;A single mutex is fine at moderate rates. Profile before optimizing it.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  6. Rejecting requests without losing events
&lt;/h2&gt;

&lt;p&gt;Whether a rejected webhook is lost depends on the sender, so you need to know how each one behaves:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Senders with retries:&lt;/strong&gt; Stripe retries with exponential backoff for up to 3 days in live mode. Shopify retries 8 times over 4 hours.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Senders without automatic retries:&lt;/strong&gt; GitHub does not redeliver automatically. If you shed a GitHub delivery, you need to redeliver it manually or through the API once you recover.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subscription risk:&lt;/strong&gt; Shopify deletes a webhook subscription created through the Admin API after 8 consecutive failures, and Stripe emails you and eventually disables an endpoint that fails for multiple days. A long shedding episode can cost you the subscription itself.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For these reasons, shedding should be short, targeted and backed by a reconciliation job. Shopify itself recommends building one if you can’t afford to miss webhooks. The job periodically fetches data you may have missed through the provider’s API.&lt;/p&gt;

&lt;h3&gt;
  
  
  Status codes
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;503 Service Unavailable&lt;/code&gt; with &lt;code&gt;Retry-After&lt;/code&gt;:&lt;/strong&gt; the standard way to signal temporary overload. RFC 9110 defines &lt;code&gt;Retry-After&lt;/code&gt; as either a number of seconds or an HTTP date.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;429 Too Many Requests&lt;/code&gt;:&lt;/strong&gt; RFC 6585 defines it for rate violations by a particular sender. It also accepts &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Don’t just drop connections.&lt;/strong&gt; An abrupt reset gives the sender no information, and it will treat the delivery as a failed attempt. A clean 503 tells it explicitly that the failure is temporary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do senders actually honor &lt;code&gt;Retry-After&lt;/code&gt;? Some do. Webhook infrastructure vendors such as Hookdeck and Kit document that they respect &lt;code&gt;Retry-After&lt;/code&gt; on 429 and 503, and AWS EventBridge API destinations are documented to as well. Svix advises that good senders will respect it when you return a 429. Stripe’s public documentation describes exponential backoff but doesn’t mention &lt;code&gt;Retry-After&lt;/code&gt;, so check its behavior in your delivery logs rather than assuming. Because support varies, treat the header as a hint and rely on the sender’s own backoff as the fallback.&lt;/p&gt;

&lt;p&gt;Add jitter to the values you return, for example 5 to 15 seconds instead of a constant 10. Otherwise senders that honor the header will retry in lockstep.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. Priority-based shedding
&lt;/h2&gt;

&lt;p&gt;Not all webhooks are equally urgent. A payment confirmation matters more than a bulk analytics sync. Classify by endpoint or authenticated partner, then apply different behavior per tier. The sketch above implements this policy:&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;Examples&lt;/th&gt;
&lt;th&gt;Behavior in the sketch&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PriorityHigh&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Payment confirmations, security alerts&lt;/td&gt;
&lt;td&gt;Queue for a slot; shed only when the wait queue is full.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PriorityDefault&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Standard operational updates&lt;/td&gt;
&lt;td&gt;Queue, but shed once the CoDel-style overload flag is set.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PriorityLow&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Bulk syncs, analytics, non-essential notifications&lt;/td&gt;
&lt;td&gt;Shed immediately when no slot is free.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;This is a policy choice rather than a standard. Choose tiers based on which events you can safely replay later.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. Observability
&lt;/h2&gt;

&lt;p&gt;Since each node decides for itself, you need per-node metrics. Export them to Prometheus or OpenTelemetry:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;webhook_ingress_concurrency_limit&lt;/code&gt;: gauge for the current adaptive limit.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook_ingress_inflight_requests&lt;/code&gt;: gauge for requests being processed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook_ingress_shed_total{priority}&lt;/code&gt;: counter of rejected requests.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook_ingress_queue_delay_seconds&lt;/code&gt;: histogram of time spent waiting for a slot. Use millisecond-scale buckets, since the targets are in the single-digit-millisecond range.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;webhook_ingress_overloaded&lt;/code&gt;: gauge (0 or 1) for the overload flag.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Alert on a sustained rise in shed counts or on queue delay staying above target. Both mean you should look at downstream capacity, not just the limiter.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. Checklist
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Acknowledge fast:&lt;/strong&gt; Verify the signature, enqueue durably and return 2xx. Do the real work asynchronously.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Keep admission state local:&lt;/strong&gt; Avoid a shared-store call on every inbound request.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use a latency-driven limit:&lt;/strong&gt; An adaptive, gradient-style limit beats a fixed number.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Watch queue delay, not just errors:&lt;/strong&gt; Use a CoDel-style minimum-delay signal, and tune the target to your handler.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reject explicitly:&lt;/strong&gt; Return 503 (or 429 for per-sender limits) with a jittered &lt;code&gt;Retry-After&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Know your senders:&lt;/strong&gt; Document each provider’s timeout, retry window and subscription-deletion rules.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Reconcile:&lt;/strong&gt; Run a job that backfills events missed during shedding, and redeliver GitHub deliveries manually.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Classify safely:&lt;/strong&gt; Derive priority from routes or verified identity, never from unauthenticated headers.&lt;/li&gt;
&lt;/ul&gt;

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

&lt;ul&gt;
&lt;li&gt;Stripe webhooks documentation: &lt;a href="https://docs.stripe.com/webhooks" rel="noopener noreferrer"&gt;https://docs.stripe.com/webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub, “Best practices for using webhooks”: &lt;a href="https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks" rel="noopener noreferrer"&gt;https://docs.github.com/webhooks/using-webhooks/best-practices-for-using-webhooks&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Shopify, “Deliver webhooks through HTTPS”: &lt;a href="https://shopify.dev/docs/apps/build/webhooks/subscribe/https" rel="noopener noreferrer"&gt;https://shopify.dev/docs/apps/build/webhooks/subscribe/https&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Slack Events API response deadline: &lt;a href="https://www.vincentschmalbach.com/setting-webhook-response-deadlines-to-prevent-failures/" rel="noopener noreferrer"&gt;https://www.vincentschmalbach.com/setting-webhook-response-deadlines-to-prevent-failures/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Netflix Technology Blog, “Performance Under Load”: &lt;a href="https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581" rel="noopener noreferrer"&gt;https://netflixtechblog.medium.com/performance-under-load-3e6fa9a60581&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Netflix concurrency-limits library: &lt;a href="https://github.com/Netflix/concurrency-limits" rel="noopener noreferrer"&gt;https://github.com/Netflix/concurrency-limits&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;RFC 8289, “Controlled Delay Active Queue Management”: &lt;a href="https://datatracker.ietf.org/doc/rfc8289/" rel="noopener noreferrer"&gt;https://datatracker.ietf.org/doc/rfc8289/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;CoDel pseudocode (ACM Queue): &lt;a href="https://queue.acm.org/appendices/codel.html" rel="noopener noreferrer"&gt;https://queue.acm.org/appendices/codel.html&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Facebook Wangle CoDel implementation: &lt;a href="https://github.com/facebook/wangle/blob/master/wangle/concurrent/Codel.cpp" rel="noopener noreferrer"&gt;https://github.com/facebook/wangle/blob/master/wangle/concurrent/Codel.cpp&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Svix, “Webhook retry strategies”: &lt;a href="https://www.svix.com/resources/webhook-university/reliability/webhook-retry-strategies/" rel="noopener noreferrer"&gt;https://www.svix.com/resources/webhook-university/reliability/webhook-retry-strategies/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Hookdeck, “Webhook Delivery Guarantees”: &lt;a href="https://hookdeck.com/webhooks/platforms/webhook-delivery-guarantees-comparison" rel="noopener noreferrer"&gt;https://hookdeck.com/webhooks/platforms/webhook-delivery-guarantees-comparison&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-an-in-memory-circuit-breaker-for-webhook-ingress-shedding-load-without" rel="noopener noreferrer"&gt;https://instawebhook.com/blog/building-an-in-memory-circuit-breaker-for-webhook-ingress-shedding-load-without&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

</description>
      <category>programming</category>
    </item>
    <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>
  </channel>
</rss>
