
You set up a simple automation: when a payment comes through Stripe, send a welcome email and add the customer to your CRM. It works perfectly in testing. Then, three weeks later, a customer emails asking why they received two welcome messages six minutes apart.
The culprit isn’t your logic. It’s webhook retries—and the fact that most workflow automation platforms don’t deduplicate them by default.
How webhook retries create duplicate triggers
When a service like Stripe, Gumroad, or your payment processor sends a webhook to your automation platform, it expects a 200 response within a few seconds. If your automation platform is slow to respond—maybe it’s processing a heavy task, or there’s a temporary network blip—the webhook times out.
The sending service assumes the webhook failed and retries. Stripe retries up to three times over the next few hours. Gumroad retries up to 16 times over three days. PayPal retries for 96 hours.
Here’s the problem: most automation platforms treat each retry as a brand-new event. If your workflow triggers on “payment received,” it fires once for the original webhook and again for each retry—even though it’s the same transaction.
The result: duplicate emails, double CRM entries, redundant Slack messages, or—worst case—charging a customer twice if your automation triggers a secondary transaction.
Which platforms deduplicate and which don’t
Zapier deduplicates webhooks automatically if the incoming payload includes an id field at the root level. If the sender nests the ID deeper in the JSON structure, or names it something else, Zapier won’t catch it.
Make (formerly Integromat) offers a “webhooks with deduplication” module, but it’s a separate option—not the default webhook trigger. If you use the standard “Custom Webhook” trigger, retries fire as new events.
n8n requires you to manually configure deduplication using an “If” node and a lookup table or database check. There’s no built-in retry filter.
Pipedream deduplicates based on event ID if you explicitly configure it in the trigger settings—but the default behavior is to process every incoming request.
How to stop duplicates without breaking valid retries
The safest approach is to add a deduplication step as the second action in every webhook-triggered workflow. Here’s the pattern that works across platforms:
- Extract a unique identifier from the webhook payload—usually
id,event_id,transaction_id, ororder_id. - Check if that ID exists in a simple lookup table. This can be a Google Sheet, an Airtable base, or a lightweight database like Supabase. If you’re on n8n, use the built-in “Set” node with a memory store for short-lived deduplication.
- Stop the workflow if the ID already exists. If it doesn’t, log it and continue.
This adds one extra step and about 200 milliseconds of latency, but it eliminates duplicate triggers entirely—even if the sending platform retries a dozen times.
One edge case to watch: if your workflow takes longer than the webhook timeout window (usually 10–30 seconds), the sender will retry while your first run is still processing. In that scenario, your deduplication check might not catch the duplicate because the first run hasn’t logged the ID yet. The fix is to log the ID before any slow tasks run—write it to your lookup table as the very first action, then proceed with the rest of your workflow.
When retries are actually useful
Webhook retries exist for a reason: they catch legitimate failures. If your automation platform goes down for maintenance, or your API rate limit is hit, a retry can save you from missing a critical event.
The trick is distinguishing between a retry of a failed event (which you want) and a retry of an event that already succeeded (which you don’t). The deduplication pattern above handles both: if the first attempt failed and never logged the ID, the retry will process normally. If the first attempt succeeded, the retry stops at the lookup step.
One more thing: some platforms let you configure webhook response behavior. If your automation platform supports it, set your webhook endpoint to return a 200 response immediately, then process the payload asynchronously. This minimizes timeouts and reduces unnecessary retries in the first place.
Want more automation teardowns like this? Subscribe to One Two Three Send and get one operator-focused article every morning—no fluff, just the mechanics that matter.
