Skip to main content

Overview

Idempotency keys ensure that retrying a request multiple times has the same effect as making it once. This is critical for operations like creating resources or processing payments, where duplicate requests could cause unintended side effects. Key benefits:
  • Safely retry failed requests without creating duplicates
  • Handle network timeouts and transient errors gracefully
  • Guarantee exactly-once semantics for critical operations

When Idempotency is Required

Requests with method POST or PATCH that don’t include an idempotency key will be rejected with a 400 validation error.
Validation logic (from services/proxy.service.ts:66):

How to Use Idempotency Keys

Provide the idempotency key as a request header:

Body Field Method

Alternatively, include it in the request body:
Precedence: If both the header and body field are present, the header value takes precedence. See routes/proxy.ts:48.

Idempotency Key Format

  • Type: String
  • Length: 1-255 characters
  • Scope: Per-agent (different agents can use the same key without collision)
  • Recommendations:
    • Use a unique identifier that represents the operation (e.g., order-{orderId}-{timestamp})
    • Include a retry counter for clarity (e.g., payment-123-retry-2)
    • Avoid special characters that might require URL encoding
Example patterns:

How Idempotency Works

Request Hashing

GaiterGuard creates a SHA-256 hash of the request to detect changes:
Hash includes:
  • HTTP method
  • Target URL (including query parameters)
  • Request body
If you retry a request with the same idempotency key but different method, URL, or body, the request will be rejected. This prevents accidentally executing a different operation.

State Machine

Each idempotency key goes through a lifecycle:

Implementation Details

The idempotency logic is implemented in services/idempotency.service.ts:31:

Cache Hit Behavior

When a request matches an existing completed idempotency key:
  1. Gateway looks up cached response from idempotency_keys table
  2. Returns the original response status, headers, and body
  3. No request is made to the target service
  4. Response includes X-Idempotency-Status: processed header
Example:

Handling Concurrent Requests

If multiple requests arrive with the same idempotency key while the first is still processing: Request 1 (arrives first):
  • Creates idempotency record with status=processing
  • Forwards to target service
  • Updates status to completed when done
Request 2 (arrives concurrently):
  • Finds existing record with status=processing
  • Returns 409 Conflict error immediately
  • Client should wait and retry
Error response:

Transaction Isolation

Idempotency checks use READ COMMITTED transaction isolation to prevent race conditions:
This ensures that if two requests arrive simultaneously with the same key:
  1. First transaction creates the record with status=processing
  2. Second transaction sees the existing record and returns 409
  3. No duplicate requests are forwarded to the target service

TTL and Expiration

Idempotency keys have a 24-hour TTL:
After expiration:
  • Cached responses are no longer available
  • The same idempotency key can be reused for a new operation
  • Expired records may be cleaned up by a background job (implementation pending)
The TTL starts from when the idempotency key is first created, not when the request completes.

Database Schema

Idempotency keys are stored in the idempotency_keys table:
Key points:
  • Unique constraint on (agent_id, key) ensures one record per agent per key
  • Indexed on expires_at for efficient cleanup queries
  • Cascading delete when agent is deleted

Completing and Failing Idempotency Keys

On Success

When the proxied request completes successfully:

On Failure

When the proxied request fails:
Failed keys allow retry - the next request with the same key will delete the failed record and create a new one.

Best Practices

Do’s

  • Use descriptive, unique keys that represent the operation
  • Include identifiers that make the operation traceable (order ID, user ID, etc.)
  • Retry 409 errors with exponential backoff
  • Keep the same idempotency key when retrying a failed request
  • Use the header method for cleaner request bodies

Don’ts

  • Don’t reuse idempotency keys for different operations
  • Don’t change the request body/URL when retrying with the same key
  • Don’t use random UUIDs (defeats the purpose - retries need the same key)
  • Don’t use extremely short keys that might collide
  • Don’t rely on idempotency for GET requests (use them, but they’re already safe)

Example: Creating an Order