# Idempotency > Why every send needs an Idempotency-Key, and how to choose one. > Source: https://onpad.in/docs/api/idempotency Every `POST /messages` request **must** carry an `Idempotency-Key` header. It is the difference between a retry that is safe and a retry that messages your customer twice. ## The problem it solves Your server posts a message. The network drops before the response arrives. Your code does not know whether the message was queued or not. Without idempotency you have two bad choices: retry and risk sending twice, or do not retry and risk not sending at all. With an idempotency key, retrying is simply safe. The second request returns the first one's result and sends nothing. ## How it works ```text Idempotency-Key: order-10482-shipped ``` Rules: 8 to 120 characters, made of letters, digits, `.`, `_`, `:` and `-`. When a request arrives, ONPAD looks for an earlier message in your workspace with the same key: - **No earlier message** → the message is queued. `202 Accepted`. - **Earlier message, identical body** → the original is returned with `"duplicate": true`. `200 OK`. Nothing new is sent. - **Earlier message, different body** → `409`, with *"This Idempotency-Key was already used for a different request."* Nothing is sent. The comparison is on the whole request body, normalised so that key order does not matter. `{"to":"91...","type":"text"}` and `{"type":"text","to":"91..."}` count as the same request. Keys are scoped to your workspace. Another workspace using the same string does not collide with yours. ## Choosing a key **Use something from your own data that identifies this exact send.** The best keys are derived, not random, because a derived key is the same on a retry: ```text order-10482-shipped invoice-2026-0931-reminder booking-77341-confirmation-2 appointment-5521-reminder-24h ``` **Do not use a fresh random string per attempt.** A new UUID on every retry defeats the whole mechanism — each attempt looks like a new message and each one sends. **Do not reuse a key for a different message.** Sending `order-10482-shipped` and later reusing it for a delivery notification gets a `409`, and the second message never goes. If one order genuinely needs several messages, put the purpose in the key: `order-10482-shipped`, `order-10482-delivered`. ## Retrying correctly ```python key = f"order-{order_id}-shipped" for attempt in range(3): try: response = post_message(key, payload) break except NetworkError: time.sleep(2 ** attempt) ``` The key is computed **once, outside the loop**. Every attempt carries the same one, so at most one message is ever sent, no matter how many attempts run. ## What it does not do **It does not retry delivery for you.** It makes your retries safe. Delivery retries happen separately inside ONPAD — 3 attempts with backoff. **It does not expire quickly.** Keys are kept with the message. Reusing a key from months ago still returns that old message rather than sending. **It does not protect against your own duplicate logic.** Two different code paths that build different keys for the same event will both send. Derive the key from the event, not from where you are in the code. ## If you get a 409 You reused a key with a different body. Two possibilities: 1. **The key is too generic** — something like `order-10482` used for several different messages about that order. Make it specific. 2. **The body changed between attempts** — a timestamp or a random value inside the payload, so the retry no longer matches. Build the payload once and reuse it, exactly like the key.