Skip to content

Duplicated vs Repeated Operations ​

Idempotency protects against duplicated operations from network retries while allowing repeated operations—new requests with the same business parameters.

The Difference ​

DuplicatedRepeated
Same request sent multiple times due to network failures, timeouts, or client retriesNew operation that happens to have the same parameters
Should return the same responseShould create a new result
Protected by idempotencyAllowed by idempotency

Example: Monthly Invoice Payments ​

Your company pays the same vendor each month:

  • January: Transfer €100 to DE89370400440532013000 for invoice INV-001
  • February: Transfer €100 to DE89370400440532013000 for invoice INV-002

Same IBAN, same amount, same currency—but two distinct operations.

Request Model ​

javascript
// POST /api/transfers
{
  "iban": "DE89370400440532013000",
  "amount": 10000,           // cents
  "currency": "EUR",
  "description": "Monthly consulting fee",
  "internal_reason": "invoice-550e8400-e29b-41d4-a716-446655440000"
}

The internal_reason field uniquely identifies this payment in your system.

How Idempotency Works ​

Duplicated Request (Retry) ​

http
POST /api/transfers
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "iban": "DE89370400440532013000",
  "amount": 10000,
  "currency": "EUR",
  "description": "Monthly consulting fee",
  "internal_reason": "invoice-550e8400-e29b-41d4-a716-446655440000"
}

Network timeout occurs. Client retries:

http
POST /api/transfers
Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000
Content-Type: application/json

{
  "iban": "DE89370400440532013000",
  "amount": 10000,
  "currency": "EUR",
  "description": "Monthly consulting fee",
  "internal_reason": "invoice-550e8400-e29b-41d4-a716-446655440000"
}

Same key, same body → server returns cached response. No double payment.

Repeated Operation (New Invoice) ​

http
POST /api/transfers
Idempotency-Key: a1b2c3d4-e5f6-7890-abcd-ef1234567890
Content-Type: application/json

{
  "iban": "DE89370400440532013000",
  "amount": 10000,
  "currency": "EUR",
  "description": "Monthly consulting fee",
  "internal_reason": "invoice-a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}

Different key, different body (different internal_reason) → server processes as new transfer.

Choosing a key strategy? See Client Key Strategies for patterns on generating and managing idempotency keys.

Server Implementation ​

Note: The following example uses idempot-js middleware with the Hono framework. For framework-specific implementations, see the idempot-js documentation.

javascript
import { Hono } from "hono";
import { idempotency } from "idempot-js/hono";
import { RedisIdempotencyStore } from "idempot-js/stores/redis";

const app = new Hono();
const store = new RedisIdempotencyStore({ client: redis });

app.post("/api/transfers", idempotency({ store }), async (c) => {
  const { iban, amount, currency, description, internal_reason } =
    await c.req.json();

  // Process transfer - only executed once per unique idempotency key
  const transferId = await processTransfer({
    iban,
    amount,
    currency,
    internal_reason
  });

  return c.json(
    {
      transferId,
      status: "completed",
      internal_reason
    },
    201
  );
});

Summary ​

ScenarioIdempotency KeyRequest BodyFingerprintResult
Retry of same requestSameSameSameCached response
New invoice paymentDifferentDifferent internal_reasonDifferentNew operation

The combination of new key per operation and unique internal_reason in each request body ensures retries return cached responses while new operations process normally.