In one line. anyhook signs a business event with the Standard Webhooks scheme, fans it out to every subscribed customer endpoint, and then retries, circuit-breaks, dead-letters, and replays each delivery from inside a Cloudflare Worker or an AWS Lambda, so a product can send reliable webhooks without operating a delivery service or provisioning a database.
Every product with an API eventually needs to send webhooks: a customer registers a URL, and when something happens (a payment succeeds, a build finishes, a document is signed), your system is supposed to POST them a signed notification. It sounds like a loop and an HTTP call. It is not. Reliable outbound delivery is a distributed-systems problem, and every team rediscovers the same list the hard way: at-least-once delivery, exponential backoff with jitter, per-endpoint circuit breaking, idempotency, a signature scheme, a dead-letter store, and replay. The open-source tools that solve it well (Svix, Convoy, Hookdeck Outpost, Hook0) are all long-running servers that require Postgres and usually Redis. None of them runs natively on the serverless and edge runtimes where a growing number of products already live. anyhook fills that gap: it is a library plus a thin runtime that installs as a Cloudflare Worker (with Durable Objects) or an AWS Lambda (with SQS and DynamoDB), with no server to operate and no database to provision.
Figure 1. Left: a product that must stand up and operate a separate webhook delivery server backed by Postgres and Redis. Right: the same delivery guarantees running inside the product's existing Worker or Lambda, with no server and no database.
Why this matters
- For any product with a public API: outbound webhooks are table stakes, but the reliability layer under them (retries, backoff, circuit breaking, idempotency, dead-lettering, replay) is a real state machine that teams re-implement badly and repeatedly. anyhook makes that layer a dependency you install, not a service you operate.
- For the Standard Webhooks ecosystem: the Standard Webhooks specification, backed by OpenAI, Anthropic, Google Gemini, Svix, and Supabase (status as of July 2026), standardizes how webhooks are signed and verified, but explicitly leaves reliability (retries, backoff, dead-lettering) non-normative, with no open reference delivery engine. anyhook is wire-compatible with the signing scheme and aims to be that reference for the reliability behavior.
- For teams already on the edge: because delivery runs where your code runs and returns at durable acceptance, the producer's request latency is never coupled to a receiver's uptime, and there is no delivery cluster to size, patch, or pay for at idle.
🧭 If you only read this far: anyhook lets a product send signed, retried, circuit-broken, replayable webhooks with no delivery server and no database, running inside a Cloudflare Worker or an AWS Lambda.
Terms in 30 seconds
Domain engineers: skip ahead, this is orientation for everyone else.
- Outbound webhook: an HTTP request your product sends to a customer's URL when an event happens, so their system can react without polling yours.
- Standard Webhooks: an open specification for signing and verifying webhooks (an id header, a timestamp header, and an HMAC-SHA256 signature header), so any receiver can verify any sender with a shared library. Living document, source of truth in the spec repository; status as of July 2026. (standardwebhooks.com)
- Durable Object: a Cloudflare Workers primitive that is a single addressable piece of state with its own storage, single-threaded consistency, and an Alarms API for scheduled wake-ups. (Cloudflare Durable Objects)
- Circuit breaker: a pattern that stops calling an endpoint after a threshold of consecutive failures, then probes it again after a cooldown, so one dead receiver cannot burn delivery capacity for everyone else.
- Dead-letter queue (DLQ): where a delivery that has exhausted its retry schedule is parked, so it can be inspected and replayed rather than silently lost.
- At-least-once delivery: the guarantee that a message is delivered one or more times, never zero; receivers deduplicate using a stable id.
The problem, for engineers who don't live in this space
Imagine you run a shop and you promise customers a receipt for every order. You could hire a courier to hand-deliver each one and wait at the door, but then you cannot serve the next customer until this receipt lands, and if the customer's mailbox is full you have no plan except to try forever or give up. What you actually want is a postal service with tracking: you hand off the receipt, it gets delivered, failed attempts are retried on a schedule, and undeliverable mail goes to a returns office you can inspect. That is exactly the gap between "POST the webhook inline" and "deliver the webhook reliably."
Here is the concrete version. Your API emits a payment.succeeded event, and three customers have registered endpoints for it. One endpoint returns 200 immediately, one is timing out, and one has been returning 503 for an hour. The naive implementation loops over the three endpoints and POSTs to each inside the request that triggered the event. Now your API's latency is hostage to the slowest receiver, a thread is blocked on a timeout, and when the 503 endpoint fails you either drop the delivery or retry in-line and make it worse. Nothing records that endpoint two is sick, so the next event repeats the whole ordeal.
Figure 2. Inline fan-out: one event, three endpoints, delivery attempted synchronously. A dead endpoint blocks and fails delivery to the healthy ones, and no per-endpoint retry state survives the request.
Why this is genuinely hard. The honest fix is a per-endpoint state machine, and the unit of that state has to be the message (one event for one endpoint), never the event. A dead endpoint must provably never delay or fail delivery to a healthy endpoint for the same event. On top of that you need at-least-once semantics, jittered backoff so recovering receivers are not stampeded, a circuit breaker per endpoint, idempotency so replays are safe, a dead-letter store, and a replay surface. Building that as a server with a database is well understood. Building it so it also runs on a runtime that has no long-lived process and no database is the part nobody had packaged.
What exists today (and the gap)
| Tool | Standard Webhooks signing | Retries, backoff, circuit breaker, DLQ | Message-level (event x endpoint) isolation | Runs on serverless / edge with no server and no database |
|---|---|---|---|---|
| Svix (self-hosted) | ✅ | ✅ | ✅ | ❌ (Rust server + Postgres + Redis) |
| Convoy | ✅ | ✅ | ✅ | ❌ (Go server + Postgres + Redis) |
| Hookdeck Outpost | ✅ | ✅ | ✅ | ❌ (Go server + Postgres + Redis + queue) |
| Hook0 | ✅ | ✅ | ⚠️ | ❌ (Rust server + Postgres) |
| Roll-your-own (inline POST) | ⚠️ | ❌ | ❌ | ⚠️ |
| anyhook | ✅ | ✅ | ✅ | ✅ |
These are capable, fair competitors: Svix authored the Standard Webhooks spec, and Convoy and Hookdeck Outpost both do retries, circuit breaking, and per-tenant isolation well. The one column none of them fills is the last one. No existing open-source webhook delivery engine runs on serverless and edge runtimes with no server to operate and no database to provision; every mature option is a long-running server backed by Postgres, and usually Redis or a broker. That empty final column is anyhook's original contribution, and the rest of this piece is about how it is filled without giving up the reliability guarantees the server-based tools provide.
How it works
anyhook is a runtime-agnostic engine plus thin per-runtime adapters, the same inversion its sibling queue library uses. It has three layers:
@anyhook/core: the engine. It handles ingest and idempotency, subscription matching and fan-out, Standard Webhooks signing, the retry and circuit-breaker and dead-letter state machine, the delivery log, and the replay and portal handlers. It has zero runtime-SDK dependencies; it defines ports (interfaces), and adapters implement them.- Runtime adapters:
@anyhook/cloudflare(Durable Objects for state, the Alarms API as the retry scheduler, Cloudflare Queues for transport) and@anyhook/aws(DynamoDB for state, SQS for transport, adue_atindex plus a sweeper for long retries). Each adapter bundles a transport and a durable state store for one runtime. - Transport via anyq: rather than reimplement queueing, anyhook composes anyq, an existing queue abstraction, as its internal buffer.
@anyhook/cloudflareuses the anyq Cloudflare Queues driver;@anyhook/awsuses the anyq SQS driver.
Figure 3. send() durably accepts an event into the core engine (blue), which fans it out and signs. The transport (anyq) buffers each message; the runtime adapter's durable store (Durable Objects or DynamoDB) holds per-endpoint retry and circuit state and owns the retry schedule. Delivery targets (gray) are the customers' endpoints.
The engine and the async contract
send() returns only after the event is durably accepted (enqueued and idempotency recorded), and it never blocks on delivery. Fan-out turns one event into N messages, one per matching endpoint, each with its own independent retry and circuit state. That independence is the load-bearing property: it is what lets a healthy endpoint be delivered on the first attempt while a dead sibling endpoint fails on its own schedule.
The two-layer retry model
This is the decision that makes serverless viable, so it is worth stating precisely. There are two distinct retry layers, and they are kept separate. Transport-level redelivery (making sure a message travels from ingest to a delivery worker, and is redelivered if a worker crashes) is anyq's job. Endpoint-level retry (the webhook-specific state: this endpoint returned 503, next attempt in five minutes with jitter, three strikes before the circuit opens) is anyhook's, and that state lives in the durable store, not the queue. The store is the schedule: on Cloudflare a Durable Object Alarm wakes the endpoint up when a retry is due; on AWS a due_at index plus a periodic sweeper does the same.
The runtime adapters
On Cloudflare there is one Durable Object per endpoint, which gives single-threaded consistency and per-endpoint fairness for free, plus one index object per tenant for endpoints and idempotency. On AWS the same shape maps onto a single DynamoDB table with conditional writes for circuit transitions, tenant-scoped keys, and SQS DelaySeconds for short retries with a sweeper for the long tail. The core engine is unchanged across both.
The decisions that actually mattered
A zero-dependency core versus a Cloudflare-first engine
The fork. anyhook could have been written directly against the Cloudflare Workers runtime (fastest to a working edge product) or as a runtime-agnostic engine that knows nothing about any cloud SDK.
Options. A Cloudflare-native engine versus a ports-and-adapters core with zero runtime-SDK dependencies.
Chosen. The zero-dependency core. Trade-off accepted. Every runtime capability (transport, durable state, the scheduler, the HTTP client, the URL policy) becomes an interface the core depends on and an adapter must implement, which is more indirection and more code than calling env.QUEUE.send directly. The payoff is that the entire delivery state machine is tested in memory with no cloud in the loop, and that porting to a new runtime (AWS after Cloudflare, and a Go port after TypeScript) never touches the engine. A CI check enforces that the core has no runtime-SDK imports.
The message, not the event, is the unit of retry
The fork. When an event fans out to many endpoints, retry and circuit state can be tracked per event (simpler, fewer records) or per message, meaning per (event, endpoint) pair.
Options. Event-level retry state versus message-level retry state.
Chosen. Message-level. Trade-off accepted. One event that matches ten endpoints becomes ten durable messages, each with its own attempt counter, next-attempt time, and circuit interaction, which is more state to store and schedule. But it is the only model in which a dead endpoint provably cannot delay or fail a healthy one for the same event, and that isolation is the correctness property the whole product is sold on. There is a dedicated test that fans one event out to a healthy and a dead endpoint and asserts the healthy delivery is on time and unaffected.
Why this is new, and why it's significant
What's novel. anyhook is, to my knowledge, the first open-source webhook delivery engine that runs natively on serverless and edge runtimes, with no server to operate and no database to provision, while remaining wire-compatible with the Standard Webhooks signing scheme. This is the empty column from the landscape table: the mature open-source delivery engines are all long-running servers backed by Postgres.
Why it's significant to the field. The Standard Webhooks specification standardizes signing and verification, and it is deliberate about scope: its reliability recommendations (retries, backoff, dead-lettering) are non-normative and have no reference delivery engine. That is a gap the standard itself names as open, which is the strongest kind of significance argument, because a problem a specification explicitly leaves unsolved is significant by construction. The specification is not a fringe effort; it is backed by OpenAI, Anthropic, Google Gemini, Svix, and Supabase (status as of July 2026), so the sending side of a large slice of the industry already speaks it. The second anchor is the runtime shift: primitives like Cloudflare Durable Objects and AWS Lambda have made "reliable stateful delivery with no server" a real deployment target rather than a contradiction, and no delivery engine had been built to that target.
Standards and ecosystem alignment. This work implements and aligns with:
- Standard Webhooks: anyhook signs with the
webhook-id/webhook-timestamp/webhook-signatureheader scheme and thev1,HMAC-SHA256 format, including space-separated multi-signatures for key rotation. Its signatures are verified byte-for-byte against the officialstandardwebhookslibrary in a required cross-library test, in both directions. - anyq: anyhook composes the anyq queue abstraction as its transport rather than hardcoding a broker, which is how it reaches two runtimes without reimplementing queueing.
- Cloudflare and AWS platform primitives: Durable Objects and the Alarms API on the edge; SQS, DynamoDB conditional writes, and delayed messages on AWS.
📌 Honest scope. anyhook is at v0.2. The full delivery state machine is covered by 98+ automated tests, the signing is verified against the official Standard Webhooks library, and an end-to-end delivery has been proven on the real Cloudflare Workers runtime under Miniflare. It has not yet been load-tested at production scale, it has no external adopters, and no upstream contribution proposing its reliability behavior to the Standard Webhooks specification has been filed yet.
See it run
Install the core and the Cloudflare adapter, deploy the Worker template, then register an endpoint and emit an event:
npm i @anyhook/core @anyhook/cloudflare
# register a customer endpoint (the signing secret is returned once)
curl -X POST https://your-worker/v1/endpoints \
-H 'x-anyhook-tenant: acme' \
-d '{"url":"https://customer.example.com/hook","eventTypes":["payment.*"]}'
# emit an event; this returns at durable acceptance, not delivery
curl -X POST https://your-worker/v1/events \
-H 'x-anyhook-tenant: acme' \
-d '{"type":"payment.succeeded","payload":{"amount":4200}}'
{"eventId":"evt_9f3k...","accepted":true,"messageCount":1} # ← returned in ~milliseconds; delivery happens in the background
The customer's endpoint then receives a signed POST that any Standard Webhooks library can verify:
webhook-id: msg_2a... # ← stable across retries, so receivers dedupe
webhook-timestamp: 1769470000
webhook-signature: v1,g9EIBBIwm31AQEkP7q60DV8jDWYbrjV7TTZJL+PIcMo= # ← verifies with the official standardwebhooks library
Every attempt is recorded in a delivery log queryable at GET /v1/deliveries, and an exhausted or failed delivery can be re-sent with POST /v1/deliveries/:id/replay.
🔬 Go deeper. The delivery state machine, retry schedule, and circuit breaker live in
packages/core/src/engine.ts, and the runtime-agnostic ports every adapter implements are inpackages/core/src/ports/index.ts.
What's next
The near-term work is load testing the edge and AWS paths at production volumes and filing an upstream proposal that offers anyhook's reliability behavior as reference for the Standard Webhooks specification's non-normative delivery guidance. anyhook is the outbound piece of a small toolkit of the plumbing that products re-solve: auth-gateway handles requests coming in, anyq handles async work within the system, and anyhook handles events going out. The through-line is the same: take a distributed-systems problem every team re-implements, and turn it into a library that runs where the code already runs.
Appendix / references
- Repository: github.com/sns45/anyhook
- Related in this series: anyq (the queue abstraction anyhook composes) · auth-gateway (the inbound-auth sibling)
- Standards referenced: Standard Webhooks (standardwebhooks.com) · Cloudflare Durable Objects (docs)
- Discussion / feedback: github.com/sns45/anyhook/issues