Skip to content
YAS.SH
Developers

Why POST Needs Idempotency Keys

Prevent duplicate links and charges with a real example.

mohamed-elsaadouni6 min readapiidempotencydevelopers
Why POST Needs Idempotency Keys
Featured imageWhy POST Needs Idempotency Keys

Why POST Needs Idempotency Keys

You send a request. The server processes it. But the response is lost in the network — a timeout, a dropped connection, a flaky mobile network. So your client retries. The server processes it again. Now you have two short links, or two charges, or two emails.

This is the classic duplicate-problem, and the fix is an idempotency key: a unique string your client sends with the request so the server can recognize and deduplicate retries. This guide explains how they work, when you need them, and how to implement them on the create-links pattern.

The problem: retries are inevitable

Networks drop things. It's not a bug — it's physics. A request can succeed at the server and still "fail" for the client because the response never arrived. The client's only sane behavior is to retry. But a retry of a non-idempotent operation creates a duplicate.

  • Retrying a create → duplicate resource.
  • Retrying a charge → double charge.
  • Retrying a send → double email.

Without a way to tell "this is the same request as before," the server can't know it should return the original result rather than create another one.

What an idempotency key is

An idempotency key is a unique, client-generated string sent as a header (or body field) on a request:

POST /api/v1/links
Idempotency-Key: 4f8a9c2e-...-unique

The contract:

  1. The client generates a fresh key for each new logical operation.
  2. The same key is reused on every retry of that operation.
  3. The server stores the key with the operation's result.
  4. If a request with an already-seen key arrives, the server returns the stored result instead of running the operation again.

The key only needs to be unique per client; the server treats it as opaque.

How the server handles it

A minimal implementation:

key = request.header("Idempotency-Key")
if key is present:
    existing = store.lookup(key)
    if existing:
        return existing.result        # deduplicate — no second create
    result = create_link(payload)     # do the real work
    store.save(key, result)           # remember for retries
    return result

Two things matter:

  • Check the key before doing the work. The lookup must happen before the side effect, so a retry never triggers a second create.
  • Store the result atomically with the work, so a race between two identical requests can't both create.

Say a mobile app shortens a URL. The user taps once, the network blips, the app retries. Without a key:

  • Request 1 creates ab3x7.
  • Request 2 creates cd5y9.
  • The user now has two links for the same destination.

With an idempotency key:

  • Request 1 (Idempotency-Key: abc) creates ab3x7 and stores the result under abc.
  • Request 2 (Idempotency-Key: abc) finds the stored result and returns ab3x7.
  • The user gets one link, and the client gets a clean success.

This is exactly how a robust create-links API should behave — and why a good shortener API supports idempotency keys on its POST /links endpoint.

When you need it vs when you don't

Need it for any non-safe operation with a side effect you don't want duplicated: creating resources, charging payments, sending emails, registering, uploading.

Don't strictly need it for GET (already idempotent) or for operations you're willing to let run twice. But even "harmless" duplicates create confusion and data-quality problems, so the key pattern is cheap insurance.

Common mistakes

  1. Checking the key after the work. If you create first and check later, retries still duplicate.
  2. Reusing one key for everything. The key identifies a specific operation; reuse breaks dedup.
  3. Ignoring the key on some clients. If only some clients send keys, retries from the others still duplicate. Enforce it on the critical paths.
  4. Not storing enough to return the original result. If you don't persist the result, you can't replay it.

The takeaway

Idempotency keys turn an unsafe retry into a safe one. Generate a unique key per operation, reuse it on retries, and have the server check it before doing work and store the result. It's a small header that prevents the most embarrassing class of bugs — duplicate links, duplicate charges, duplicate emails.

When you're building on yas.sh's API, send an Idempotency-Key on link creation to guarantee your retries never create duplicates.

Why POST is not naturally idempotent

The problem idempotency keys solve comes from a property of HTTP itself: GET is naturally safe and repeatable, but POST is not. A POST that creates a resource will create a new one every time it is sent — so if a network failure causes the client to retry, the server can receive two create requests and produce two resources. This is a real problem for link creation, checkout, and any write where duplicates are expensive. Idempotency keys make a POST safe to retry by letting the server recognize that a repeated request is the same logical operation and return the original result instead of creating a duplicate.

How an idempotency key works

The pattern is straightforward:

  1. The client generates a unique key for the operation — a UUID or a hash of the request content — and sends it in the request.
  2. The server records the key along with the result of the first request that used it.
  3. If another request arrives with the same key, the server returns the stored result instead of performing the action again.

The key is scoped to the resource and identifies the logical operation, not the physical request. The result is that retries become safe: a request that succeeded but whose response was lost, or that failed mid-flight, can be safely re-sent with the same key and will not duplicate work.

Idempotency is the backbone of reliable link automation and bulk operations. In the bulk creation workflow, attaching an idempotency key per row means that if the batch is interrupted and restarted, already-created links are recognized and returned rather than created a second time. The same applies to webhook handling, where legitimate re-deliveries must not duplicate actions. Wherever you have a write that could be retried — and in automation, everything can be retried — an idempotency key turns a potentially duplicate operation into a safe one.

Choosing and storing keys

Two design details matter. First, generate the key deterministically where possible: a content hash makes the same logical request produce the same key even across retries, while a random UUID guarantees uniqueness but requires the client to keep the key for the whole retry sequence. Second, decide how long to retain keys and results — long enough to cover legitimate retry windows and reconciliation, but not so long that storage grows unboundedly. With a deterministic key and a sensible retention period, idempotency becomes an invisible guarantee that your writes stay correct under failures.

Frequently asked questions

What is an idempotency key?

A unique client-generated string sent with a POST request so the server can recognize and deduplicate retries. If the same key arrives again, the server returns the original result instead of creating a duplicate.

Why do I need one for creating links?

Network timeouts and client retries can resend the same create request. Without a key, you get duplicate links. With a key, the second request is a no-op that returns the first result.

When should I send an idempotency key?

Whenever the operation has a side effect you don't want duplicated: creating a resource, charging a payment, sending an email, or registering something. GET is naturally idempotent; non-safe POSTs benefit most.

Was this helpful? Share
Ask YAS AI
🍪 Cookies & privacy. Essential cookies keep you signed in and remember language and theme. Google AdSense and reCAPTCHA are Google technologies: AdSense runs only after Accept All; reCAPTCHA loads on sign-in and contact forms. See how Google uses data: https://policies.google.com/technologies/partner-sites cookie policy · privacy policy.
Settings