An append-only event-stream API on Cloudflare Workers. This page replays an actual recorded run (real requests, real responses, real SHA-256 digests) and explains what happens inside at each step.
The Worker at the edge is stateless; it authenticates and routes. The guarantees live in Durable Objects: tiny single-threaded actors, one per stream and one per API key. D1 (SQLite) is a read-only mirror for fast queries.
These are the three events from the recorded run, hashes byte-for-byte as the API returned them. Your browser is recomputing the chain right now with the same public rule the server uses. Edit any amount and watch verify catch it, then hit reset.
In production, "editing a payload" means tampering with the Durable Object's storage directly; the test suite does exactly that and asserts verify reports the first broken seq. Every hash folds in the previous one, so one edit invalidates the entire suffix of the log. Deleting the newest events (truncation) is caught too: verify compares the recomputed head against the stream's meta.
The client sent the same Idempotency-Key three times: once normally, once as a network-style retry, once as a retry with a different body (a buggy or malicious client). The ledger recorded exactly one event, and every retry got the original answer back.
The key is authoritative: it's bound to the first event forever. That last line matters: an adversarial review of this codebase found (and we fixed) a subtle bug where a changed retry body could poison the read-model mirror. Now the Durable Object hands the mirror its canonical payload, so the stored payload always hashes to the stored hash.
This key was minted with rate_per_min: 3. Three requests spend the bucket; the fourth is refused, and the limiter tells the client exactly when to come back: 60 s ÷ 3 tokens = 20 s to the next token.
| request | status | RateLimit-Remaining | Retry-After |
|---|---|---|---|
| POST /v1/streams #1 | 201 Created | 2 | · |
| POST /v1/streams #2 | 201 Created | 1 | · |
| POST /v1/streams #3 | 201 Created | 0 | · |
| POST /v1/streams #4 | 429 Too Many | 0 | 20 s |
Each API key gets its own single-threaded RateLimiterDO, so two simultaneous requests can never double-spend the same token: the same actor trick that gives streams their ordering.
One envelope everywhere: {"error":{"code","message"}}. And note the fourth row: asking about someone else's stream returns 404, not 403, so the API never confirms that a stream exists.
| scenario | status | code |
|---|---|---|
| No Authorization header | 401 | unauthorized |
| Invalid API key | 401 | unauthorized |
| Append without Idempotency-Key | 400 | idempotency_key_required |
| Another key's stream (no existence leak) | 404 | stream_not_found |
| Body over 256 KiB | 413 | payload_too_large |
| Bucket empty | 429 | rate_limited |
| Wrong admin secret on POST /v1/keys | 403 | forbidden |
__proto__ JSON keys, tail-truncation blindness in verify, and a fail-open admin guard.