This implementation provides webhook idempotency using Redis to prevent duplicate transaction processing when webhooks are delivered multiple times due to network retries.
- Webhooks must include an
X-Idempotency-Keyheader (typically theanchor_transaction_id) - This key uniquely identifies each webhook request
- Client sends webhook with
X-Idempotency-Key: transaction-123 - Middleware checks Redis for key
idempotency:transaction-123 - Key doesn't exist → Set key to "PROCESSING" with 5-minute TTL
- Process the webhook normally
- On success (2xx response) → Store response in Redis with 24-hour TTL
- On failure → Delete the key to allow retry
- Client sends same webhook while first is still processing
- Middleware finds key with value "PROCESSING"
- Return
429 Too Many Requestswith retry-after header - Client should wait and retry
- Client sends same webhook after successful processing
- Middleware finds key with cached response
- Return cached response (200 OK) with
cached: trueflag - No duplicate processing occurs
- Processing Lock: 5 minutes (prevents stuck locks from failed requests)
- Completed Response: 24 hours (prevents duplicate processing within reasonable window)
REDIS_URL=redis://localhost:6379Redis is automatically configured in docker-compose.yml:
redis:
image: redis:7-alpine
ports:
- "6379:6379"curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: anchor-tx-12345" \
-d '{
"id": "webhook-001",
"anchor_transaction_id": "anchor-tx-12345"
}'{
"success": true,
"message": "Webhook webhook-001 processed successfully"
}Status: 200 OK
{
"error": "Request is currently being processed",
"retry_after": 5
}Status: 429 Too Many Requests
{
"cached": true,
"message": "Request already processed"
}Status: 200 OK
-
IdempotencyService (
src/middleware/idempotency.rs)- Manages Redis connections
- Provides methods for checking and storing idempotency state
- Handles lock acquisition and release
-
Idempotency Middleware (
src/middleware/idempotency.rs)- Axum middleware that wraps webhook handlers
- Extracts idempotency key from headers
- Coordinates request flow based on idempotency status
-
Webhook Handler (
src/handlers/webhook.rs)- Business logic for processing webhooks
- Protected by idempotency middleware
idempotency:{anchor_transaction_id} → "PROCESSING" | CachedResponse
- Start services:
docker-compose up -d- Send first request:
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: test-123" \
-d '{"id": "w1", "anchor_transaction_id": "test-123"}'- Send duplicate immediately (should get 429):
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: test-123" \
-d '{"id": "w1", "anchor_transaction_id": "test-123"}'- Wait a few seconds and send again (should get cached response):
curl -X POST http://localhost:3000/webhook \
-H "Content-Type: application/json" \
-H "X-Idempotency-Key: test-123" \
-d '{"id": "w1", "anchor_transaction_id": "test-123"}'docker exec -it synapse-redis redis-cli
> KEYS idempotency:*
> GET idempotency:test-123
> TTL idempotency:test-123- Middleware fails open (allows request to proceed)
- Logs error for monitoring
- Prevents Redis outage from blocking all webhooks
- Processing lock expires after 5 minutes
- Allows retry if original request failed/hung
- Prevents permanent lock from crashed requests
- Key Validation: Idempotency keys are validated for proper format
- TTL Limits: Keys automatically expire to prevent Redis memory exhaustion
- Fail Open: Redis failures don't block legitimate requests
- No Sensitive Data: Only status codes and success flags stored in Redis
- Response Body Caching: Store full response body for exact replay
- Distributed Locking: Use Redlock algorithm for multi-instance deployments
- Metrics: Track duplicate request rates and cache hit ratios
- Configurable TTLs: Make TTL values configurable per environment