# Idempotency

> How to retry safely: the Idempotency-Key header on every write, refund keys, and how webhooks and plugin callbacks dedupe.

Source: https://shopfa.st/docs/guides/idempotency

Networks fail after a request reaches us but before you read the answer. Retrying must never refund twice or create
two products.

## The Idempotency-Key header

Send `Idempotency-Key` with any write made with an API key or an OAuth token, or with a
[storefront key](https://shopfa.st/docs/headless/storefront-api#retries). The first answer is kept for 24 hours;
sending the same request with the same key again returns that answer, with `Idempotent-Replayed: true`, and changes
nothing.

```sh
curl -X POST https://api.shopfa.st/v1/store/products \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -H "Idempotency-Key: 6f1b7c2e-launch-pack" \
  -d '{"name":"Launch Pack","type":"service","priceMinor":2500,"compareAtMinor":null,"description":"","delivers":"","status":"draft","lowStockThreshold":0}'
```

- Make the key **once** per intent (a UUID stored with your record), not per HTTP attempt.
- Keys belong to the key or app that sent them (and, on the Storefront API, to the signed-in buyer): another key never
  sees them.
- The same key with a different method, path or body answers `422 idempotency-key-reused`.
- While the first request still runs, a retry answers `409 idempotency-key-in-use` with `Retry-After: 1`.
- Answers that never began (401, 403, 429, 503) are not kept: retry them with the same key.

## Refunds

Refunds also take an `idempotencyKey` in the body: 8 to 80 letters, numbers, dashes or underscores, one per refund
attempt. A repeat with the same key is a no-op that answers the same order.

```sh
curl -X POST https://api.shopfa.st/v1/store/orders/$ORDER/refunds \
  -H "Authorization: Bearer $KEY" -H "Content-Type: application/json" \
  -d '{"amountMinor":1200,"reason":"Duplicate purchase","destination":"original","idempotencyKey":"refund-ord123-1"}'
```

## Without a key

- `PUT` and `PATCH` set state: sending the same body twice leaves the same result.
- `DELETE` of something already gone answers `404`; treat it as done.
- A `POST` that creates (a product, a discount) without `Idempotency-Key` creates twice if you send it twice.

AI tools connected over [MCP](https://shopfa.st/docs/ai/safety) get the same protection through each change tool's `idempotencyKey`.

## Receiving events

- **Webhooks** keep the same `webhook-id` across retries and replays. Store the ids you processed and skip repeats.
- **Plugin callbacks** apply each `event_id` a payment plugin returns once: ShopFast keeps an inbox of processed
  provider events per plugin, so a provider retrying its notification never pays an order twice.
- The **sandbox and real gateways** follow the same rule with their own event ids.
