You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Background .env.example documents ACTIVITY_STORAGE_MODE (memory default, or file to "persist webhook IDs across local restarts") and ACTIVITY_STORAGE_DIR (default .guildpass-activity), explicitly described as "Webhook activity idempotency storage." This is the mechanism preventing the same incoming webhook (member.joined, pass.activated, etc.) from being processed twice.
Problem
Both memory and file idempotency stores are single-instance by design: memory is lost on restart, and file writes to local disk, which does not work correctly if the dashboard is deployed across multiple server instances or serverless/ephemeral-filesystem environments — a duplicate webhook delivered to a different instance than the one that first processed it would not be recognized as a duplicate, breaking the "idempotency" guarantee the naming implies.
Expected outcome
The webhook idempotency store is defined behind an interface (e.g. IdempotencyStore with hasSeen(id)/markSeen(id, ttl)), with memory and file kept as local-dev implementations, plus a new implementation suitable for multi-instance deployments (e.g. backed by the durable Postgres storage from Issue #6, or a documented Redis-compatible option) selectable via ACTIVITY_STORAGE_MODE.
Suggested implementation
Extract the current idempotency logic (wherever it lives, e.g. near the webhook handler) into a small interface with pluggable backends.
Add a durable (or postgres) mode that stores seen webhook IDs with a TTL/expiry column, reusing the Postgres connection if Issue Add a server-wide role sync command for admins #6 is implemented, or with its own minimal table otherwise.
Ensure TTL-based cleanup doesn't require a background cron — expire lazily on lookup or via a scheduled cleanup query, whichever fits the existing architecture better, and document the choice.
Update .env.example and CONTRIBUTING.md to describe the new mode and when to use it.
Acceptance criteria
memory and file modes retain their exact current behavior for local development (no regression).
A new multi-instance-safe mode correctly rejects a duplicate webhook ID even when the "duplicate" request is simulated as coming from a second, independent process/instance.
Idempotency records expire (or are cleaned up) after a documented retention window rather than growing unboundedly.
Tests cover: first-seen acceptance, duplicate rejection, and expiry behavior for each backend.
pnpm --filter @guildpass/dashboard test and pnpm typecheck pass.
Difficulty: Advanced
Type: Refactor
Background
.env.exampledocumentsACTIVITY_STORAGE_MODE(memorydefault, orfileto "persist webhook IDs across local restarts") andACTIVITY_STORAGE_DIR(default.guildpass-activity), explicitly described as "Webhook activity idempotency storage." This is the mechanism preventing the same incoming webhook (member.joined,pass.activated, etc.) from being processed twice.Problem
Both
memoryandfileidempotency stores are single-instance by design:memoryis lost on restart, andfilewrites to local disk, which does not work correctly if the dashboard is deployed across multiple server instances or serverless/ephemeral-filesystem environments — a duplicate webhook delivered to a different instance than the one that first processed it would not be recognized as a duplicate, breaking the "idempotency" guarantee the naming implies.Expected outcome
The webhook idempotency store is defined behind an interface (e.g.
IdempotencyStorewithhasSeen(id)/markSeen(id, ttl)), withmemoryandfilekept as local-dev implementations, plus a new implementation suitable for multi-instance deployments (e.g. backed by thedurablePostgres storage from Issue #6, or a documented Redis-compatible option) selectable viaACTIVITY_STORAGE_MODE.Suggested implementation
durable(orpostgres) mode that stores seen webhook IDs with a TTL/expiry column, reusing the Postgres connection if Issue Add a server-wide role sync command for admins #6 is implemented, or with its own minimal table otherwise..env.exampleandCONTRIBUTING.mdto describe the new mode and when to use it.Acceptance criteria
memoryandfilemodes retain their exact current behavior for local development (no regression).pnpm --filter @guildpass/dashboard testandpnpm typecheckpass.Likely affected files/directories
Webhook handler route (
apps/dashboard/app/api/webhook*/), new idempotency-store module (e.g.apps/dashboard/lib/idempotency/),.env.example,CONTRIBUTING.md.