> ## Documentation Index
> Fetch the complete documentation index at: https://docs.smartsend.io/llms.txt
> Use this file to discover all available pages before exploring further.

# Idempotency

> Safely retry API requests without creating duplicate side effects.

<Warning>
  Idempotency is a new feature only available in the upcoming API that is currently in beta. The behavior described below is subject to change until the API is released in *General Availability*.
</Warning>

Network errors, timeouts, and other transient failures can leave you uncertain whether a request actually reached Smart Send. Idempotency lets you retry such requests safely, without risk of booking the same shipment twice or creating duplicate resources.

## How idempotency works

Idempotency is opt-in. To enable it, include an `Idempotency-Key` header on a write request:

```bash Example theme={null}
Idempotency-Key: order-4521_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
```

When the API receives a key it has not seen before, it processes the request normally and stores the response against that key. If you retry the same API request with the **same key and the same payload** within 24 hours, the API returns the stored response instead of running the operation again. The retry is therefore safe: eg. a shipment is booked once, even if you send the request twice.

Requests sent **without** an `Idempotency-Key` header are not deduplicated. Each one is processed independently.

<Note>
  Idempotency applies to write operations (`POST` and `DELETE`). Read operations (`GET`) are already safe to retry, so they ignore the header. When a stored response is replayed, the API includes an `Idempotency-Replayed: true` response header so you can tell a replay apart from a freshly executed request.
</Note>

## Creating an idempotency key

The key is a string that uniquely identifies a single logical operation. It must meet these requirements:

| Requirement        | Value                                               |
| ------------------ | --------------------------------------------------- |
| Length             | 16–128 characters                                   |
| Allowed characters | Letters, digits, `.`, `_`, `-`                      |
| Case               | Case-sensitive (`ABC` and `abc` are different keys) |
| Scope              | Unique per operation, per client                    |

A random UUID v4 works, but it forces you to generate the key up front and store it alongside the order so you can reuse the exact same value on a retry.

A simpler approach is to **derive the key deterministically** from data you already have, such as the order ID combined with the request payload. Because the same inputs always produce the same key, a retry recomputes the identical key without you having to store anything:

<CodeGroup>
  ```php PHP theme={null}
  // $orderId is your own order reference; $payload is the exact body you will send.
  $key = $orderId . '_' . hash('sha256', json_encode($payload));

  // $key is your order reference followed by a 64-character hex hash, e.g.
  // "order-4521_9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08"
  ```

  ```javascript JavaScript theme={null}
  import { createHash } from 'node:crypto';

  // orderId is your own order reference; payload is the exact body you will send.
  const hash = createHash('sha256').update(JSON.stringify(payload)).digest('hex');
  const key = `${orderId}_${hash}`;
  ```
</CodeGroup>

Send the derived value in the `Idempotency-Key` header. If the request fails and you retry, recompute the key from the same order and payload, and the API recognises it as the same operation.

<Tip>
  Including the payload in the key means that a different payload produces a different key, which the API treats as a new operation. If you instead key on a stable identifier alone (for example just the order ID), reusing it with a changed payload is reported as a conflict — see below.
</Tip>

## How repeated requests are handled

| Situation                                 | Result                                                                                                                                                    |
| ----------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------- |
| New key                                   | The request is processed normally and the response is stored.                                                                                             |
| Same key, same payload, original finished | The stored response (status code and body) is returned without re-executing the operation. The response includes the header `Idempotency-Replayed: true`. |
| Same key, **different** payload           | `422 Unprocessable Entity`. The key is already associated with a different request.                                                                       |
| Same key, original still in progress      | `409 Conflict` with a `Retry-After` header indicating how many seconds to wait before retrying.                                                           |

Keys are scoped per client and expire automatically 24 hours after first use. After that, the same key can be used again for a new operation.

<Note>
  A request that fails validation is not stored against the key. You can correct the payload and retry with the same key.
</Note>

## Conflicts and errors

If you receive a `409 Conflict`, the original request is still being processed. Wait for the number of seconds given in the `Retry-After` response header, then retry with the same key:

```bash Example response theme={null}
HTTP/1.1 409 Conflict
Retry-After: 1
```

A `422 Unprocessable Entity` means the key has already been used for a request with a **different payload** which is considered wrong. Either reuse the original payload, or generate a new key for the new operation.
