---
title: "Stop duplicate webhook deliveries running Flow twice"
description: "Name the header carrying your sender's unique event id and repeat deliveries within 24 hours are accepted but not re-sent to Shopify Flow."
canonical: "https://docs.workflow-webhooks.app/duplicate-delivery"
---

# Duplicate delivery protection

Most systems retry a webhook when they do not get a fast reply. If the first attempt
actually arrived, your Shopify Flow workflow runs twice for one event - a second email, a
duplicate tag, a repeated order note.

Duplicate delivery protection stops that. It is **off by default** on every webhook.

## How it works

If your sending system includes a unique id per event, name the header that carries it
under **Advanced Settings -> Duplicate delivery**.

| | Behaviour |
| --- | --- |
| First request with a given id | Processed normally, fires Flow |
| Repeat within **24 hours** | `200 OK` with `duplicate: true` - **not** sent to Flow |
| A different id | Processed normally |
| Request without that header | Processed normally |
| Field left empty | Every request processed - nothing changes |

Common header names: `Event-Id`, `Idempotency-Key`, `X-Request-Id`. Matching is
case-insensitive.

```bash
# Same Event-Id twice - the second is accepted but not re-sent to Flow
curl -X POST https://your-app-url/webhook/ab12cd34 \
  -H "Content-Type: application/json" \
  -H "X-Api-Key: your-token" \
  -H "Event-Id: evt_12345" \
  -d '{"orderId":"1001"}'
```

## Why the repeat still returns 200

A non-2xx response is exactly what makes a sender retry harder. Answering `200` tells it
the event is safely handled, so it stops - while `duplicate: true` in the body lets you
distinguish the two outcomes if you log responses.

## Where duplicates show up

A suppressed duplicate:

- creates **no invocation-history entry**, so it does not count toward your plan
- **does** appear in the **Live Request Inspector** while you are editing the webhook,
  labelled as a suppressed duplicate

That combination is deliberate: your history and quota stay clean, but a call never looks
like it silently vanished.

## Picking the right header

The id must be **stable across the retry and unique per event** - that is the whole
mechanism.

Good: an event id or idempotency key generated once by the sender when the event happens.

Bad, and rejected at save time: proxy-controlled headers such as `X-Forwarded-For`, `CF-*`
or `X-Signature`. Their value changes per request or per hop, so they would never match a
repeat - failing silently instead of loudly.

> [!NOTE]
> If your sender does not send any unique id, there is nothing to match on. Most platforms
> can add a custom header - in n8n, Make and Zapier it is one line in the HTTP request step.

## If the dedup store is briefly unavailable

The delivery is processed rather than dropped. A duplicate workflow run is a much smaller
problem than a lost event, so the feature fails open by design.
