Skip to main content

Overview

Idempotency ensures that performing the same operation multiple times has the same effect as performing it once. Agatabo uses idempotency keys to prevent duplicate financial transactions from network issues, accidental double-clicks, or system retries.
How it works: Every financial operation requires a unique x-idempotency-key header. If the same key is used twice, the second request is rejected as a duplicate.

Why Idempotency Matters

Without idempotency protection:
With Agatabo’s idempotency:

How It Works

Database Constraint

JournalEntry model has unique constraint:
What this means:
  • Within an organization, each idempotencyKey can only be used once
  • Attempting to create a duplicate journal entry fails with database constraint error
  • Second request with same key is rejected automatically

API Requirement

All mutation operations require x-idempotency-key header:
Operations requiring idempotency key:
  • Recording deposits (POST /savings)
  • Recording withdrawals (DELETE /savings/{id})
  • Creating loans (POST /loans)
  • Recording loan payments (POST /loans/{id}/repay)
  • Recording expenses (POST /expenses)
  • Creating assets (POST /assets)
  • Reserve allocations (POST /reserve-allocations)
  • Reserve releases (POST /reserve-allocations/{id}/release)
  • Dividend distributions (POST /dividends/pools, POST /dividends/pools/{id}/distribute)
  • Period closing (POST /period-closing/close, POST /period-closing/undo)
Operations NOT requiring idempotency key:
  • GET requests (read-only)
  • Reports generation
  • Viewing data

Request Validation

Backend validates idempotency key:
Missing key = 400 Bad Request error

Generating Idempotency Keys

Frontend responsibility: Generate unique key for each operation. Recommended format:
Best practices:
  • ✅ Use UUID v4 (cryptographically random)
  • ✅ Include timestamp for easier debugging
  • ✅ Store key in application state during request
  • ✅ Reuse same key for retries of the SAME request
  • ✅ Generate new key for new operations
  • ❌ Never reuse keys across different operations
  • ❌ Don’t use sequential numbers (predictable)

Idempotency Scope

Keys are scoped to organization:
Different organizations can use same key (unique constraint includes organizationId) Within same organization:

Duplicate Detection

When duplicate detected: Request 1 (first time):
Request 2 (duplicate - same key):
Database error (unique constraint violation) caught and returned as API error.

Common Scenarios

Scenario 1: Network Timeout

Situation:
  1. User submits deposit form
  2. Network timeout (30 seconds, no response)
  3. Frontend retries with same idempotency key
Result:
Frontend handling:

Scenario 2: Accidental Double-Click

Situation:
  1. User double-clicks “Record Payment” button
  2. Two requests sent rapidly
Frontend implementation:
Even if both requests reach backend:

Scenario 3: Failed Request Retry

Situation:
  1. Transaction fails due to validation error
  2. User fixes error and resubmits
Can retry with same key if transaction failed:
Key point: Idempotency protection only blocks successful transactions.

Scenario 4: New Transaction

Situation:
  1. User successfully records deposit
  2. Wants to record another deposit
Must generate new key:
Using same key would be rejected:

Error Handling

Missing Idempotency Key

Request without key:
Frontend must include key for all mutations.

Duplicate Key Error

Duplicate request detected:
What to do:
  1. Check if original transaction succeeded
  2. If succeeded: Don’t retry, show success message
  3. If failed: Retry with same key
  4. If creating new transaction: Generate new key

Frontend Implementation Example

Complete idempotency handling:

Retry Logic

Safe retry strategy:

Best Practices

Idempotency implementation checklist:Key generation:
  • ✅ Use UUID v4 (crypto.randomUUID())
  • ✅ Generate key in frontend before request
  • ✅ Store key in component state during request
  • ✅ Include key in all mutation API calls
  • ✅ Log keys for debugging (don’t expose to users)
Retry handling:
  • ✅ Reuse same key for network timeouts
  • ✅ Reuse same key for 5xx server errors
  • ✅ Generate new key for new operations
  • ✅ Check transaction status before retrying duplicates
  • ✅ Implement exponential backoff for retries
Error handling:
  • ✅ Detect duplicate errors (400/409 with “idempotency” message)
  • ✅ Verify original transaction status before showing error
  • ✅ Show “Transaction already recorded” instead of generic error
  • ✅ Prevent user confusion (don’t say “failed” if it succeeded)
UI/UX:
  • ✅ Disable submit button during request (prevent double-click)
  • ✅ Show loading spinner while processing
  • ✅ Clear form only after confirmed success
  • ✅ Handle network timeouts gracefully (retry automatically)
  • ✅ Provide clear error messages
Testing:
  • ✅ Test duplicate key rejection
  • ✅ Test network timeout retry
  • ✅ Test concurrent requests with same key
  • ✅ Test key uniqueness across operations

Troubleshooting

Q: Getting “idempotency-key header is required” error A: Add x-idempotency-key header to request:
All POST, PUT, DELETE requests for financial operations require this header.
Q: Getting “duplicate transaction” error immediately A: The idempotency key was already used. Either:
  1. Original transaction succeeded (check transaction history)
  2. Reusing key from previous operation (generate new key)
Solution:

Q: Need to record identical transactions A: Use different idempotency keys:
Idempotency prevents duplicate requests, not duplicate transactions.
Q: Transaction failed but can’t resubmit A: If transaction failed (not created), you can retry with same key:

Q: How long are keys valid? A: Idempotency keys are permanent. Once used successfully, they cannot be reused in that organization. Implication: Don’t use predictable keys (sequential numbers, dates alone).
Q: Can I use same key in different organizations? A: Yes. Unique constraint is (organizationId, idempotencyKey):

Recording Deposits

Deposit API with idempotency

Creating Loans

Loan API with idempotency

Period Closing

Period close with idempotency

Audit Trail

Track duplicate prevention