Build1 distinct publisher3 min readUpdated
One adverb in a requirement turned a POST forwarder into retries, a dead-letter queue, per-endpoint ordering and two layers of semaphore. The author's own tests kept correcting him.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
A developer published a writeup of webhook-delivery, a Go and Kafka service that accepts webhook events over HTTP and delivers them to customer endpoints, built as his first project with either technology [1][2]. The useful part is not the code but the accounting: one adverb in the requirement generated retries, exponential backoff, a dead-letter queue, per-endpoint ordering, idempotency, concurrency limits and per-host circuit breaking [3], which is seven mechanisms hanging off one word [4].
On the surface, he writes, it is "accept a POST, forward a POST", but say "reliably" and you inherit a distributed systems curriculum for free [5]. The five questions he lists are the ones any delivery system has to answer: what happens when the customer endpoint is down, what happens when it is slow rather than down, what happens when two events for the same customer arrive out of order, what happens when your own process crashes mid-delivery, and how you distinguish a failed delivery from a response that got lost [6]. He says none of them have a clean answer [7].
The resulting shape is conventional and worth restating because most teams get it wrong in the details: the API validates, publishes to a Kafka topic called events and returns 202; delivery workers consume that topic, group messages by an orderingKey so one customer's events stay in order, and POST them out; failures go to a retries topic with exponential backoff; permanent failures and events past a maximum age go straight to the dead-letter queue [8]. That paragraph, he notes, took about three weeks to actually get right [9].
The correction that matters is small. A `chan struct{}` with capacity N is a free concurrency limiter, and he needed two of them, a global cap on in-flight deliveries and a per-host cap so one flaky endpoint could not eat the whole pool, both the same primitive [10]. What did not click until he wrote a test is that if the acquire helper drops its `ctx.Done()` branch, a goroutine can block forever on a saturated semaphore even after the parent context is cancelled, because nothing wakes it [11]. The test that pins this, TestDeliverGroupUnblocksFromSaturatedSemaphore, fills the host semaphore, cancels the context, and fails if delivery is still stuck after two seconds [12]. His summary: a blocking channel operation without a `ctx.Done()` escape hatch is not a semaphore, it is a deadlock waiting for a bad day [13].
The other two lessons are about discipline rather than discovery. The delivery path nests a per-host semaphore, a global semaphore and a mutex over circuit breaker state, and writing that as flat Lock/Unlock with early returns is how you leak a lock, so each critical section became a function literal to give `defer` a clean scope [14]. And the worker depends on a one-method Publisher interface rather than a concrete Kafka producer, so tests use a recording publisher that appends to a slice, which is why 45 tests run in about three seconds with the race detector on and no Docker container involved [15], roughly 67 milliseconds per test [16]. Failure classification uses `errors.As` rather than string matching so it can see through wrapped errors [17].
What to watch: the post flags its Kafka section as the meatier part [18] and says it was the system, or the benchmark numbers, that corrected his mental model [19]. The material in hand stops at the error classification helper, before those numbers appear [20], so the two-layer semaphore and the cancellation escape hatch are what is currently transferable.
Follow any of these and your For You feed starts watching them — no settings page required.
Ranked by verification strength, evidence, and original report placement.
The author built webhook-delivery, a service that accepts webhook events over HTTP and reliably delivers them to customer endpoints.
The project was the author's first with both Go and Kafka, started after he got tired of reading about them.
The stated requirements included retries, exponential backoff, a dead-letter queue, per-endpoint ordering, idempotency, concurrency limits and per-host circuit breaking.
The author writes that on the surface the system is "accept a POST, forward a POST", but saying the word "reliably" means you inherit a whole distributed systems curriculum for free.
The author lists five questions: what happens when the customer's endpoint is down; what happens when it is slow instead of down; what happens when two events for the same customer arrive out of order; what happens when your own process crashes mid-delivery; and how you know a delivery failed versus the response just getting lost.
The author states that none of those questions have a clean answer, which is what he wanted to sit inside for a while.
Evidence-backed comparisons of source perspectives and observed adoption signals. Read the methodology
Which Builder, Operator, and Investor concerns the observed source mix emphasized—not a truth score.
Evidence, demonstrated adoption, hype gap, incentives, and confidence are assessed independently, each on its own current evidence. How these are measured.
Thin but self-consistent single-source evidence
All content comes from one first-person dev.to post. Its strength is that the mechanisms are shown, not just asserted: the acquire() semaphore helper, the cancellation regression test, the defer-scoped locking pattern and the full IsPermanent implementation are quoted as code, and the Kafka offset watermark lesson is explained with a concrete A/B key-group failure mode. Its weakness is that nothing is externally checkable — no repository link, commit history, CI log or benchmark output accompanies the three-week build estimate or the 45-tests-in-about-3-seconds figure, and no second publisher or reviewer corroborates any part of it. The capture is also truncated mid-sentence in the section the author calls the most substantive, so part of the argument is unavailable.
No adoption signal in supplied sources
The supplied post describes a personal learning project. It discloses no release, package, deployment, user, customer, traffic volume, star count, download figure, licence or pricing detail, and no other party is reported using or evaluating the code. There is nothing in the cluster from which adoption could be measured, so no adoption observations were recorded and this dimension is left unmeasured rather than inferred from the project's existence.
Mildly overstated by framing, restrained in substance
The gap is small and comes from framing rather than from the technical content. The rhetorical device — say 'reliably' and you inherit a whole distributed systems curriculum for free — sits above what a single-author first project can substantiate, and the two headline quantities (three weeks; 45 tests in about three seconds with -race) are unverified self-reports presented as clinching evidence for the interface-seam argument. Pulling the score back toward zero: the author explicitly frames the piece as the moments his mental model was flat-out wrong, concedes a bad first draft on offset commits, ships code rather than conclusions, and makes no claim of production use, scale or novelty. Nothing here is dressed as a product or a benchmark win.
Reputational, non-commercial
The only incentive visible in the supplied material is developer-audience reputation: a personal dev.to retrospective on a self-directed project, which rewards engaging narrative and credible-sounding metrics. There is no vendor sponsorship, no product being sold, no employer or funder named, no affiliate or pricing angle, and no benchmark being marketed against a competitor. That keeps distortion pressure low but not zero, since the unverified timing and test-count figures are exactly the kind of detail a portfolio post benefits from.
Moderate on mechanics, low on metrics
Confidence is split. The mechanical claims — a ctx.Done() escape hatch being required on blocking channel operations, defer scoping via function literals, errors.As surviving fmt.Errorf wrapping, and a Kafka offset commit being a watermark rather than a per-message acknowledgement — are checkable against language and broker semantics and are stated precisely, so they can be relied on. The quantitative and process claims are single-source self-reports with no artefacts and warrant much less weight. Single-publisher structure, absence of any adoption signal, and a body that truncates mid-sentence inside the author's designated core section all hold the overall figure near the middle.
build
Your meter now runs on someone else's machine: signed receipts, fsync, and failing open1 distinct publisher
build
A Timed-Out Reset SMS Is Not A Failed One, And Your Retry Code Probably Disagrees1 distinct publisher
build
Three services you can delete: queue, cache and search in one Postgres1 distinct publisher
build
Buy transactional email on recovery controls, not send price1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 16, 2026