A practical business guide

How to prevent duplicate records and actions in business automation

A practical control plan for repeated events, failed requests, and manual reruns. Includes a fictional handoff example and checks for uncertain outcomes.

Download the worksheet ↧

Give each business action a stable identity. Save its state before work starts. Reuse that identity on retries. Then check the destination before repeating an action whose result is unknown. A filter that removes repeated rows in one run cannot provide all these controls.

This guide applies when an automation creates invoices, sends messages, creates tasks, or moves records between systems. It also applies when two triggers can start the same work. It does not replace a customer-data cleanup plan. Two records with similar names can represent different people.

First, define what counts as a duplicate

Use three separate questions. They need different identifiers.

Three kinds of repeated work
QuestionExampleUseful identifier
Did this delivery arrive before?The same payment event arrives twice.Source account plus event ID.
Did this business action happen before?Two different events both request the same welcome task.Business record plus action and purpose.
Is this the same entity?Two contact records may describe the same buyer.A documented identity rule, with review for uncertain matches.

An event is a notification from another system. Its stable event ID can identify one notification, even when it arrives again. It does not necessarily identify the business action. A new workflow execution ID also cannot identify a retry of an earlier action. It changes every time the workflow runs.

Write one sentence before you build: “For each confirmed order, create one fulfillment task in this workspace.” Define what happens after a cancellation, replacement order, or corrected submission. Do not use a time window alone to decide that two actions are the same.

Build the control plan in six steps

  1. Choose an action key. Combine the workspace or account, the source record, and the intended action. Keep personal information out of the key. Use the same key when retrying the same request. Give a genuinely new approved action a new key.
  2. Create a durable action record. Save the key, current state, destination record ID, attempt count, and last error. Durable means the record remains available after a restart. Store only the source fields you need.
  3. Claim the action in one database operation. Enforce one action record per key. A separate “find first, then create” sequence can let two simultaneous runs both pass the check. A database uniqueness constraint can enforce this rule. It cannot, by itself, prevent a remote service from taking an action twice.
  4. Use the destination's retry control. If its application programming interface (API) accepts an idempotency key, reuse the original key with the same parameters. Idempotency means repeating the request does not repeat its completed effect. Check that provider's rules and retention period. If the destination has no such control, use its enforced unique external reference where available.
  5. Save the confirmed result. Record the destination ID and mark the action completed only after confirmation. Keep separate states for ready, processing, completed, failed, and outcome unknown.
  6. Give recovery an owner. Send uncertain or conflicting cases to a review queue. Show the source record, expected action, latest attempt, and destination evidence. Add a controlled retry action. Do not require staff to restart the whole workflow.

PostgreSQL documents uniqueness constraints for one field or a combination of fields. Key fields also need a rule for missing values. For example, a normal PostgreSQL unique constraint can permit multiple null values. Reject a missing action key before processing.

Stripe's idempotency documentation is a useful provider-specific example. Its API v1 permits key removal after at least 24 hours. A later replay can therefore require your own action history. Set a maximum age for an attempt. An older attempt must not send automatically after the destination retry control expires. Other providers and API versions can behave differently.

Need help applying these controls to an existing workflow? See ZenOut's workflow automation service. A useful starting scope is one action and its failure cases.

A fictional example: one paid order, one task

This example uses fictional records. It is not a customer result. A form system and a payment system both notify an operations team about order ORD-1042. The team needs one fulfillment task after payment is confirmed.

Expected behavior for the fictional order
InputDecisionResult
Payment event EV-81 arrives.Claim team-a:ORD-1042:fulfillment.Create task T-208. Save that ID.
EV-81 arrives again.The delivery and action are already recorded.Return the stored result. Create no task.
A different event confirms the same payment.The event is new. The fulfillment action is completed.Record the event. Create no task.
A staff member clicks retry.Read the saved action record.Show T-208 and its current status.
An approved replacement order arrives as ORD-1043.This is a different business action.Create a separate fulfillment task.

The action key includes the workspace. This prevents two teams that use the same order number from blocking each other. A deliberate second task for an order needs a documented purpose, such as a replacement shipment. Do not bypass the control by generating a random new key on every retry.

