# Idempotency

`GET`, `PUT` and `DELETE` are idempotent by HTTP design. For `POST` and
`PATCH` on sensitive resources (tasks, service orders, tickets) follow the
practices below.

## Correlate with `externalId`

- Send a client-generated identifier in `externalId` (e.g. ERP ID). Before
creating, run the matching `GET` filtered by `externalId`.
- If it exists, update via `PATCH`; otherwise create via `POST`.


## Per-delivery idempotency (Webhooks)

- Use the `deliveryId` from the payload to deduplicate on your side (write-once
into a `webhook_deliveries` table).


## Safe retry policy


```ts
async function postWithSafety(body) {
  const existing = await findByExternalId(body.externalId);
  if (existing) return existing;
  try {
    return await api.post('/tasks', body);
  } catch (err) {
    if (err.status === 409) {
      return findByExternalId(body.externalId);
    }
    throw err;
  }
}
```

## Roadmap

- Native `Idempotency-Key` support (Stripe-like) — watch the [changelog](/changelog).