Stop duplicate requests from doing the same paid work twice

Prevent repeat clicks and retries from creating duplicate jobs, emails or charges. Learn where idempotency belongs and what to test in a paid AI product.

One saved report on a laptop beside two request slips and a single finished paper document.

A customer clicking twice should not accidentally buy, send or generate twice. Preventing that requires a stable identity for their intention and protection at every place where the product creates a lasting effect.

Decide what one customer action means

A customer presses Generate, sees no response and presses again. Your product now has two requests. They may represent one intended report, not two purchases. The same ambiguity appears when a phone reconnects, an integration retries or a worker starts over after a timeout.

Write down the action you want to protect: generate one report from this submission, send this campaign once, or create this purchase. The boundary matters. A customer may intentionally request a second report from the same source. Deduplicating every matching input forever would prevent useful repeat work.

Idempotency means that retrying the same operation does not add another side effect. It does not mean the product can infer the customer's intention from similar-looking requests. The operation needs an identity that survives a retry but changes when the customer deliberately starts something new.

Keep the same reference across retries

Create a non-sensitive operation reference for the intended action and reuse it when the client retries. Scope it to the authenticated customer or workspace and the relevant operation. The server must check ownership rather than trusting an account name supplied in the request.

Store enough information to reject a retry that reuses the reference with different input. Decide how long the reference remains valid and what happens to late retries. A retention period should reflect your actual recovery window; an expired record cannot protect a request that arrives after it has been discarded.

The interface should show that an action is underway and disable accidental repeat clicks where appropriate. That is helpful feedback, but it cannot protect against another tab, a network retry or a direct API request. The server remains responsible for the rule.

Protect the database against simultaneous requests

Checking whether a job exists and then inserting it leaves a race: two requests can both see nothing and both create work. Ask for a database-enforced uniqueness rule on the operation's account scope and non-null reference. PostgreSQL supports composite unique constraints for this purpose.

Recording the claim and creating the associated local work should succeed or fail together in a transaction. If an existing operation is still running, return its current status. If it has finished, return the recorded result when the caller is entitled to see it. An ordinary duplicate should not force the customer to guess whether anything happened.

Do not confuse a durable operation claim with holding a database transaction open during a long AI call. Long work needs its own persisted state and worker coordination. The transaction protects the local decision to create that work, not every later step performed by another service.

Check the boundary with the outside service

A transaction in your database cannot atomically include an email provider, model API or payment service. The outside service may accept the request and then your connection may fail. Your system needs to distinguish confirmed failure from an outcome it has not yet learned.

Where the provider supports idempotency, pass a stable reference according to its exact contract. Stripe is one example: a reused key returns the recorded result for an executed request, and that result can include an error. Its retention and parameter rules matter. Do not assume another API behaves the same way.

Where a provider offers neither idempotency nor result retrieval, exactly one external effect may not be something you can guarantee. Choose whether to pause for reconciliation, accept a bounded duplicate risk or change the integration. Record uncertainty visibly for operations staff instead of presenting every timeout as safe to retry.

Protect usage records and follow-up messages too

One saved report can still produce two usage deductions if each worker records consumption separately. Tie the customer's usage event to the protected operation, and make repeated recording safe. Decide what happens when a provider consumes resources but the customer does not receive a usable result.

Follow-up work needs the same attention. Saving a result, emailing its owner and exporting it to another system are separate side effects. Use a persisted record of pending follow-up actions and reconcile unfinished ones. A notification failure should not cause the expensive report to be generated again.

If the customer intentionally chooses Regenerate, show what will happen and create a new operation. Keeping retry recovery separate from new product work makes support, billing and customer expectations easier to explain.

Run a test that includes overlap and uncertainty

In a disposable environment, submit the same operation reference concurrently from two clients. Repeat it after completion. Reuse it with changed input. Send the same reference from another account. Interrupt the connection after the outside service accepts the action, then retry.

Count lasting effects rather than successful HTTP responses: reports created, messages sent, usage deducted and provider operations started. Check that the customer can retrieve a coherent result and that another account cannot discover it. Include a delayed retry near the end of the retention window.

Ask the engineer to explain the uncertain case and demonstrate recovery. A disabled button and a happy-path demo are not enough evidence. Once customers rely on the action, dependable retry behavior protects both their trust and the product's operating costs.

Frequently asked questions

Is disabling a button enough to prevent duplicate requests?

No. It helps users avoid accidental clicks, but another tab, network retry or direct request can still repeat the operation. The server needs a stable operation reference and a database-enforced claim.

Should identical AI prompts always reuse the same result?

Not necessarily. A customer may intentionally ask for a new result from the same input. Reuse the operation reference for retries of one intention and create a new reference for deliberate regeneration.

Can a database transaction prevent duplicate external API charges?

It protects local database changes, not a remote provider's operation. Use that provider's documented idempotency or retrieval behavior where available, and define a recovery policy for uncertain outcomes.

Sources & further reading

Is your product repeating work customers only asked for once?

Bring one action that duplicates, what customers see and where the cost appears. We can help trace and protect the whole path. We help businesses with paying customers turn those questions into focused engineering work.

Discuss duplicate work

Not ready for a conversation?
Put the situation into a scaling brief