Skip to main content
Network calls fail. Lambda timeouts mid-write. Zapier retries on its own. The Idempotency-Key header tells us “if you have already seen this request, replay the original response — do not run the work again.”

When to use it

Include Idempotency-Key on every POST and PATCH. For GET requests it has no effect (reads are naturally idempotent).

How to generate one

Use any unique-enough string per request, ≤ 255 characters. A UUID works perfectly:
const idempotencyKey = crypto.randomUUID();
Or hash the source event id:
const idempotencyKey = `hubspot-contact-${event.objectId}-${event.occurredAt}`;
Use the same key for every retry of the same logical request.

How it works

POST /v1/customers
Authorization: Bearer ic_live_...
Idempotency-Key: 7f9a1c2d-...

{ "externalId": "crm-7741", "fullName": "Jane Doe", "email": "jane@example.com" }
  • First request with that key: the work runs, the response is stored, and the response is returned.
  • Second request with the same key (and the same body): the stored response is replayed verbatim, including the original HTTP status code. A Idempotent-Replayed: true header is added so you can tell.
  • Repeat with a different body: returns 409 idempotency_conflict. This catches a real client bug — never silently shadows a prior result.

TTL

Cached responses are kept for 24 hours. After that, the next request with that key runs the work again. Make sure your retry window fits inside 24 hours.

What counts as “the same request”

A request is matched by the tuple (organization, method, path, key). That means:
  • You can reuse the same idempotency key across POST and PATCH without collision.
  • Two different organisations can use the same key independently — they are isolated.
  • POST /v1/customers with key X and PATCH /v1/customers/abc with key X do not interfere.

Idempotency vs externalId

These are two complementary safeties:
  • Idempotency-Key — protects against duplicate calls within a 24h retry window. Operational concern.
  • externalId — protects against duplicate records across all of time. Domain concern. The same externalId reused on POST performs a deliberate upsert.
Use both. They cover different failure modes.

Example: safe retry loop

async function ingestCustomerWithRetry(customer, opts = {}) {
  const key = opts.idempotencyKey ?? crypto.randomUUID();
  for (let attempt = 0; attempt < 5; attempt++) {
    try {
      const res = await fetch('https://app.instantcompliance.ai/api/v1/customers', {
        method: 'POST',
        headers: {
          Authorization: `Bearer ${process.env.IC_API_KEY}`,
          'Content-Type': 'application/json',
          'Idempotency-Key': key
        },
        body: JSON.stringify(customer)
      });
      if (res.status < 500) return res.json();
    } catch (err) {
      // transient — retry with same key
    }
    await new Promise((r) => setTimeout(r, 2 ** attempt * 500));
  }
  throw new Error('Gave up after 5 attempts');
}