The screen hasn’t changed. The button still says Create Label. So somebody presses it again.
That’s a fairly normal human response. We should probably design for it before writing an error message that sounds disappointed in everyone.
Duplicate actions also happen without a second click. Connections drop, background jobs retry and other systems send notifications again. A dependable integration needs a way to recognize when another attempt represents the same intended work.
Developers call that idempotency. The name sounds like something your doctor would ask you to spell. The idea is much kinder: repeating an operation should not keep repeating its business effects.
Trace the uncertain handoff
Let’s work through a hypothetical dispatch system purchasing a shipping label. The carrier creates it, but the connection drops before the dispatch system receives confirmation.
There may now be a perfectly valid label. The local application simply doesn’t know. That distinction is where the interesting work begins.
Give the operation an identity that survives a retry
Assign an identifier to the intended label request and keep it for further attempts at that same request. If the receiving service supports an appropriate idempotency mechanism, it can recognize the repeat and handle it according to its documented contract.
Brandur Leach’s explanation of Stripe’s API design describes why this matters when the outcome of a network request is uncertain.2 No response does not necessarily mean no action.
Generating a new identifier for every retry defeats that protection. It’s like arriving at the same restaurant reservation desk wearing a fresh disguise each time, then wondering why several tables have been booked.
Check what the external provider actually supports, including how long it remembers request identities and what it does if the same identity arrives with different details. Don’t borrow guarantees from another provider’s documentation because the APIs look related.
Our integrations and AI team builds connections that keep records in step.

Record completion where the business change happens
Incoming notifications have the same uncertainty in reverse. An application may process a message and then fail before acknowledging it. The sender tries again, reasonably enough.
Derek Comartin’s idempotent-consumer approach records a message identifier alongside its business change in the same database transaction.3 A uniqueness constraint helps prevent concurrent copies from both committing the change.
The transaction matters. Recording Done before the work succeeds can lose the work. Recording it separately afterward can leave a gap in which a repeated message performs it again.
This protects the local database change when implemented correctly. It doesn’t automatically protect another purchase or email sent through an external system. That side effect needs its own strategy. A database transaction is useful, but it has not been elected mayor of the internet.
Keep a retry distinct from a new request
Staff may legitimately need a replacement label. That is different from asking whether the first request succeeded.
The interface should make those intentions clear. Retry this request should preserve its identity. Create a new label should represent a deliberate new operation, with any relevant warning or confirmation.
Show the last known status and external reference where available. An unresolved request should look unresolved. Calling it Failed prematurely can encourage someone to start a second purchase when the first is still being investigated.
If this was useful, try Your endpoint is idempotent. The payment still needs protection.
Practice the recovery before it’s needed
Svix’s 2023 webhook work provides useful context for the delivery and retry behavior integrations need to handle.1 For your own workflow, a demonstration should include the uncomfortable cases, not just a cheerful request that returns immediately.
Try losing the response after the remote operation succeeds. Send the same notification twice. Restart processing around the local save. Check whether the resulting records still describe one intended action.
Automatic retries also need restraint. Space them appropriately, stop treating a persistent provider failure as an invitation to try harder indefinitely and give someone a visible exception to investigate.
Zulu Shack recommends agreeing on that recovery process as part of the integration’s scope. Otherwise, it has a habit of becoming a support procedure invented during the first busy afternoon.
Nobody needs a staff screen full of transport jargon. They need to know what happened, what remains uncertain and which action is safe to take next. A useful activity history can provide that without making them read server logs.
You can still disable the button after the first click. That helps communicate progress and prevents some accidental repeats. Just don’t make the button the only adult responsible for duplicate processing.
The real test is whether the system lets a person recover from a missing answer without quietly buying the same thing twice. If it can do that, pressing the button again becomes an ordinary event instead of the opening scene of a reconciliation exercise.