Skip to content

Idempotency

Write routes that create a resource (for example, creating a webhook endpoint, sending a test webhook event, or uploading to the knowledge base) require an Idempotency-Key header. It lets you safely retry a request — after a timeout, a dropped connection, anything — without risking a duplicate side effect.

POST /api/v1/webhooks
Idempotency-Key: 7d6f0a2e-8b1c-4a3d-9e5f-2c1b0a3d4e5f
Content-Type: application/json
{ "url": "https://example.com/hooks/clienta", "events": ["conversation.created"] }

Use a fresh, unique key (a UUID is fine) per logical operation, and reuse the same key when retrying that same operation.

Requests are claimed first, before any work happens — this is what makes it crash-safe, not just retry-safe:

  1. Your request arrives with an Idempotency-Key. The server tries to atomically claim (organization, key, route, idempotencyKey).
  2. If nothing else has claimed that combination yet, you win the claim and the request proceeds normally.
  3. If it’s already claimed, the server looks at what happened to the earlier attempt and responds accordingly — see the table below.
SituationResponse
Same key, identical request, already completedThe original response is replayed verbatim (same status code and body)
Same key, but a different request body/method/path409 IDEMPOTENCY_KEY_REUSED
Same key, an earlier attempt is still in flight425 IDEMPOTENCY_IN_PROGRESS with a Retry-After header
Same key, an earlier attempt crashed mid-request (its processing lease expired)The claim is safely reclaimed and the request is retried server-side
No Idempotency-Key header on a route that requires one400 IDEMPOTENCY_KEY_REQUIRED
  • A processing claim is held under a short lease (long enough to comfortably cover one request, including any database work) — if the process handling it crashes, another attempt with the same key safely takes over once the lease expires, instead of returning 425 forever.
  • A completed record — and its cached response — is retained for 24 hours, matching how long we’ll honor a retry with the same key. After that window, reusing the same key starts a brand-new operation.

An idempotency key is scoped to your key/identity + the specific route — the same Idempotency-Key value used against two different routes, or by two different API keys, never collides.