User clicked submit once, but two identical order requests appear in the backend logs—which side should you check first?
The user clicked submit only once, but the backend received two identical requests. Most of the time this is not slow network but a missing fallback layer somewhere between the click and the database write. A greyed-out front-end button only blocks rapid taps on the same phone; weak-network retries, payment callback re-sends, and the user going back and clicking again can all bypass it. Based on 2026 project delivery experience, reliable order, payment, and coupon-claim APIs usually implement three layers: client-side debounce, server-side idempotency key, and database unique constraint. Missing any one layer means duplicate or lost orders in the corresponding scenario.
Why one click can produce two requests: the break usually sits in these four segments
A submit request travels through four segments from finger down to database write: client click, network transmission, server processing, and database write. As long as none of the four segments recognizes that "these two requests are actually the same thing," the second one is treated as a new business event. Many teams add a loading state to the button and think the problem is solved, only to find that on a weak network the phone retries automatically and the server still receives two nearly identical requests.
You do not have to guess when troubleshooting. Take one duplicate order and compare the logs; look at the time difference between the two requests, whether the idempotency key or order number is the same, and you can usually locate the break: a millisecond-level time difference often means concurrent pass-through, while a difference of several seconds or more often means a retry or a user's second click. In 2026, a common situation is client-side debounce without server-side idempotency, so a weak-network retry slips through; or server-side idempotency without a database unique key, so concurrency still breaks through.
- Client segment: the click event is not locked, and the user can go back to the previous page and submit again; small change but narrow coverage.
- Network segment: timeout retries, manual user re-sends, and low-level automatic replays; most teams cannot control this segment and can only rely on the server to catch it.
- Server segment: no idempotency check, so two threads can both pass validation at the same time—a common concurrency gap.
- Database layer: no unique constraint or state machine, so the final gate is left wide open.
What each deduplication layer handles: client, server, and database
Based on 2026 delivery habits, write operations such as ordering, payment, coupon claiming, and ticket submission are usually split into three layers instead of expecting one layer to solve everything. The three layers fail in different scenarios: the client layer blocks repeated finger taps, the server layer blocks timeout retries and concurrency, and the database layer provides the final fallback after the earlier layers miss. Each layer only solves its own segment; writing the boundaries clearly keeps the client and server teams from blaming each other.
- Client-side debounce: grey out the button after a click, show loading, and restore it after success or failure. Failure recovery needs a timeout cap, with a typical range of 3–10 seconds, adjusted to the API's actual latency; otherwise, if the network drops, the button stays grey and users think the app is frozen.
- Server-side idempotency key: the client generates a requestId when entering the submit page and sends it with the request; the server deduplicates using "user + business type + idempotency key" and, on a hit, returns the previous processing result instead of directly throwing an error. The validity period depends on the business; an experience range is 10–30 minutes. Too long wastes storage; too short cannot block a user from submitting again after going back.
- Database unique constraint: create a unique index on the serial number, idempotency key, or "user + business + time window," or use a state machine to restrict an order from moving from "pending payment" to "paid" only once. This layer is the final interception; duplicate requests will fail to write, so the API must still catch this error and return a friendly message rather than exposing the database error to the user.
With all three layers in place, the pass line can be defined as: send the same idempotency key 10 times in a row and only one record should exist in the business table. Different idempotency keys that are genuinely duplicate in the business sense are handled by a state machine or end-of-day reconciliation. Before work starts, write the idempotency key's field name, generation timing, and validity period into the API documentation; this reduces inconsistent understanding between the two sides during integration testing.
Checkable comparison: what changes from front-end greying out alone to all three layers
Different projects have different budgets and timelines, so you do not have to do everything at once, but you should know which piece of risk you are cutting.
- Front-end greying out only: small change, typically half a day to one day; it blocks repeated clicks but not weak-network retries or callback re-sends, so the risk falls on the server and later reconciliation.
- Front-end + server-side idempotency key: requires both sides to agree on the field name and generation timing, with an experience range of 1–3 days; covers most retry and concurrency scenarios and is a fairly common implementation level in 2026.
- Add database unique constraint or state machine: one more round of table structure or process adjustment, possibly involving historical dirty data cleanup; broader coverage, but first assess whether existing duplicate data would cause index creation to fail.
- Database unique constraint only: easier for development, but users see a cold error message, and it does not distinguish "duplicate submission" from "real business conflict," increasing customer-service explanation cost.
Trade-off advice: for flows involving money or inventory, implement all three layers; for likes, tracking reports, and other operations that can tolerate duplicates, one front-end debounce plus a quick server-side deduplication is usually enough, leaving the saved time for core flows.
When it applies and when it does not
Three-layer deduplication fits write operations where "one duplicate creates a real cost": ordering, payment, refunds, coupon claiming, flash sales, ticket submission, and balance or points changes. In these scenarios, duplicate submissions directly cause financial loss or manual reconciliation cost, and the investment to add deduplication is usually between half a day and a few days—a cost-effective change.
Scenarios that should not be copied blindly are also clear: pure query APIs, tracking reports, and content browsing are naturally idempotent, and adding an idempotency key only increases storage and complexity. Internal back-office batch imports should use overall idempotency based on "task number + status" rather than locking each record. Conversely, if the business itself allows the same user to place multiple orders for the same product—for example, different specifications or different recipients—then the idempotency key dimension must be broken down to specification or delivery information; simply deduplicating by user would block legitimate orders.
- Suitable: money, inventory, entitlement changes, and submissions that trigger offline fulfillment actions.
- Not suitable: read-only queries, logging and tracking reports, and statistical writes that can tolerate duplicates.
- Use caution: normal ordering that allows duplicates by business rule but also has frequency requirements; the idempotency key dimension and rate limiting need to be designed separately.
Delivery reality: the cost of writing the agreement one step late
One type of on-site constraint is very common: only two or three weeks remain, the client and server are still two separate teams, and the API documentation lists only the fields without saying when the idempotency key is generated or what to do after it expires; during integration testing, each side just gets its own part running and calls it done. The practical fix is to settle, before integration testing, at least the idempotency key's generation timing, deduplication dimensions, and the return message after a duplicate hit, and to add a round of weak-network and concurrency validation. The cost is that if this step is missed and a weak-network retry happens in the first week after launch, a dozen or more duplicate orders may appear, forcing manual refunds and reconciliation; the rework is often much larger than writing it clearly from the start. By experience range, upfront agreement usually takes only half a day to one day, while after-the-fact reconciliation and patches often take weeks.
FAQ
Is greying out the button enough?
No. Greying out only blocks repeated clicks on the same phone; weak-network retries, going back and re-entering, and payment callback re-sends can all bypass it. For APIs involving money or inventory, add at least a server-side idempotency layer.
What value is usually best for generating the idempotency key?
A common practice is for the client to generate a unique random string when entering the submit page, send it with the request, and have the server validate it on write. Do not use "user ID + second-level timestamp"; repeated clicks within the same second will break through.
If a payment callback is re-sent several times, does that count as duplicate submission?
Yes, and it is more common than fast user clicks. Third-party callbacks retry by nature, so the server must make order number or serial number idempotent; duplicate callbacks should only update status, not duplicate shipment or duplicate points.
If duplicate orders have already been created, how are they usually wrapped up?
A common approach is to first pull suspected duplicates by idempotency key or time window, verify the payment transaction, then refund or merge, while adding server-side idempotency and unique constraints to prevent the next batch.
If you do only one thing: add server-side idempotency keys to write APIs such as ordering, payment, and coupon claiming first, then add database unique constraints; front-end greying out can come later as a UX improvement. Also state the applicability boundaries clearly: for pure queries, tracking reports, and statistical writes that can tolerate duplicates, deduplication has limited payoff. Before launch, running one round with a concurrency script and one round in a weak-network scenario is a practical way to bring risk into an acceptable range in 2026.
-
Well-Recognized Custom E-Commerce Mall System DevThe Good Shopping mini-app is natively buil ...
-
Anhui Huixiang Vegetable Garden Agricultural Products Mini Program v2.0 Iteration DevelopmentHuiXiang MiniApp V2.0: Upgraded homepage, n ...
-
Kunshan TrialBook Mini-Program Custom DevelopmentThis project developed an English-only Tria ...
-
Agricultural Products WeChat Mini Program Custom DevelopmentLvran Di enhances agricultural sales via a ...
-
Order success page popped for users, but the event tracking report is short of backend orders—is the client missing events, or is reporting being cut off by the system?
Date: Oct 14, 2026 Read: 8
-
Phone overheats and drops frames after scrolling dozens of screens of product images—is the phone too old, or is the image loading pipeline misaligned?
Date: Oct 13, 2026 Read: 10
-
Order date shows one day earlier overseas: is it really the phone time zone setting?
Date: Oct 12, 2026 Read: 20
-
The photo preview is upright when picked from the album, but lies flat as soon as it's uploaded to the App — is the compression step losing the orientation?
Date: Oct 10, 2026 Read: 23
-
Same app icon looks fine on iOS but on Android becomes a round base with white edges — is the slicing wrong?
Date: Oct 9, 2026 Read: 33




