Idempotent APIs in Node.js with Redis

Published: (February 10, 2026 at 08:28 PM EST)
3 min read
Source: Dev.to

Source: Dev.to

Overview

Cover image for Idempotent APIs in Node.js with Redis

Distributed systems lie. Requests get retried. Webhooks arrive twice. Clients timeout and try again. What should be a single operation suddenly runs multiple times — and now you’ve double‑charged a customer or processed the same event five times. Idempotency is the fix. Doing it correctly is the hard part.

This post shows how to implement idempotent APIs in Node.js using Redis, and how the idempotency-redis package helps handle retries, payments, and webhooks safely.

What idempotency means for APIs

An API operation is idempotent if multiple calls with the same idempotency key produce the same result — and side effects happen only once.

  • One execution per idempotency key
  • Concurrent or retried requests replay the same result
  • Failures can be replayed too

This matters for:

  • 💳 Payments
  • 🔁 Automatic retries
  • 🔔 Webhooks
  • 🧵 Concurrent requests

Why naive solutions fail

Common approaches break down quickly:

  • In‑memory locks → don’t work across instances
  • Database uniqueness → hard to replay results
  • Redis SETNX → no result or error replay
  • Returning 409 Conflict → pushes complexity to clients

What you actually need is coordination + caching + replay, shared across all nodes.

Using idempotency-redis

idempotency-redis provides idempotent execution backed by Redis:

  • One request executes the action
  • Others wait and replay the cached result
  • Errors are cached and replayed by default
  • Works across multiple Node.js instances

Basic example

import Redis from 'ioredis';
import { IdempotentExecutor } from 'idempotency-redis';

const redis = new Redis();
const executor = new IdempotentExecutor(redis);

await executor.run('payment-123', async () => {
  return chargeCustomer();
});

Calling this five times concurrently with the same key runs the function once.

Real‑world use cases

Payments

Payment providers and clients retry aggressively. Your API must never double‑charge.

await executor.run(`payment:${paymentId}`, async () => {
  const charge = await stripe.charges.create(/* … */);
  await saveToDB(charge);
  return charge;
});

If the response is lost, retries replay the cached result — no second charge.

Webhooks

Webhook providers explicitly say “events may be delivered more than once.”

await executor.run(`webhook:${event.id}`, async () => {
  await processWebhook(event);
});

Duplicate delivery? Same result. One execution.

Retries without fear

With idempotency in place, you can safely:

  • Enable HTTP retries
  • Retry background jobs
  • Handle slow or flaky dependencies

No duplicate work. No race conditions.

Error handling and control

By default, errors are cached and replayed — preventing infinite retries. You can opt out selectively:

await executor.run(key, action, {
  shouldIgnoreError: (err) => err.retryable === true
});

When to use this

Use idempotency-redis if you:

  • Build APIs that mutate state
  • Accept retries or webhooks
  • Run multiple Node.js instances
  • Care about correctness under failure

Learn more

  • 📦 npm:
  • 🐙 GitHub:
0 views
Back to Blog

Related posts

Read more »

Descent, Ported to the Web

Article Descent, Ported to the Webhttps://mrdoob.github.io/three-descent/ Discussion Hacker News thread – 36 points, 7 commentshttps://news.ycombinator.com/ite...