Join the contributor Telegram: https://t.me/+DOylgFv1jyJlNzM0
Why this matters
/api/loans/:loanId/repay/build and similar write endpoints do not provide an idempotency cache hit response that tells the client "this was already processed". Today, when a client retries with the same Idempotency-Key, it does receive a cached result but without a clear X-Idempotent-Replayed: true header. Adding it lets the frontend distinguish "first success" from "retried" so the UI can de-duplicate toasts and avoid double-counting transactions.
Acceptance criteria
Files to touch
backend/src/middleware/idempotency.ts
backend/src/middleware/__tests__/idempotency.test.ts
docs/wiki/api-idempotency.md (new) or short note in an existing wiki page
Out of scope
- Changing the cache backend or TTL.
- Replaying side effects (the current design is response-only replay, which is correct).
Join the contributor Telegram: https://t.me/+DOylgFv1jyJlNzM0
Why this matters
/api/loans/:loanId/repay/buildand similar write endpoints do not provide an idempotency cache hit response that tells the client "this was already processed". Today, when a client retries with the sameIdempotency-Key, it does receive a cached result but without a clearX-Idempotent-Replayed: trueheader. Adding it lets the frontend distinguish "first success" from "retried" so the UI can de-duplicate toasts and avoid double-counting transactions.Acceptance criteria
idempotencyMiddleware, when a cached entry is found, set response headerX-Idempotent-Replayed: truebefore forwarding the cached body.X-Idempotent-Replayed: false.docs/wiki/orREADMEfor API consumers.Files to touch
backend/src/middleware/idempotency.tsbackend/src/middleware/__tests__/idempotency.test.tsdocs/wiki/api-idempotency.md(new) or short note in an existing wiki pageOut of scope