HatchedDocs
Concepts

Idempotency

How retry-safe mutations work in the Hatched API — the Idempotency-Key contract, what the platform caches, and how the SDK uses it automatically.

Hatched accepts an Idempotency-Key header on every mutating endpoint (POST, PUT, PATCH, DELETE). When you supply one, the platform guarantees that retries of the same request return the same response — body, status, and side effects — for 24 hours.

This is the same shape Stripe, Slack, and AWS use. Apply it whenever a network blip, timeout, or client crash could cause you to repeat a request that should run exactly once.

How it works

  1. You send Idempotency-Key: <unique-string> alongside the request.
  2. The interceptor hashes the request method, path, and body to produce a fingerprint.
  3. Cache miss → handler runs normally. The response (body + status) is cached under idem:{customer_id}:{key} for 24 hours.
  4. Cache hit, matching fingerprint → the cached response replays. Idempotency-Replayed: true is set on the response so you can tell the difference in a log line.
  5. Cache hit, different fingerprint409 idempotency_key_conflict. You reused the key for a different request. Use a fresh key.

Failed responses (4xx / 5xx) are not cached — retries should produce a fresh attempt. Cache only sticks on 2xx success.

Picking a key

A good key is:

  • Unique per logical action. Use the business id (order_<id>, lesson_<lessonId>_user_<userId>), not a clock value.
  • Stable across retries. The whole point: a retry must reuse the original key.
  • Opaque to the user. Never expose it in a URL.

UUID v4 works fine when you don't have a natural business id. The SDK auto-generates one for you (next section).

SDK behaviour

@hatched/sdk-js injects Idempotency-Key automatically on mutating resource methods. Most callers never have to think about it:

// Auto-generated, retried safely on transient network errors.
await hatched.eggs.create({ userId: 'user_42', ensure: true });

To pass an explicit key from your own business logic — recommended for operations you may retry from a different process — set it via the options object:

await hatched.events.send(
  {
    eventId: 'lesson_42_user_99',
    userId: 'user_99',
    type: 'lesson_completed',
    properties: { … },
  },
  {
    headers: { 'Idempotency-Key': `lesson:42:user:99` },
  },
);

What this is not

  • Not event-level deduplication. Use eventId on send events for that — it lives in the rule-engine, not the HTTP layer.
  • Not webhook idempotency. That contract is on the consumer side — see Webhook delivery.
  • Not a queue. Replays return the cached response immediately; they don't re-enqueue work.

When to skip it

Read-only requests (GET, HEAD) ignore the header — they're naturally idempotent. One-shot operations that intentionally produce a side effect each time (charging a credit grant, issuing a unique token) should reuse a different key per attempt rather than letting the platform replay.

If you need a route that explicitly opts out of idempotency caching, flag it on your side — Hatched will keep the header behaviour but you can set the key to a unique UUID per attempt to force a fresh run.