Idempotency for duplicate prevention: Define a business action like 'create one invoice per order'; Use stable identifiers such as order ID + invoice purpose to detect repeats; Persist status (in progress/succeeded/failed) to avoid race conditions
Image: Workflow Automation Guide

Error Handling

Idempotency and duplicate prevention

Define idempotent workflow actions, stable keys and safe retry behaviour to prevent duplicate business effects.

Idempotency means a repeated operation has no further intended effect after the first successful application. In an automated workflow, it lets you retry a timed-out step without creating another invoice, order or notification. Define explicitly what counts as the same business action; matching payloads alone is rarely enough.

Why retries create duplicates

A caller may send a request, lose the response and retry because it cannot tell whether the first attempt succeeded. A queue consumer may receive the same message again after a failure. Azure's messaging guidance says commands must be delivered at least once, and duplicate processing can cause erroneous transactions, such as duplicate orders or double billing.

This is normal failure handling, not necessarily a defect in the delivery service. The risk arises when a side effect repeats: creating a second invoice, charging twice, sending another confirmation, or moving a record to the next stage twice. The workflow should recognise the earlier action and return its result or safely decline the duplicate.

Azure distinguishes commands, which request an operation, from events, which announce that something has happened. In most cases, consumers should not process a command more than once. Events may have multiple subscribers, each taking independent action, and the producer does not expect an acknowledgement from each consumer. AWS recommends idempotent code that validates events and gracefully handles duplicates.

Define the action to protect

Begin with the side effect, not the event transport. Define the business action you are protecting — for example, creating one invoice for one approved order — and then decide how a retry will be recognised as the same action. A later legitimate correction or adjustment is a different business action; the guide to distinguishing repeated events from legitimate new actions covers this.

The choice and management of the identifier that represents that action is a detailed topic; the guide to choosing an idempotency key for an event covers it. Do not generate a fresh identifier every time a retry runs, because that would make the retry look new.

SituationSuggested identityWhy
Same event delivered twiceProducer's stable event IDRecognises redelivery
Two triggers request one invoiceOrder ID plus invoice purposeProtects the business effect across triggers
Customer places two separate ordersDistinct order IDsAllows both legitimate actions

Make the check and write safe together

A separate 'look up key, then create record' sequence can race: two workers may both see nothing and both act. Use a database uniqueness constraint, a transaction or the downstream API's idempotency feature to resolve competing attempts to one business outcome.

Persist a status that distinguishes in progress, succeeded and failed work. A retry of in-progress work may need to wait or return a pending response; a retry of completed work can return the recorded result.

Where an external API supports idempotency keys, read its exact contract. Stripe, for example, records the status and body once endpoint execution begins and returns that result on later matching requests, including error responses. Validation failures and some concurrent conflicts before execution starts are not saved, so they can be retried. Its key retention and parameter-matching rules matter.

Do not assume another provider uses the same behaviour, or that a provider key protects the workflow's own database writes.

Zepto's API illustrates why contracts cannot be assumed to match: for supported POST actions, an idempotency key is required and expires after 24 hours. A repeated request with the same key during that period returns a 409 Conflict, with a reference to the previously created resource. A request made quickly after another with the same key may instead return 503, with a Retry-After value.

Failure between two effects requires special care. If the workflow crashes after an external effect but before recording success, a retry must be able to find that effect or repeat it safely using the same key. For a worked example involving invoices, see the guide on preventing duplicate invoices after a workflow retry.

Test retries as a normal path

The guide to testing duplicate prevention has a practical pre-production test plan.

Idempotency is not a promise that a message is delivered only once. It is a way to make repeated delivery safe for a defined action, within a defined retention period and across the systems that can perform that action. Document those boundaries so a future workflow edit does not quietly remove the protection.

In this guide

  1. Choosing an idempotency key for an eventChoose a stable idempotency key that recognises retries without blocking a legitimate new event or action.
  2. Preventing duplicate invoices after a workflow retryProtect invoice creation across workflow retries, lost responses and concurrent workers using a stable business identity.
  3. Distinguishing repeated events from legitimate new actionsTell redelivered events from genuinely new actions using both event identity and the identity of the business effect.
  4. Testing duplicate prevention before productionTest duplicate prevention with lost responses, concurrent delivery, changed inputs and legitimate new actions.

More from Error Handling