
Error Handling
Part of Idempotency and duplicate prevention
Testing duplicate prevention before production
Test duplicate prevention with lost responses, concurrent delivery, changed inputs and legitimate new actions.
Test duplicate prevention by causing retries where the result is uncertain, then check the downstream business records. A green workflow status is insufficient if two invoices, orders or messages were produced. Before each test, define one protected effect and its expected count.
Set up an observable action
Use a non-production environment with a realistic downstream system, or a test endpoint that records requests and resulting objects. Give each test action a stable business ID, and record the initial state so a duplicate is distinguishable from an object left by an earlier test. Avoid using live customer data or sending real customer communications.
Specify what the system should do with a repeated request: return the original result, report an already-completed state, or halt for conflict. The exact response may vary, but the number of intended side effects should not increase after a repeat. Azure's messaging documentation and AWS Lambda guidance both describe the possibility of repeated processing, so retries should be a normal test path.
Exercise the failure windows
Run these cases separately:
- Repeat after success.Deliver the same event again and confirm no additional effect appears.
- Lost response.Let the downstream create call succeed, interrupt the workflow before it records success, then retry.
- Concurrent delivery.Start two workers on the same action and inspect the resulting objects.
- Changed input with the same key.Confirm the system detects a conflict instead of silently using the old result.
- Genuine new action.Send a second order or new billing period with similar details and confirm it is accepted.
The lost-response case is especially revealing: a local flag may still say “pending” while the external invoice already exists. The retry must find or safely recreate the result through the downstream system's idempotency contract. Stripe's documented key behaviour illustrates one possible contract, but other APIs need their own checks.
Inspect more than the log
For each case, compare the event ledger, workflow status and downstream record count. Check that both workers or retries point to the same result where appropriate. If one attempt was skipped, confirm the reason is visible to an operator. Verify that a key retained for the intended retry window will still be recognised after the workflow restarts.
Capture the observed output beside the expected output and rerun any failing case after a fix. Do not call an exercise “passed” merely because the automation did not throw an error. The final criterion is whether the business state matches the defined action: one effect for one intended action, and another effect when the user genuinely initiates a new one.
![How to choose the right idempotency key for an event: Keep the same idempotency key for retries of one action; use a new key for a new action.; Write: one [effect] for each [identity], e.g. one standard invoice per approved order.; Check stability, uniqueness, scope and lifetime; include tenant ID for tenant-scoped IDs. Choosing an idempotency key for an event](/covers/choosing-an-idempotency-key-for-an-event-640.webp?v=39704930)


