> For the complete documentation index, see [llms.txt](https://support.swisspay.ai/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://support.swisspay.ai/api-reference/idempotency.md).

# Idempotency

The `Idempotency-Key` header lets you safely retry a request without risk of double-processing.

## When is it required, when is it recommended

| Endpoint                 | Header                                         |
| ------------------------ | ---------------------------------------------- |
| `POST /api/v1/payments`  | **Required**                                   |
| `POST /api/v1/customers` | Recommended (especially for unattended writes) |
| Everything else (`GET`)  | Not used                                       |

If you call `POST /payments` without an `Idempotency-Key`, you'll get:

```
HTTP/1.1 400 Bad Request
{
  "error": {
    "code": "missing_idempotency_key",
    "message": "Idempotency-Key header is required on POST /api/v1/payments"
  }
}
```

## How it works

```
Idempotency-Key: 9c6a5a52-1aa3-4f6f-9b1d-7d8a5d4e3a2b
```

* Pick a stable, unique key per logical attempt — a fresh UUID, or a structured key like `order_1234:attempt_1`.
* Send the same key + same body to retry safely. We return the **original response** — your retry never creates a duplicate payment.
* Reuse the same key with a **different body** and we return:

```
HTTP/1.1 409 Conflict
{
  "error": {
    "code": "key_reused",
    "message": "Idempotency-Key already used with a different request body"
  }
}
```

The fix is to pick a new key — the new request is genuinely a different operation.

## When to pick what

| Situation                               | Suggested key                                                                                                |
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| One-shot charge from a fresh checkout   | A fresh UUID: `uuidgen` or `crypto.randomUUID()`.                                                            |
| Charge tied to an order in your system  | `order_<id>:attempt_<n>` where you bump `n` only when the request body actually differs.                     |
| Scheduled charge                        | `subscription_<id>:cycle_<yyyymmdd>` — stable across retries within the same cycle, different across cycles. |
| Customer creation tied to a user signup | `signup_<user_id>` — guarantees that double-clicks don't create two customers.                               |

## Key lifetime

* We retain the idempotency record for **24 hours**.
* Within that window, replays return the original response (with the exact same fields, timestamps, and IDs).
* After 24 hours, the same key submitted again is treated as a new request.

## What it doesn't help with

* Idempotency-Key isn't a duplicate-charge prevention mechanism beyond the 24-hour window — your own application should still avoid charging the same shopper twice for the same logical order.
* It doesn't change the outcome of a declined card — a `failed` response is still the original response, and replaying the key returns the same failure.
