# Idempotency

Idempotency ensures that the same transaction is never processed more than once, even if the client retries a request due to network failures or timeouts.

## How It Works

Every `POST /transaction` request requires an `Idempotency-Key` header. This key uniquely identifies the transaction intent. If the same key is submitted again within the idempotency window, the system rejects it with HTTP 409.

```bash
curl -X POST http://<host>:8080/transaction \
  -H "Idempotency-Key: payment-abc-123" \
  ...
```

## Idempotency Window

The idempotency window is configured via the `LEDGER_IDEMPOTENCY_TTL_HOURS` environment variable. After this period, old idempotency records are cleaned up by a background job running at the interval configured by `LEDGER_IDEMPOTENCY_CLEANUP_INTERVAL_MIN`.

**Recommended:** `LEDGER_IDEMPOTENCY_TTL_HOURS=1` with `LEDGER_IDEMPOTENCY_CLEANUP_INTERVAL_MIN=5`.

## Three Layers of Duplicate Detection

EntryTarget uses three layers to catch duplicates at different stages:

| Layer               | Stage                            | Behavior                                                    |
| ------------------- | -------------------------------- | ----------------------------------------------------------- |
| **In-flight check** | Before entering the queue        | Immediate 409 — nanosecond response, lock-free              |
| **Batch check**     | Within the same processing batch | Immediate 409 for the duplicate; valid requests continue    |
| **Database check**  | During database commit           | Full batch rollback — all requests in the batch receive 409 |

### Why the aggressive rollback on database-level conflicts?

Your system is expected to be a well-built integration with its own idempotency controls. Duplicate keys reaching the ledger at the database level indicate a bug in your integration, not normal behavior. The aggressive rollback makes these failures loud and visible so you can fix the root cause quickly.

## Querying Idempotency Records

You can retrieve the original request and response for any idempotency key within the TTL window:

```bash
curl http://<host>:8080/idempotency/<key> \
  -H "X-Api-Key: ak_..." \
  -H "X-Api-Secret: sk_..."
```

**Response:**

```json
{
  "idempotency_key": "payment-abc-123",
  "request": { ... },
  "response": { ... },
  "created_at": "2026-01-15T10:30:00.123Z"
}
```

Returns 404 if the key is expired (past the configured TTL) or was never used.

## Idempotency Key Rules

| Rule       | Details                              |
| ---------- | ------------------------------------ |
| Required   | Every `POST /transaction`            |
| Max length | 100 characters                       |
| Uniqueness | Must be unique within the TTL window |

## Design Rationale

* **No replay** — submitting the same key does NOT return the original response. It returns 409. Your system should use `GET /idempotency/:key` to retrieve the original response if needed.
* **Request/response persistence** — both are stored as JSON in the idempotency record, adding no overhead to the hot path.
* **Configurable window** — you control how long keys are remembered via environment variables.

{% hint style="warning" %}
If a duplicate key is detected at the database level (layer 3), **all requests in the same batch** are rolled back and receive 409. This is by design — it forces immediate investigation of the duplicate source.
{% endhint %}

## After the Window Expires

Once the idempotency window expires and the cleanup job removes the record, that key can theoretically be reused. However, you should always use unique keys — reusing keys defeats the purpose of idempotency.

If you need to reconcile after an extended outage where the idempotency window may have expired, use the [Crash Recovery](/docs/security-and-integrity/crash-recovery.md) mechanism instead.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://entrytarget.gitbook.io/docs/concepts/idempotency.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
