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
POSTaccepts 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
409idempotency_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, a422included. - 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 withchannel_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, orevt_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
422answer is kept under its key like any other, so repeating the key returns the same422. The message you send instead (abasicafter abuttonscame back422 unsupported_content) is a new request with a new key: the refused one plus what it is, such asevt_123:card:2:basic. - Webhook replies already use the event’s
idas their key. - Stream frames: the
refof asendorstartframe is its idempotency key. - The TypeScript SDK generates a key for every
POSTand reuses it across its own retries; passidempotencyKeyto choose it yourself.