Skip to main content
An Idempotency-Key header makes a POST safe to retry: a repeat with the same key returns the first answer and does not act twice, so your retries never make a second send.

Rules

  • Any unique string up to 255 characters works; a UUID or ULID is a good choice. Every POST accepts one.
  • Keys are kept for 24 hours per app and mode.
  • A repeat returns the first answer with the header Idempotent-Replayed: true.
  • Reusing a key with a different method, path or body fails with 409 idempotency_conflict. So does a repeat that arrives while the first request is still running, and a repeat of a request that made a secret shown only once (creating a webhook endpoint, rotating its secret): Flow does not keep that answer, so it cannot show the secret again.
  • Answers worth retrying (429, 500, 502, 503, 504) are not kept, so a retry with the same key runs again. Every other answer is kept, a 422 included.
  • Typing takes a key too: a repeat within 24 hours answers the first result and does not show the indicator again, so give each typing call its own key (or none).

What it does not cover

The key protects your requests, not the hop to the channel. When a channel takes a send but never answers, Flow cannot tell whether it arrived, so it sends it once more; on Telegram, which has no way to check, the person can rarely get it twice (on the other channels Flow first waits for the channel’s own report). If the outcome is still unknown, the message fails with channel_timeout_unknown_outcome (channel_error): check the conversation before sending it again.

Good keys for agents

  • Replying to an event: use the event’s id, plus what each call is when you send several messages (evt_...:0, evt_...:1, or evt_123:text, evt_123:card:2). Give every call its own key. A redelivered webhook then cannot make your agent answer twice.
  • A send that replaces a refused one: a 422 answer is kept under its key like any other, so repeating the key returns the same 422. The message you send instead (a basic after a buttons came back 422 unsupported_content) is a new request with a new key: the refused one plus what it is, such as evt_123:card:2:basic.
  • Webhook replies already use the event’s id as their key.
  • Stream frames: the ref of a send or start frame is its idempotency key.
  • The TypeScript SDK generates a key for every POST and reuses it across its own retries; pass idempotencyKey to choose it yourself.