My automation ran twice and created two records. How do I stop that?
By making the operation idempotent, which means running it a second time produces the same result as running it once. That is a design property, not a setting you switch on. Until you build it in, every retry, every duplicate webhook and every nervous manual re-run is a chance to create a second copy of something.
This is the failure I see most often in automations that are otherwise well built. The logic is correct, the connections work, and then a timeout causes a retry nobody saw, and now there are two deals in the CRM with slightly different timestamps. Nothing errored. The system did exactly what it was told, twice.
So the question worth answering is what makes an operation safe to repeat, and where you get that property for free.
What does idempotent actually mean here?
It means the second identical request changes nothing. Setting a field to a value is naturally idempotent, because setting it again leaves the same value. Appending a row is not, because appending again gives you two rows. Most automation steps are one of those two shapes, and knowing which is most of the work.
The distinction maps onto the operations you already use. Reads are safe. Updates to a specific record by its identifier are usually safe. Creates are the dangerous ones, and so is anything that sends, charges, or notifies, because those have effects outside your database that you cannot undo by setting a field back.
Once you see it that way, the design rule follows. For every step that is not naturally idempotent, you need either a key that lets the receiving system recognize a repeat, or a check that stops you making the call at all.
How do mature APIs solve this?
With an idempotency key, and Stripe's documentation is the clearest explanation of the pattern I know. Its API docs state that the API supports idempotency for safely retrying requests without accidentally performing the same operation twice, and that you supply a key when creating or updating an object.
The mechanism is worth understanding rather than copying blindly. Stripe's docs say its idempotency works by saving the status code and body of the first request made for a given key, regardless of whether that request succeeded or failed, and that subsequent requests with the same key return the same result, including server errors. So a retry replays the original outcome rather than redoing the work.
The docs are also specific about the key itself. They suggest V4 UUIDs or another random string with enough entropy to avoid collisions, state that keys are up to 255 characters long, and warn against using sensitive data such as email addresses or personal identifiers as keys.
What happens if you reuse a key carelessly?
You either get the old answer when you wanted a new one, or an error. Stripe's documentation says its idempotency layer compares incoming parameters against those of the original request and errors if they are not the same, specifically to prevent accidental misuse. That is a feature, and it will surprise you the first time it fires.
Keys also do not live forever. Stripe's docs state that keys can be removed from the system automatically once they are at least 24 hours old, and that a new request is generated if a key is reused after the original has been pruned. So a key is protection against a retry, not a permanent record of what you have done.
Which means the key is not your audit trail. If you need to know whether something happened last week, that belongs in your own storage. I keep a record of what the automation did on my side precisely because I do not want to depend on another system's retention window.
Which requests even need a key?
Only the ones that change something. Stripe's docs put this plainly: all POST requests accept idempotency keys, and you should not send them in GET and DELETE requests because it has no effect, since those are idempotent by definition. That sentence is a useful mental model well beyond Stripe.
A read cannot duplicate anything, so protecting it is wasted effort. A delete of a specific record is already safe to repeat, because the second delete finds nothing to remove. The risk concentrates almost entirely in creation and in side effects, which is a much smaller surface than people assume when they start worrying about this.
This is also why I resist adding retry protection everywhere. Identify the two or three steps in your flow that create or send, protect those properly, and leave the rest alone. A system with protection in the right two places is more reliable than one with half-measures in twelve.
What if the tool you use has no idempotency key?
Then you build the check yourself, on your side, before the call. The pattern is to compute a deterministic identifier for the thing you are about to create, look it up first, and only create if it is absent. The identifier must come from the input, not from the moment of running.
An identifier derived from the run time changes on every attempt and protects nothing. For content automations I use the slug for this, because a slug is unique by definition in most systems and is derived from the article rather than from the run. Before creating anything I query for that exact slug, and if it exists, the run stops and tells me rather than creating a near-duplicate with a suffix appended.
Whether your flow runs through Zapier, Make, n8n or a script, the principle does not change, though what each platform offers for deduplication differs and you should read their documentation rather than assume. I wrote about the point where this pushes you out of a visual tool in when to move an automation from Zapier to code.
Do platforms expose this directly?
Some do, and it is worth looking before you build your own. While working inside Webflow's own tooling I noticed that its app management actions accept an optional idempotency key, documented there as a stable key of up to 255 characters used for best-effort deduplication of exact retries of the same operation. That phrase, best-effort, is doing honest work.
Best-effort means it reduces the chance of a duplicate without promising to eliminate it, which is the right expectation to hold for any such feature. I would not design a flow that becomes incorrect if deduplication misses once. I would design one where a duplicate is caught by a check I own.
The practical reading is that vendor-side keys are a useful second layer rather than your primary defense. Your own existence check runs first because you control it, and the key catches the case where your check and the create were separated by a crash.
Where do duplicates actually come from?
Four sources, in rough order of frequency. A timeout where the request succeeded but the response never arrived. A webhook delivered more than once, which most senders explicitly allow for. A person re-running a flow because it looked stuck. And two scheduled runs overlapping because one took longer than the gap between them.
That last one is the quietest and the most embarrassing to diagnose. A job scheduled every fifteen minutes that occasionally takes twenty will eventually run concurrently with itself, and both copies will do the same work. The fix is a lock or a guard on overlapping runs, not a shorter schedule.
The re-run by a human deserves sympathy rather than blame. If your automation gives no clear signal about whether it finished, somebody will eventually run it again to be sure, and they are behaving reasonably. Making the state visible is part of making the system safe, which is the same argument as monitoring for silent failures.
How do you test that it worked?
Run it twice on purpose. That is the whole test and almost nobody does it. Trigger the flow, let it complete, then trigger it again with identical input and confirm the result is one record rather than two. If you cannot do that safely in a test environment, you do not yet know whether your automation is safe.
Then test the harder case, which is interrupting it halfway. Kill the run after the create but before whatever marks it as done, then run it again. That sequence is what actually happens in production, and it finds the gap between doing the work and recording that the work was done.
Keep both tests as something you repeat after changes, not as a one-time exercise. An idempotency guarantee is easy to break by adding a step, and the break is invisible until the day a connection times out. That is precisely when you least want to find out.
What should you do next?
Open your most important automation and list every step that creates, sends or charges. For each one, write down what would happen if it ran twice with the same input. Any answer other than nothing is a gap, and that list is your work queue in priority order.
Then fix the top one with a deterministic existence check before the call, using an identifier derived from the content rather than the run. Across the automations I keep in production, including an Airtable and WhaleSync pipeline for Ajust and a HubSpot flow through Zapier for Kismet Health, that single pattern has prevented more problems than any monitoring I have added. Choosing the platform matters less than this, as I argued in comparing Make, Zapier and n8n.
If you have an automation quietly creating duplicates and want help finding where, reach out. It is usually one step, and it is usually the one nobody suspected.
Get found, cited and the back office automated
Let's make your site the source AI engines quote and wire up the systems behind it.
Read more blogs
Let's get your website found and cited by AI
Tell me what you're working on, whether AI search is skipping your product, your back office is buried in manual work, or you need a build that does both.