In one line. anyonce claims each idempotency key in one atomic write behind HTTP, queue and webhook doors in TypeScript and Go, so it runs a handler at most once per key while its record is alive (limits under Honest scope), and its conformance suite grades any implementation of the Internet Engineering Task Force (IETF)
Idempotency-Keydraft the same way.
A phone retries an order after losing the answer; a queue redelivers to a second worker; a webhook sender posts again. Every way work reaches a backend delivers at least once, and the safe response is to recognize the second copy and answer it with the result of the first. Teams solve that once per door, with libraries that disagree. anyonce solves it once: one core, three thin doors, seven stores, two languages, and a test suite anyone can point at their own implementation.
Figure 1. Today each door gets its own partial fix. anyonce puts one claim, run, store, replay cycle behind all three, and grades HTTP implementations with one suite.
Why this matters
- For anything that creates, charges or sends: a duplicate order or a double charge is the visible cost of at-least-once delivery. "Look up the id, then write it" leaves a window in which two copies both miss and both run. anyonce closes it with one atomic write per store, tested by a race of 50 concurrent claims in which exactly one wins, on every backend, in both languages.
- For implementers of the IETF draft:
draft-ietf-httpapi-idempotency-key-header-07expired on 18 April 2026 with no -08, and no conformance vectors for it existed anywhere (status as of September 2026). anyonce ships 20 executable vectors, 11 for what the draft requires and 9 for its own choices, and ran the 11 against three independent implementations: hono-idempotency 0.9.1 passes 11, idempo v1.0.0 passes 9, Fiber v3.5.0 passes 7. - For teams with more than one door: an HTTP endpoint, an anyq queue consumer and a Standard Webhooks receiver get the same state machine and store contract, so "what happens on a retry" has one answer per system, not one per library.
🧭 If you only read this far: one idempotency engine behind three doors in TypeScript and Go, one atomic claim write per store, and the first executable conformance suite for the IETF
Idempotency-Keydraft, run against three other implementations. 0.1.0, validated in continuous integration, published on npm with provenance and as Go module taggo/v0.1.0.
Terms in 30 seconds
Domain engineers: skip ahead, this is orientation for everyone else.
- Idempotency key: a unique value a client attaches to a request so the server can recognize a retry of it.
- At-least-once delivery: nothing is lost, but anything may arrive more than once.
- The
Idempotency-Keyheader draft:draft-ietf-httpapi-idempotency-key-header-07, a document of the IETF HTTP APIs working group. Published October 2025, expired 18 April 2026, no -08 (status as of September 2026). - Fingerprint: a hash of the request payload stored with the key, so the same key with a different body is detected rather than replayed. anyonce uses SHA-256.
- Lease and fence token: a claim holds the key for a bounded time (the lease), and every takeover of an expired claim increments a counter (the fence) so a late writer cannot overwrite the newer result.
- RFC 9457, Problem Details for HTTP APIs: the standard JSON shape for HTTP error bodies (July 2023, obsoletes RFC 7807). (rfc-editor.org)
- RFC 9651, Structured Field Values for HTTP: the syntax the draft uses for the header value, a quoted "sf-string" (September 2024, obsoletes RFC 8941). (rfc-editor.org)
- Standard Webhooks: an open specification for signing and verifying webhooks with a
webhook-id, a timestamp and an HMAC-SHA256 (hash-based message authentication code) signature. Living document, status as of September 2026. (standardwebhooks.com) - anyq: a TypeScript and Go message queue library with one consumer interface across brokers; anyonce's queue door is anyq middleware. (case study)
The problem, for engineers who don't live in this space
Think of a coat check. The same ticket twice gets the same coat twice, not two coats. Two people with the same number at the same moment: one gets the coat, the other waits. Your number with a different coat description: "that is not your coat". The ticket is the idempotency key, the description is the fingerprint, and the attendant is what most backends lack.
Here is the concrete version. A Hono app on Cloudflare Workers takes POST /orders. The mobile client times out and retries with the same Idempotency-Key while the first request is still running. The order also goes to a queue, where a consumer's visibility timeout expires before it acknowledges, so the broker redelivers it. The payment provider's webhook for the order is retried because the receiver answered slowly. Three doors, one order, three duplicates.
Figure 2. Every door delivers at least once. A lookup table keyed on the id is not a fix: when the second copy arrives before the first has written its claim, both read "absent" and both run.
Why this is genuinely hard. The claim must be one atomic operation, and every store spells atomic differently. A crashed worker must not hold a key forever (a lease), and a worker that outlives its lease must not overwrite whoever took over (a fence). Then the policy questions: is a 500 replayed, what does a concurrent duplicate get, how large a result is worth storing. anyonce records 17 places where the draft is silent, ambiguous or out of date in DRAFT-GAPS.md.
What exists today (and the gap)
The last three columns are the three items of the novelty claim, quoted in full under Why this is new, with item 3 in its narrowed form.
| Project | HTTP door, draft semantics | Queue consumer door | Webhook receiver door | TypeScript | Go | Claim 1 | Claim 2 | Claim 3 |
|---|---|---|---|---|---|---|---|---|
| hono-idempotency 0.9.1 (18 Jul 2026) | ✅ Hono only | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| idempo v1.0.0 (2 Jun 2026) | ✅ | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
| Fiber idempotency middleware, v3.5.0 (12 Aug 2026) | ⚠️ X-Idempotency-Key, no 422 | ❌ | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ |
idempot-js (@idempot/core 1.3.0, 3 Aug 2026) | ✅ draft -07 | ❌ | ❌ | ✅ | ❌ | ❌ | ❌ | ❌ |
| quayside 1.4.0 (first published 15 Aug 2026) | ✅ Express, Fastify, Hono | ⚠️ recipes, not adapters | ❌ | ✅ | ❌ | ⚠️ one core, HTTP adapters plus queue recipes, TypeScript only | ❌ | ⚠️ TypeScript only |
| Powertools for AWS Lambda idempotency 2.35.0 (18 Aug 2026) | ⚠️ any Lambda event, no draft header | ✅ SQS via Lambda | ❌ | ✅ | ❌ | ⚠️ Lambda only, no Go | ❌ | ❌ |
Watermill deduplicator, MassTransit inbox, NATS Nats-Msg-Id, SQS FIFO | ❌ | ⚠️ one broker or framework each | ❌ | n/a | ⚠️ Watermill | ❌ | ❌ | ❌ |
| anyonce 0.1.0 (24 Sep 2026) | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
Reading the rows honestly. hono-idempotency passes all 11 core vectors; its author documents that its Cloudflare KV store is not atomic across edge locations. quayside is the closest project and the reason item 3 is narrowed: it has one transport-agnostic core, and five TypeScript stores pass one storage contract suite with an atomic create-if-absent claim and a 50-way concurrency race. That is part of claims 1 and 3 in one language, so it earns ⚠️ on both. Powertools applies one utility to any Lambda event source, with no Go, no draft header semantics and no suite. Claims 1 and 3 require both languages, so ⚠️ is the most a single-language project can earn there. quayside and idempot-js were found in the launch re-verification and have not been graded by the suite yet; idemgate, a Go sidecar citing draft -04, is HTTP only and ships no suite either.
How it works
The doors translate their protocol into an operation, the engine decides, and only the store touches state.
- Engine:
execute(store, op, run, policy), where an operation is a scope, a key and a fingerprint; it decides run, replay, in flight or mismatch. - Store contract:
begin(one atomic write),complete,abandon,purge; seven stores, six in TypeScript and five in Go. - Doors:
withIdempotencyand@anyonce/hono(Gohttpmw);idempotentplusidempotencyStrategy(Goanyqmw);webhookReceiver(Gowebhookmw). - Conformance suite: JSON vectors, runners in TypeScript and Go, a CLI that speaks only HTTP, and a generated cross-implementation report.
Figure 3. Three thin doors over one engine and one store contract. Adapters never talk to a store directly, so a new store or door is written against one interface.
The state machine
A record is absent, in_flight or completed. A duplicate of a completed request gets the stored result, marked Idempotency-Replayed: true; one that arrives while the first is running gets 409 conflict with Retry-After set to the seconds left on the lease; the same key with a different body gets 422 fingerprint-mismatch. A 5xx or a thrown error abandons the claim so the client can retry. Every error is an RFC 9457 problem with a stable code, from nine documented in docs/problems.md.
Figure 4. One state machine, three protocols. The queue door maps the same outcomes onto broker verbs: a completed duplicate is acknowledged without running, an in-flight one is parked for the rest of the lease, a mismatch is dead-lettered.
Leases, fences and the doors
If a worker dies, its lease expires and the next request takes the key over with the fence incremented, so no janitor is needed to free a stuck claim. If the first worker was only slow and completes afterwards, its fence is stale and the write is refused, so every replay returns the takeover's result. The HTTP door applies to POST and PATCH by default and fingerprints the method, path and body. The queue door keys on a producer-supplied idempotency-key header by default, because some brokers change a message's id on redelivery (docs/queue-ids.md has the table per adapter). The webhook door verifies the signature before anything touches the store.
The conformance suite
core vectors test what the draft requires (single execution, replay, 409, 422, 400 on a missing required key, sf-string keys, case-insensitive header names), and each cites the draft section it tests. profile vectors test anyonce's own choices, such as the replay header and not storing a 5xx. Third parties are graded on core only. The runners know nothing about stores or languages: they send HTTP requests to a few fixture routes (/echo, /status/{n}, /slow, /counter), so any implementation in any language can be graded the same way.
The decisions that actually mattered
One atomic write per store, identical in both languages
The fork. A claim can be a read then a write, which every backend supports and a test on one machine can pass, or one conditional write, which each backend spells differently. Options. Get-then-lock; a distributed lock around the lookup; one conditional operation per store. Chosen. One conditional operation per store, with the text shared: the SQL statements, Lua scripts and DynamoDB expressions exist once per language, and a parity test compares the Go constants byte for byte with the TypeScript ones. Every store in both languages runs the same race: 50 concurrent begins, exactly one acquired, 20 iterations. Trade-off accepted. Cloudflare KV is eventually consistent across locations, so two Workers can both read "absent" and both write; there is no KV store, by design. DynamoDB's 400 KB item limit caps a stored result there at 300 KiB, and a refused SQL claim costs a second statement to classify.
Figure 5. Two round trips leave a window; one conditional write does not. The parity test keeps both languages' statements identical.
A 5xx releases the key instead of being replayed
The fork. The draft says a retry gets the result of the completed operation, "success or an error". Read literally, a 500 is pinned to the key for its lifetime and the retry the header exists to make safe is useless. Options. Replay everything; replay only 2xx; or draw the line at 500. Chosen. Store and replay anything below 500, including a 404, and abandon the record on a 5xx or a thrown error. This is a profile choice, written up as gaps G6 and G7 with proposed text. The survey is split: against a 201, a 404 and a 500, hono-idempotency stores the 201 alone, idempo the 201 and the 404, Fiber all three.
Trade-off accepted. A handler that wrote to a database and then returned 500 runs again on retry. anyonce does not roll back side effects, and says so.
The webhook door verifies before it claims
The fork. Deduplicating first is cheaper: a duplicate never pays for verification. Options. Claim then verify, or verify then claim. Chosen. Verify then claim. An unsigned or forged delivery is 401 signature-invalid and never touches the store. Both languages are tested in a round trip against anyhook's signer and against its golden signature vectors.
Trade-off accepted. Every duplicate pays for an HMAC. The alternative lets anyone who can reach the endpoint plant a webhook-id and suppress the genuine delivery when it arrives.
Figure 6. Nothing unsigned crosses the trust boundary. Verification runs first, so the dedupe table only ever holds ids from deliveries that proved who sent them.
Why this is new, and why it's significant
What's novel. Section 0.3 of requirements.md claims anyonce is the first open-source idempotency primitive that:
- applies one core state machine and one store contract across the three ingress doors (HTTP, queue consumer, webhook receiver), in TypeScript and Go;
- ships an executable, language-agnostic conformance suite for
draft-ietf-httpapi-idempotency-key-header, separating the draft's normative requirements from implementation profile choices, and publishes results for third-party implementations; - guarantees the claim step is a single atomic write on every supported store (no get-then-lock window), verified by a shared store contract test that runs a concurrency race on every backend.
Items 1 and 2 stand. Re-verification on 24 September 2026 found item 3 already true of quayside 1.4.0 in TypeScript (first published to npm on 15 August 2026, 1.4.0 on 5 September 2026), so this page narrows it to:
guarantees the claim step is a single atomic write on every supported store in both TypeScript and Go, with each shared store's claim statement (SQL, Lua, DynamoDB condition expression) byte-identical across the two languages and enforced by a parity test, and the same concurrency race run against every backend in both.
The repository's requirements.md still carries the original wording at this commit.
The prior art is the table above with its dates, plus the Stripe, Adyen, PayPal and Square documentation (descriptions, not code) and the draft itself, whose editor's copy was last committed on 26 February 2025 (dab060c); the search covered npm, proxy.golang.org, the GitHub topic idempotency-key and the IETF datatracker, re-verified 24 September 2026.
Why it's significant to the field. The first anchor is the standard itself. The IETF runs on running code, and RFC 7942 gives drafts an Implementation Status section for that evidence, but with no conformance vectors "implements the draft" meant whatever each author read into it. The cross-implementation run turned that into data. hono-idempotency passes 11 of 11 core vectors, idempo 9 (it has no way to require the header, and it stores and replays a keyed GET, a vector anyonce's own draft issue and gap G2 call arguable, since the draft is silent on keys on safe methods), and Fiber 7 (fails core/concurrent-409, core/key-missing-required, core/mismatch-422 and core/mismatch-does-not-poison). It also found where the draft's text runs out: 17 gaps, each with the sentence at issue, anyonce's choice, proposed replacement text and the implementations that diverge. hono-idempotency and idempo both send the same unregistered Idempotency-Replayed header, and hono-idempotency also has its own error codes in upper snake case: the argument for writing both into the draft. Four of the draft's eight normative references are obsolete (RFC 8941, RFC 7807, RFC 4122, RFC 7231).
The second anchor is a second standard at a second door: Standard Webhooks gives every delivery a webhook-id that stays the same across a sender's retries, so a receiver that dedupes on it is doing idempotency whether it says so or not.
Figure 7. One runner, four implementations: anyonce 11 of 11 core vectors on all 11 targets, hono-idempotency 0.9.1 11, idempo v1.0.0 9, Fiber v3.5.0 7. Profile numbers are for information only.
Standards and ecosystem alignment. This work implements or aligns with:
- IETF
draft-ietf-httpapi-idempotency-key-header-07(October 2025, expired 18 April 2026, status as of September 2026): everycorevector cites its draft section. - RFC 9457 (Problem Details for HTTP APIs, July 2023): every error body, with a stable
codemember. - RFC 9651 (Structured Field Values for HTTP, September 2024): the sf-string key parser, lenient by default, strict on request.
- RFC 8785 (JSON Canonicalization Scheme, June 2020): the default fingerprint of a structured queue message in TypeScript.
- RFC 7942 (Improving Awareness of Running Code, July 2016): the shape of the Implementation Status entry drafted for the working group.
- Standard Webhooks (living specification, status as of September 2026): HMAC-SHA256 over
id.timestamp.body,v1,signatures, 5 minute timestamp tolerance.
The upstream contributions are drafted, not yet sent: a pull request adding an RFC 7942 entry to the working group's repository, a summary for the httpapi@ietf.org list, issues for the draft gaps, and an issue per failing third-party vector. None has been filed, posted or opened as of 24 September 2026.
📌 Honest scope. anyonce is 0.1.0, its first release: six npm packages with provenance and the Go module tag
go/v0.1.0. "Validated" means the continuous integration workflow, green on this commit: TypeScript and Go suites with the race detector, the store contract suite against DynamoDB Local, Redis 7, Postgres 16 and workerd, Node 22 and Deno 2 runtime matrices, a smoke test per example, and a real rerun of the cross-implementation report. Nothing has been load-tested in production or run against live cloud accounts. The overhead figure is an in-process microbenchmark with the memory store on one Apple M4 Max machine: 0.008 ms p50 on a first execution, 0.004 ms on a replay, against a 2 ms budget. The guarantee is at most one handler execution per key while the record is alive, not exactly-once side effects, bounded by the record's time to live (TTL, 24 hours by default), by the lease (a handler that outlives it can overlap a takeover; the fence keeps one result) and by fail-open mode if you choose it. On Cloudflare Workers, a client that disconnects mid-response can leave the record in flight until the lease expires, and a retry after that runs the handler again. There is no Cloudflare KV store, by design. Third-party results hold for the pinned versions and fixture settings in the report; Fiber needed its header name and key validation overridden to be measured.
See it run
Everything here works today from a clone of the public repository.
git clone https://github.com/sns45/anyonce && cd anyonce
bun install && bun run build && bun run test
0 fail # ← every test passes
Start the Go reference fixture behind httpmw and the memory store, then grade it with the TypeScript conformance CLI, the same runner used for every third-party row:
go run -C go ./cmd/fixture -idempotent -store memory -addr 127.0.0.1:8787 &
bun run conformance -- --url http://127.0.0.1:8787 --capability short-ttl --ttl-ms 2000
20 passed, 0 failed, 0 not applicable, 0 errored # ← 11 core + 9 profile
| core/concurrent-409 | core | pass | |
| core/mismatch-422 | core | pass | |
| core/retry-replays | core | pass | |
| profile/5xx-not-stored | profile | pass | |
| profile/retry-after-on-409 | profile | pass | |
...
The fixture's records live 2 seconds by default so the expiry vector runs quickly. For typing by hand, restart it with a 10 minute record lifetime, then send the same order three times:
go run -C go ./cmd/fixture -idempotent -store memory -ttl-ms 600000 -addr 127.0.0.1:8787 &
curl -si -X POST http://127.0.0.1:8787/echo -H 'Idempotency-Key: order-1' -d '{"item":"book"}'
curl -si -X POST http://127.0.0.1:8787/echo -H 'Idempotency-Key: order-1' -d '{"item":"book"}'
curl -si -X POST http://127.0.0.1:8787/echo -H 'Idempotency-Key: order-1' -d '{"item":"lamp"}'
HTTP/1.1 201 Created # ← first request, handler runs
{"item":"book"}
HTTP/1.1 201 Created
Idempotency-Replayed: true # ← same answer, handler did not run
{"item":"book"}
HTTP/1.1 422 Unprocessable Entity # ← same key, different payload
Content-Type: application/problem+json
{"type":"https://in8.sh/anyonce/problems/fingerprint-mismatch","title":"This Idempotency-Key was already used with a different request payload","status":422,"code":"fingerprint-mismatch"}
A second request under a key whose first request is still running (the fixture's /slow route) gets 409 Conflict with Retry-After: 30, the seconds left on the lease. The packages install with bun add @anyonce/core @anyonce/hono hono and go get github.com/sns45/anyonce/go@v0.1.0.
🔬 Go deeper. The claim is
beginin each store; start with the Postgres statement inpackages/stores/src/sql.tsand the test that holds the Go copy to it,packages/stores/test/parity.test.ts. The race every store must pass is inpackages/core/src/testing/index.tsandgo/storetest/storetest.go. The full scorecard isconformance/REPORT.md, all at commit4e0582e.
What's next
0.1.0 is published. Next is sending what is drafted: the Implementation Status pull request, the summary to the list, a first wave of draft issues (replay header, Retry-After on 409, the "success or an error" sentence, sf-string syntax, obsolete references), and the failing vectors to each project graded, offered as a shared test rather than a verdict. Then small parity items: a waitUntil hook for Workers, a Skip option for the Go webhook door, and closing the Go SQL stores' pools.
The larger arc is the one anyq and anyhook started. anyq gave consumers one interface across brokers, and anyonce is the consumer-side guarantee it lacked. anyhook signs and retries outbound webhooks, and anyonce is its receiving twin, deduplicating on the webhook-id anyhook sends, with that round trip tested in continuous integration.
Appendix / references
- Repository: github.com/sns45/anyonce, at commit
4e0582e - Packages: npm: @anyonce/core · pkg.go.dev: github.com/sns45/anyonce/go
- Design:
requirements.md(0.2 prior art, 0.3 novelty claim, 0.4 standards actions); semantics, stores, queue ids, problem types and security notes underdocs/; draft gaps inconformance/DRAFT-GAPS.md; the scorecard inconformance/REPORT.md. - Prior art checked: hono-idempotency v0.9.1 · idempo v1.0.0 · Fiber idempotency middleware v3.5.0 · quayside 1.4.0 · Powertools for AWS Lambda idempotency 2.35.0 · idempot-js
- Standards referenced: draft-ietf-httpapi-idempotency-key-header-07 · HTTPAPI working group repository · RFC 9457 · RFC 9651 · RFC 8785 · RFC 7942 · Standard Webhooks
- Related in this series: anyq · anyhook · auth-gateway
- Discussion / feedback: github.com/sns45/anyonce/issues