Rate Limiting

Velocity checks and abuse protection at the API Gateway layer

Phase 2
Spec
Rate Limiting middleware sits in the API Gateway right after Auth. It blocks abuse before the request reaches the orchestrator. All checks are atomic and stored in Redis. Two protection layers: IP-level (DDoS and bot protection) and user-level (velocity checks for deposits/withdrawals).
Velocity limits on deposit amount (deposit_total_24h) are configurable per brand via the psp_configs table. Defaults are set at brand initialization. For Phase 2, hard-coded in config.
1.

Request Flow

1
Request arrives at API Gateway
2
Auth middleware validates JWT → extracts user_id + brand_id
3
Rate limiter middleware runs checks (IP → user velocity → amount)
4
If any check fails → return HTTP 429 with retry_after
5
All checks pass → request forwarded to Request Router
Middleware runs synchronously, before any business logic. Overhead < 2ms with Redis.
2.

Velocity Rules

CheckWindowLimitScopeAction
Deposit attempts
1 minute
5 per useruser_idBlock for 5 min
Deposit attempts
1 hour
20 per useruser_idBlock for 1 hour
Withdrawal attempts
1 hour
3 per useruser_idBlock for 2 hours
Failed payment attempts
15 minutes
3 per useruser_idRequire re-auth / captcha
Deposit total amount
24 hours
Configurable per branduser_id + brand_idSoft block (KYC gate)
Requests per IP
1 minute
60 requestsIP addressHTTP 429 + exponential backoff
3.

HTTP 429 Response

Response Fields

FieldTypeDescription
errorstringMachine-readable error code: "RATE_LIMIT_EXCEEDED"
messagestringHuman-readable description of why the request was blocked
retry_afternumberSeconds until the client may retry
block_typestring"velocity" | "ip" | "amount" — which check triggered

Example Response

HTTP 429
Too Many Requests
{
  "error": "RATE_LIMIT_EXCEEDED",
  "message": "Too many deposit attempts.
    Please wait before retrying.",
  "retry_after": 300,
  "block_type": "velocity"
}

The Retry-After header should also be set in the HTTP response (RFC 6585). Frontend should read retry_after and show a countdown timer.

4.

Counter Storage

Redis (recommended)
Recommended

Pros

  • Sub-millisecond counter increments with INCR + EXPIRE
  • Atomic sliding window via sorted sets (ZADD / ZCOUNT)
  • TTL-based expiry — counters auto-clean
  • Shared state across multiple API replicas

Cons

  • External dependency to deploy and monitor

Redis pattern for sliding window:

ZADD rate:deposit:{user_id} {now_ms} {now_ms}
ZREMRANGEBYSCORE rate:deposit:{user_id} 0 {now_ms - window_ms}
count = ZCARD rate:deposit:{user_id}
EXPIRE rate:deposit:{user_id} {window_seconds}
In-memory (per-instance)

Pros

  • Zero infrastructure overhead
  • No network latency

Cons

  • Limits not shared across API replicas — user can bypass by hitting different instances
  • State lost on restart
PostgreSQL

Pros

  • No extra infra — reuse existing DB
  • Durable audit log of rate-limit events

Cons

  • Slower than Redis for hot-path counters (extra round-trip per request)
  • Write-heavy workload on counters at scale
DEPO44 | RATE LIMITING SPEC v1 | PHASE 2