Skip to main content
POST endpoints support idempotency via the Idempotency-Key HTTP header. Same key + same body within 24 hours returns the cached original response instead of doing the work twice. Same key + a different body returns 409 IDEMPOTENCY_CONFLICT. Set this on every retried POST.

Contract

Why Every Retry Needs It

Network errors, timeouts, and re-delivered webhooks all cause your code to call the same API twice. Without idempotency that means duplicate workflow runs, duplicate emails sent, duplicate contact rows created. With Idempotency-Key, the retry replays the original outcome without doing the work again. The endpoints that need it most:
  • POST /v1/automations/triggers/{triggerEventId}/fire: a doubled fire = doubled welcome / drip / receipt emails.
  • POST /v1/sends: EMAIL_ALREADY_SENT (409) blocks a same-email retry; an idempotency key lets a retry return the original sendId / runId cleanly.
  • POST /v1/emails: generate is expensive (~30-90s). Idempotency makes a flaky-network retry free.
  • POST /v1/contacts (single OR batch): the underlying upsert is naturally idempotent on email, but Idempotency-Key saves you the second batch run.

Body-Field Fallback on the Fire Branch

POST /v1/automations/triggers/{triggerEventId}/fire also honors a body idempotencyKey field for back-compat. Prefer the Idempotency-Key header for new code:
The HTTP header is preferred for new code; the body field stays for parity. If both are set, the HTTP header wins. The key MUST be stable per logical operation. Common recipes: Anti-patterns to avoid:
  • <uuid()> per call: defeats the purpose; every retry gets a fresh key.
  • Date.now() per call: same as above.
  • <sha256(body)>: vulnerable to partial replays where body bytes change.

Worked Example: Fire a Trigger with Retry

Two invariants:
  1. Reuse the same idempotencyKey across every retry of the same logical operation. The first request wins; the rest return the cached response.
  2. Mint a fresh idempotencyKey when the body genuinely changes. A new email recipient = a new key.

409 IDEMPOTENCY_CONFLICT Envelope

The cached original response can be re-fetched by replaying with the same body. There is no separate “get cached result” endpoint.

Where It Interacts with Other Contracts

  • Rate limits: an idempotent replay counts against the rate-limit window. If you saw a 429 on the first attempt, the retry will hit the gate too. Honor Retry-After, don’t burn keys.
  • Workflow runs: POST /v1/automations/triggers/{triggerEventId}/fire creates rows in automationExecutions. An idempotent replay returns the original automationRunIds[] under details so polling stays consistent.
  • Sends: POST /v1/sends reserves the email atomically via reserveEmailSend. An idempotent replay returns the original sendId / runId and short-circuits before re-reservation; a fresh key on a same-email send produces 409 EMAIL_ALREADY_SENT.

SDK Behavior

The official @brew.new/sdk ships idempotency keys automatically on every POST call (auto-generated UUID, scoped to the in-process operation) so you get safe-by-default retries. Override the auto-key by passing { idempotencyKey: 'your-key' } in RequestOptions:

See Also

  • Rate limits: pair with idempotency for safe retry loops.
  • Errors: full 409 IDEMPOTENCY_CONFLICT envelope.
  • Async jobs & polling: for fire / send retries you typically don’t need to call again. Poll the run instead.

Need help?

Our team is ready to support you at every step of your journey with Brew. Choose the option that works best for you:

Search Documentation

Type in the “Ask any question” search bar at the top left to instantly find relevant documentation pages.

ChatGPT/Claude Integration

Click “Open in ChatGPT” at the top right of any page to analyze documentation with ChatGPT or Claude for deeper insights.