Handle uncertain results before you retry

The request timed out after the destination acted

A timeout tells you that confirmation failed. It does not prove that the action failed. Keep the outcome unknown. Query the destination using its saved reference or retry with its original supported idempotency key. If neither route can establish the result, require review before sending again.

A worker stopped while the action was processing

A worker is the program or process that performs the action. Use a documented recovery process. A processing record must not block work forever. However, an expired claim does not prove that the external action failed. It also does not stop the original worker from resuming later.

Claim recovery ownership in one database operation. Record a new ownership version. This is a number that increases each time a worker takes over the action. Save a result only when the worker's ownership version still matches. Send a result from an older version to review. Do not let it overwrite the saved state. A unique action record alone cannot stop an old worker from making an external request. A worker can check its ownership and then send the request. Ownership can change between those two steps.

Use a destination control that rejects obsolete ownership, sometimes called fencing. Where applicable, use a supported conditional write that prevents the conflicting change. The HTTP If-Match condition, for example, can require a matching resource version before an update. It does not by itself protect every kind of action.

For external creation, reuse the destination's supported idempotency key or enforced unique reference. If the destination cannot enforce the needed protection, hold automatic takeover. Reconcile the result and establish that the old worker cannot still act before authorizing a retry.

The same key arrives with different data

Hold the request as a conflict. Compare the relevant fields with the saved request. A corrected invoice amount is a change to an action, not permission to silently repeat the original invoice creation.

An older event arrives after a newer event

Read current source state or use a reliable source version before updating a record. Do not assume delivery order equals business order. Stripe documents duplicate and unordered event delivery. Your event source needs its own check.

Only part of a workflow completed

Track separate action keys for separate effects. If the task exists but the email failed, retry the email step. Do not create the task again. If repeat execution is always the intended behavior, such as a periodic report, include its reporting period in the action identity.

Check behavior with deliberate failures

Run these checks in a test environment. Use destination records you can identify and remove. Count actual external actions, not only successful workflow executions.

  • Send the same event five times. Confirm that one intended external action exists.
  • Start two copies at the same time. Confirm that one action claim succeeds.
  • Pause worker A before its external action. Let worker B claim recovery. Then resume worker A. Count actual external effects. Confirm that supported destination controls prevent a second effect. If those controls are unavailable, confirm that automatic takeover stays on hold for reconciliation. In both cases, confirm that the late result from worker A goes to review. It must not overwrite the saved action record.
  • Stop the worker after the destination succeeds but before local confirmation. Confirm that recovery finds the existing result.
  • Send a different event for the same intended action. Confirm that it creates no second effect.
  • Submit a missing key and a changed payload. Confirm that both cases enter review.
  • Send a valid new order. Confirm that duplicate protection does not block it.
  • Replay an old event after the provider's key-retention period. Confirm that your documented recovery rule still applies.
  • Verify that a named person can resolve failed and unknown actions.

These checks can establish behavior for the tested cases. They do not justify a universal “exactly once” guarantee across unrelated systems.

Copy this action-control checklist

Copy the following block into your workflow specification. Save it as a text file if you need a checklist to share.

Action name:
Source system and account:
Business record ID:
Definition of one intended action:
Stable action key:
Required key fields and missing-value rule:
Durable state store and uniqueness rule:
Destination retry control and retention period:
Destination lookup reference:
Unknown-outcome recovery rule:
Atomic recovery claim and ownership version:
Destination control against obsolete workers:
Manual hold when protection is unavailable:
Separate downstream effects:
Record retention period and reason:
Review owner:
Test evidence and unresolved cases:
Approved by:

Choose the next improvement

If you still need to choose which process to automate, start with the AI implementation guide. If staff manage failures in a workbook, read when a spreadsheet needs an internal tool.

Book a conversation about one repeated action. Bring a redacted event sample, the destination, and one example of a duplicate or uncertain result.

Put the plan to work

Start with one useful change.

Bring the process, the people involved, and a redacted example. A free 30-minute fit call can establish the next step.