Build1 distinct publisher2 min readPublished
A dev.to tutorial builds an Express receiver with SHA-256 idempotency keys, four attempts and a dead_letters table. The dedupe holds up, right until you run two copies of it.
The Engineer · Build desk
Compiled by The EngineerSomething wrong?How this is made
The dedupe here is not a lookup, it is an insert, and that is the part worth copying. The tutorial's own warning explains why: a check-then-insert pair leaves a race window when Strapi or a load balancer hands two copies of one event to two workers at the same moment [8]. Making the idempotency key the primary key of `processed_webhooks` [13] means the second copy loses at write time rather than at read time [7]. That trick ports to any store with a unique index.
It is also scoped smaller than it looks. The reference implementation keeps both tables in a local SQLite file [13]. A load balancer implies more than one receiver, and two receivers each holding their own `webhooks.db` share no unique constraint, so the exact concurrency the tutorial names is the one its sample cannot defend against [4]. Postgres or Redis behind the same code shape fixes it; nothing else about the receiver changes.
Then the fingerprint: event, model, entry id and the entry's `updatedAt`, hashed with SHA-256 [5]. Strapi's payload carries no delivery ID [4], so there is nothing better on offer, but the key's freshness rests entirely on `updatedAt` moving [6]. The builder reads the body with optional chaining [c5b], so an entry that arrives without `updatedAt` hashes to the same value forever, and the next real edit to it is received, matched and discarded as a duplicate [5]. Silently dropped updates are one of the three failure modes the receiver exists to prevent [3], which argues for rejecting a body with no `updatedAt` outright instead of hashing the word "undefined" into a permanent key.
The ordering has a second consequence. The key is recorded before any processing happens [14], and a genuine redelivery produces an identical fingerprint [6], so once an event has spent its attempts and landed in `dead_letters` [10], a retry from Strapi is a no-op [3]. Upstream gives you no second chance at that row.
The same ack-then-work sequence [9] leaves a durability hole that the SQLite tables do not cover. Between the 2xx and the last retry, the pending work lives in one Node process. If that process dies in the middle, Strapi has already been told the delivery succeeded [2], the dedupe key is already written [14], and no `dead_letters` row was ever inserted [10], so the event leaves no trace anywhere [7]. Acknowledging fast is correct; holding the retry loop in memory is what makes it lossy. The honest version of this design writes the job to a durable queue in the same transaction as the idempotency key, and retries from there.
Ranked by verification strength, evidence, and original report placement.
Strapi webhooks fire a POST request at a configured URL when an entry changes.
Strapi retries a webhook delivery if the endpoint does not respond within its timeout or returns a non-2xx status, so the tutorial tells readers to assume every event can arrive more than once.
The tutorial states that failing to plan for duplicate delivery or downstream timeouts produces duplicated content in a search index, double-charged CRM records, or silently dropped updates nobody notices until a customer complains.
Strapi's webhook payload does not include a delivery ID, so the receiver must derive a stable fingerprint from the event itself.
The tutorial computes the idempotency key as a SHA-256 hash of the string event:model:entry.id:entry.updatedAt.
The key builder interpolates entry?.id and entry?.updatedAt with optional chaining, so absent values are stringified into the fingerprint rather than raising an error.
Follow any of these and your For You feed starts watching them — no settings page required.
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.
Code shown in full, premise uncorroborated
Nearly every factual claim is directly inspectable in the supplied listings — the key builder, the DDL, MAX_ATTEMPTS, the backoff sleep, the dead_letters insert, the secret check — which makes the implementation claims strongly self-evidenced. What is not evidenced is the premise the tutorial rests on: Strapi's retry count, timeout window and backoff schedule are asserted in prose with no documentation citation, no test output, and no second source in the cluster. The body is also truncated mid-sentence at the verification step, so the promised local reproduction is not visible.
No adoption signal in cluster
The cluster contains one tutorial and no release, deployment, benchmark, pricing, licensing, or usage-disclosure evidence. Nothing indicates whether this receiver pattern is running anywhere in production, how many teams use it, or what Strapi webhook usage looks like at scale, so adoption cannot be scored without inventing facts.
Guarantees overstated versus shipped code
The article promises code that 'handles all three failure modes' and describes duplicates as no-ops rejected before any processing, but the shipped design leaves three material holes readable in that same code: the unique constraint that carries the whole dedupe argument lives in a local SQLite file and cannot cover the load-balanced concurrency the article itself raises; acknowledging before the downstream call means a crash in flight loses the event with no redelivery and no dead-letter row; and a payload missing updatedAt permanently collapses an entry's future edits into one key. The gap is a matter of scope overreach in a genuinely useful walkthrough, not fabrication, so it is moderate rather than severe.
No disclosed incentive information
The supplied material is an individually authored community post with no disclosure of employment, sponsorship, vendor affiliation, or commercial relationship with Strapi or any tool named, and no product or paid offering is promoted in the visible body. Any incentive scoring would require assuming facts the source does not provide.
High on the code, low on the premise
Confidence in what the tutorial does and what its code implies is high, because the implementation claims and all seven derived findings are readable in the listings supplied. Confidence in the surrounding picture is low: one publisher, one author, no independent verification of Strapi's retry semantics, no adoption or incentive evidence, and a truncated body. The blended result sits just above the midpoint.
build
A unique index is not a duplicate check: the race inside a webhook idempotency middleware1 distinct publisher
build
A Timed-Out Reset SMS Is Not A Failed One, And Your Retry Code Probably Disagrees1 distinct publisher
build
The OTP send-storm is an admission bug: cooldowns belong in the transaction, not the countdown1 distinct publisher
build
Your JWT Login Probably Has Exactly One Kill Switch: Log Everyone Out1 distinct publisher
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 24, 2026