Idempotency is the property of an API operation where sending the same request more than once has the same effect on the server as sending it once. The response can differ between calls, but the server's state doesn't change any further after the first successful attempt, which is what makes an operation safe to retry.
That safety matters most under failure. We've read enough incident threads to know how it plays out: a consumer function crashed mid-execution, the platform redelivered the message, and the handler ran twice.
The team's first instinct is to ask why the message arrived twice. On our platform, runtime interruptions cause re-execution by design. So, the useful question isn't "why did this happen" but "was the code written to survive it?"
When we moved Vercel Queues to public beta on February 27, 2026, we shipped it with at-least-once delivery semantics by default. That's a deliberate tradeoff, and it has teeth: a consumer crash, a deployment rollout, or a plain function timeout can all trigger redelivery. The same retry pressure shows up in Webhooks, and Cron Jobs introduce a parallel risk through overlapping scheduled invocations. Idempotency isn't a nice-to-have here. It's the minimum correctness requirement for any code that runs on a platform primitive that repeats work automatically.
Copy link to headingKey takeaways
RFC 9110 defines idempotency by server-state effects; responses may differ, and a DELETE returning 404 on the second call is still idempotent.
Vercel Queues delivers messages at least once; we deduplicate at the publish layer via Vqs-Idempotency-Key, but your consumer handler must be idempotent independently.
Duplicate scenarios differ by timing and scope; a single idempotency key covers sequential retries.
Idempotency keys give you deterministic convergence under retries, and treating them as exactly-once semantics produced a complex, hard-to-debug system at ING.
Standard p99 latency metrics won't surface duplicate-processing bugs; you need purpose-built deduplication metrics such as delivery-count histograms.
The
@vercel/kvpackage is deprecated; any Redis-based idempotency pattern on Vercel should use an external Redis integration from the Vercel Marketplace.
Copy link to headingIdempotency by server-state effects under RFC 9110
RFC 9110 §9.2.2 defines it precisely: "A request method is considered 'idempotent' if the intended effect on the server of multiple identical requests with that method is the same as the effect for a single such request." The definition leaves response identity out, which is where the confusion usually creeps in.
Take MDN's DELETE example: the first call returns 200, the second returns 404. The status codes differ, but the server state after each call is identical, so the method is idempotent.
Every safe method is also idempotent. PUT and DELETE change server state and remain idempotent. PATCH misses the guarantee by design: it modifies a resource partially, so a PATCH against an auto-incrementing counter produces a different state on each application, while PUT replaces the whole resource and lands in the same place every time.
For POST and PATCH, idempotency becomes your application's job, since the protocol gives no free guarantee there.
Copy link to headingAt-least-once delivery requires idempotency on Vercel
Vercel Queues delivers every accepted message to each consumer group at least one time. In most cases, a message arrives exactly once, but there are edge cases where it arrives more than once. At least once is the only guarantee a distributed system can honestly offer under distributed failure modes, so our consumer guidance requires idempotent handlers.
Redelivery in Queues has specific, ordinary triggers:
Your consumer function crashes mid-processing or times out before acknowledging
A deployment interrupts the function
After 32 delivery attempts, we force exponential backoff to prevent runaway deliveries.
The same logic runs through the rest of the platform. Vercel Workflows retries a failed step 3 times by default (4 total attempts); a FatalError skips retries, and a RetryableError retries after the delay you specify. Cron Jobs sit at the other extreme: failed cron invocations receive no retry. If a run exceeds its interval, a second instance can start while the first is still running, which creates duplicate-processing risk.
Vercel Connect webhooks have no built-in delivery deduplication either. If a forwarded event arrives more than once, your handlers run multiple times, so track processed event IDs and skip repeats.
Copy link to headingDuplicate scenarios need separate mechanisms
Adding an idempotency key covers one of three distinct failure shapes.
Publish with await send('orders', payload, { idempotencyKey: 'order-123' }) and we deduplicate for the entire lifetime of the original message, whether that TTL is 60 seconds or 7 days. If you fetch a duplicate by ID with ReceiveMessageById, the endpoint returns 409 with the originalMessageId.
Concurrent races need a single atomic operation. When two workers race, both can pass the key check before either writes a record. Redis SET key value NX EX 86400 returns OK for a new key and nil for a duplicate, with no race window between check and set. On Vercel, use an external Redis integration from the Vercel Marketplace.
Scope changes at system boundaries too: Kafka's exactly-once semantics don't extend to the REST API your consumer calls, and each external system needs its own strategy. Workflows gives you a stepId from getStepMetadata() that stays stable across retries; pass it as an Idempotency-Key header so a retried step sends the same key the downstream API has already seen.
The sequence of operations decides whether atomicity holds. A published webhook postmortem spells out the safe sequence: insert the processed-event row first under a unique constraint, return the cached charge if the insert conflicts, and call the payment API only if you own the new row. As the author puts it, "That sequence closes the window where two workers both 'win' a check-then-act race." And skip in-memory deduplication maps: they only work within a single function instance, and on our serverless platform, instances come and go, so the store has to live outside the function.
Copy link to headingIdempotency keys give you convergence under retries
No distributed system can honestly guarantee exactly-once delivery, and we built Queues around that limitation instead of promising something we couldn't back up. Queues defaults to at-least-once delivery, and idempotent handlers are what make retries safe.
Idempotency and exactly-once sit at different layers. Idempotency, per RFC 9110, is a semantic property of a method: what effect repeated requests have on server state. Exactly-once is a delivery guarantee: how many times a message arrives. At-least-once systems lean on idempotent handlers to produce exactly-once effects; as Confluent's own training material states, "we can simulate exactly-once using a combination of at-least-once and de-duplication or idempotency."
Conflating the two has a documented cost: Oscar Caraballo and Luis García Castro, engineers at ING, described the outcome at Confluent Current 2025, where they assumed Kafka exactly-once semantics would solve all their deduplication problems. They said that assumption produced a complex, hard-to-debug system with redundant safeguards that sometimes worked against each other.
Duplicate-processing bugs also hide from standard dashboards. In the Stackademic double-charge incident, "webhook_http_2xx was green the whole incident"; a charges_per_event_id histogram would have paged the team, because "correctness bugs do not show up in p99 until you invent a metric for duplicates." AWS's Lambda documentation carries the same warning about duplicate processing under at-least-once event sources. An idempotency key buys deterministic convergence: re-execution lands on the same final state, even under at-least-once delivery.
Copy link to headingWhat Vercel handles at the platform layer and what your handler code still owns
Idempotency on our platform splits into two categories: deduplication we perform before your code runs, and guarantees that only your code can make.
Copy link to headingWhat Vercel handles at the platform layer
When you publish to Queues with an idempotency key, we deduplicate out-of-band after the publish call returns; the SendMessage response always succeeds, and the duplicate is silently dropped while the original keeps its at-least-once delivery. The default TTL is 24 hours, with a range of 60 seconds to 7 days. Sends with an idempotency key are billed at 2× operations, a cost we think is worth naming rather than burying.
Some platform APIs are idempotent by construction: the Rolling Releases start endpoint succeeds and returns the current state if a rolling release is already active for the same canaryDeploymentId, while Deploy Hooks debounce builds, so multiple rapid POSTs discard duplicate deployments. Sandbox.getOrCreate() also uses an idempotent retrieve-or-create pattern. We made these operations idempotent by design because convention doesn't survive retries.
Copy link to headingWhat your handler code still owns
Even with publish-layer deduplication, Queues consumers must be idempotent on their own, because delivery is still at-least-once. Deduplicate on a unique message ID, or make the operation naturally idempotent by setting a value rather than incrementing it. The Vqs-Message-Id and Vqs-Delivery-Count headers exist for exactly this; Vqs-Delivery-Count is also the raw input for the duplicate metrics the previous section argued for.
For cron jobs, pair Redis distributed locks against concurrent overlap with idempotent reconciliation for missed or duplicate runs. If a cron route isn't marked dynamic, the invocation can receive a cached response and never execute the handler at all.
Next.js Server Actions ship no server-side deduplication. useFormStatus disables the submit button during submission, but only on the client, and actions invoked from useEffect can double-execute in React Strict Mode. Next.js 15 also made GET Route Handlers uncached by default, reversing the Next.js 14 behavior, which matters if your team relied on CDN-level caching to absorb repeated GETs.
Copy link to headingProve idempotency in handler code
Distributed systems re-execute code, and no infrastructure removes that. Start with stable keys for sequential retries, then test the cases keys cannot cover: worker races that need atomic check-and-set, plus downstream APIs that need their own keys. A handler that covers those cases converges on the same final state, however many times it runs.
Vercel Queues supports deduplication via idempotency keys when publishing messages, and Workflows provides durable steps that persist progress and retry across failures. Some endpoints, including Rolling Releases and Deploy Hooks, are idempotent by design. Your consumers, cron handlers, Server Actions, and webhook receivers are yours to make safe. Start with the Vercel Queues documentation for the platform patterns, and add Redis integration for the parts your code must prove.
Copy link to headingFAQ
Is a PUT request always safe to retry without an idempotency key?
PUT is idempotent by the HTTP spec, since repeating it leaves the resource in the same state, but the guarantee applies to the method's intended semantics; a misbehaving server can still violate it. When your consumer calls downstream APIs, use an explicit idempotency key regardless of the HTTP method.
Does Vercel Queues guarantee exactly-once delivery?
Vercel Queues provides at-least-once delivery. The idempotency key deduplicates at the publish layer; consumer handlers must be idempotent independently to achieve exactly-once effects.
What happens if I send the same idempotency key twice to Vercel Queues?
The second publish call succeeds because deduplication happens out-of-band after the publish returns, and the original message continues to be delivered. Fetching the duplicate via ReceiveMessageById returns 409 with the originalMessageId.
Can I use @vercel/kv for Redis-based idempotency in Next.js?
Use Upstash Redis instead. The @vercel/kv package is deprecated, and existing stores moved to Upstash Redis in December 2024. For new projects, install a Redis integration from the Vercel Marketplace and use the Upstash Redis SDK, which connects over HTTP and works in serverless and Edge runtimes.
What is the IETF Idempotency-Key header draft's status if Stripe and Adyen already use it?
The latest formally submitted revision, draft-07, expired in April 2026, and the Datatracker shows the draft in WG Document status, with IESG state "I-D Exists" and no telechat date, indicating it has not advanced to an IESG publication state. Adoption ran ahead of standardization: Stripe and Adyen ship the header, PayPal and Twilio use alternative names, and MDN carries a reference page. Your implementation can rely on the pattern either way; the pattern is stable.