Build1 distinct publisher3 min readPublished
Umair Rafi's Python walkthrough gets the contract right, and the interesting failure is what a duplicate sees while the charge is still in flight, when the replay branch fires before there is anything stored to replay.
The Engineer · Build desk

Compiled by The EngineerSomething wrong?How this is made
Read the printed FastAPI handler in execution order. It selects on the key, finds nothing, inserts a record with status `processing`, commits, and only then calls `payment_gateway.charge(body)` inside a try block [13]. That commit before the money moves is the good part of the design: it publishes the key to every other database connection before the gateway can be reached [9].
Now send the retry two hundred milliseconds later, while the charge is still open. The second request finds the record, compares hashes, and returns `existing.response_body` with `existing.status_code` [12]. Neither field has been written yet [21]. The client asked for an order id and gets an empty body with no status. The `status` column is in the record and nothing in the handler reads it [21]. A duplicate landing in that window wants a 409 and a backoff hint; the replay path is only correct once the record says `completed` [14].
The race between two simultaneous first requests is handled, but by the schema rather than by the code. `key VARCHAR(64) PRIMARY KEY` [7] means the loser's insert fails at `db.commit()`, and that commit sits outside the try/except in the snippet [13], so the loser raises instead of replaying [22]. Catch the constraint violation, re-read the row, and fall into the replay branch.
Where the key comes from is the part that costs you a client release. The rule in the post is one key per user action, resent on every retry [4]. The generator therefore sits above the retry loop, not inside it. A key minted per HTTP attempt is just a request id, and it will happily store three rows for three attempts at one payment.
Sizing is tight but adequate. The example key is a canonical UUID [19], which is 32 hex digits plus four hyphens, so 36 characters against a 64-character column, leaving 28 spare [20]. Fine for raw UUIDs. Thin if someone later prefixes a tenant id or composes the key from user id plus cart version.
The two snippets do not agree on how to fingerprint the body. FastAPI hashes `json.dumps(body, sort_keys=True)` with sha256 [15]; Flask compares the built-in `hash()` of the parsed dict [16]. Only the sha256 form is stable across worker processes and restarts, which is exactly the condition you are in when the retry lands on a different pod from the original. `sort_keys` earns its place for the same reason: two encoders that order keys differently would otherwise produce two digests for one request.
The most durable line in the whole post is the one marked optional: `CREATE UNIQUE INDEX orders_idempotency_key_idx ON orders (idempotency_key)` [8]. The keys table is a cache when it lives in Redis under a 24 to 72 hour TTL [17]. The index is a constraint with no expiry. Flush the cache, repoint at a new instance, lose the TTL argument in a config change, and the index still refuses a second order row for a key it has already seen. I would ship the index first and the replay table second, because the index degrades to a 500 and a support ticket while the missing replay table degrades to a second charge.
Rafi's framing of the stakes is a claim about detection latency: finance notices before engineering does [2]. He says he has debugged production checkout flows [24], and that ordering matches mine. A duplicate charge is visible in the gateway's own records within a day. It is visible in your orders table only if something is counting.
Ranked by verification strength, evidence, and original report placement.
The dev.to post by Umair Rafi opens with a user double-tapping Pay, the network retrying, and the API creating two orders for one charge.
The post states that POST is not idempotent, and that handling payments without an idempotency strategy will eventually ship a bug that finance notices before engineering does.
Without protection, a retried POST /orders leaves either two orders, or one order and one orphan charge.
The pattern is that the client generates a unique key once per user action and sends it on every retry in an Idempotency-Key header.
Server rules given: the first request with key K is processed normally and the mapping K to response is stored; a duplicate request with K returns the stored response and does not re-charge.
The post credits Stripe with popularizing the Idempotency-Key header and says the same pattern can be implemented on any checkout API.
Distinct publishers with included, body-backed reporting in this cluster.
dev.to
1 article · August 29, 2026
Follow any of these and your For You feed starts watching them — no settings page required.
build
Edge KV puts the permission check an hour behind the Postgres row1 distinct publisher
build
A RAG stack lived seven hours before a hosted embedding endpoint returned 4041 distinct publisher
build
isinstance(amount, (int, float)) is not a number check: NaN walks through a withdrawal guard1 distinct publisher
build
Four test runs, a week's API budget: the seam that gets the model out of CI1 distinct publisher
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.
One author, but the code is fully on the page
Everything rests on a single dev.to walkthrough by Umair Rafi, with no second publisher and no independent reading. What lifts it above the usual single-source floor is that the material is checkable in place: the DDL, both handlers and the gateway call are quoted in full, so the failure modes can be read straight off the listing rather than taken on faith. What keeps it from going higher is that nothing is demonstrated — no test, no trace, no concurrent run — and the author's production credential is asserted, not shown.
No usage on record
Nobody's deployment is described here. Stripe is credited with popularizing the header and Redis and Postgres are named as production stores, but that is a menu, not evidence of use — there is no team, traffic figure, incident or release anywhere in the reporting. Scoring reach from a tutorial's recommendations would be inventing it.
The comment promises more than the listing delivers
"Reserve the key (status: processing) — prevents duplicate charges under concurrency" is true about the narrow thing readers fear most: the card is not charged twice. The broader implication a reader will take — a duplicate always gets the first response back — is not what the code does. While the row still reads "processing" the replay branch hands back fields nobody has written, and when two first requests race, the loser hits the primary key on a commit sitting outside the try/except and errors instead of replaying. Confident framing, running one step ahead of the sample.
Attention, not vendor money
The pressures here are the ordinary ones of practitioner publishing, and they are visible rather than hidden: standing is established with production war stories in the third paragraph, and a companion Medium post is linked from the top, so audience is part of the payoff. No product is being sold, no sponsor named, and Stripe is credited for the header with no relationship claimed in either direction. Worth knowing when reading the confident comment beside the reservation; not a reason to distrust the SQL.
Sure what it says, unsure how it survives traffic
Because both handlers are quoted in full, we can be firm about what this post recommends and what its code would actually do on the two paths that matter. We are much less sure the design holds up in a real retry storm: one author, one publisher, no test output, no operator confirming they run it, and a Flask sample whose body fingerprint does not match the FastAPI one it sits beside.