Limits & Rules

Centralised rules engine for deposits and withdrawals -- limits, KYC gates, responsible gambling, AML

Phase 3
Spec
Limits & Rules is a dedicated layer inside the Orchestration Layer that runs after the Request Router and before the Orchestrator selects a PSP. It evaluates every payment operation against an ordered set of rules and returns ALLOW or BLOCK with a reason code. No transaction reaches a PSP without passing through this layer. It is the single source of truth for business rules, compliance, and responsible gambling enforcement.
Never enforce limits only on the frontend. Client-side validation is UX only (prevents entering an invalid amount). Authoritative enforcement is always server-side. Frontend loads limit config on checkout open to display correct values -- but the server validates on every request.
1.

Purpose

Business limits

Min/max per transaction, daily/weekly/monthly deposit caps -- all configurable per brand and per payment method.

KYC & compliance gates

Block or redirect users who have not completed required identity verification before a deposit or withdrawal threshold.

Responsible gambling

Self-exclusion checks, cooling-off enforcement, player self-set deposit limits. Regulatory requirement in most licensed jurisdictions.

AML monitoring

Flag large or suspicious transactions for manual review without blocking the payment. Does not expose flags to the player.

Withdrawal protections

Balance sufficiency, wagering locks from active bonuses, max pending withdrawals cap.

Audit trail

Every evaluation is logged (allow and block) for compliance reporting and dispute resolution.

2.

Where it sits and how information flows

Position in the stack

1
Request Routervalidates schema, extracts JWT context
2
Limits & Rules Engine ← YOU ARE HEREevaluates all rules → ALLOW or BLOCK
3
Orchestratorselects PSP, applies cascade logic
4
Cascade Manager → Adapter → PSPexecutes the actual payment

Invocation flow

1
Request arrives from Request Router (user_id, brand_id, amount, currency, direction, method)
2
Load user context: KYC level, self-exclusion status, active limits, accumulated totals
3
Load brand config: min/max per method, daily/weekly/monthly caps, KYC thresholds
4
Evaluate rules in order (short-circuit on first BLOCK)
5
If ALLOW → pass to Orchestrator with enriched context
6
If BLOCK → return structured error to Request Router → 422 to frontend
7
Write audit log entry regardless of result

Input data and sources

user_id, brand_id, amount, currency, direction

from Request Router (JWT context)

KYC level, self-exclusion status, account status

users table (cached in Redis)

Min/max per method, KYC thresholds, brand caps

brand_limits table (cached)

Accumulated deposit/withdrawal sums (day/week/month)

transactions table aggregate (Redis cache)

Player self-set responsible gambling limits

player_limits table

Active bonus wager requirements, locked balance

Bonus Engine (async query)

Available balance (real money only, excl. bonus)

Wallet Engine (real-time)

Wallet Engine and Bonus Engine are called only when relevant: Wallet for withdrawal balance checks, Bonus Engine for wager lock checks. Do not call these services on deposit requests unnecessarily.

3.

Rules and limits required at launch

Rule evaluation order

1. Self-exclusion / cooling-off checkRegulatory. Must block first -- no exceptions.
2. Account status check (suspended / locked)Operational. Account must be active.
3. KYC gate checkCompliance. Required before financial activity.
4. Transaction min/max checkBusiness rule. Reject invalid amounts early.
5. Daily / weekly / monthly cap checkResponsible gambling + compliance.
6. Withdrawal: balance checkCannot withdraw more than available balance.
7. Withdrawal: wagering / bonus lock checkActive bonus wager must be completed first.
8. AML large transaction flagDoes not block; flags for monitoring queue.

Per-transaction limits (min / max)

RuleScopeExample valueConfigurable?
Min deposit per transactionbrand + method + currency + countrye.g. $10 (configurable)
Yes
Max deposit per transactionbrand + method + currency + countrye.g. $5 000 (configurable)
Yes
Min withdrawal per transactionbrand + method + currency + countrye.g. $20 (configurable)
Yes
Max withdrawal per transactionbrand + method + currency + countrye.g. $10 000 (configurable)
Yes
Manual approval threshold (withdrawal)brand + methode.g. > $2 000 (configurable)
Yes

Accumulation limits (caps)

RuleScopeValue / noteConfigurable?

Daily deposit cap

Required for gambling licenses in most jurisdictions

user + brandBrand default; overrideable by player (RG)
Yes

Weekly deposit cap

user + brandBrand default; overrideable by player (RG)
Yes

Monthly deposit cap

user + brandBrand default; overrideable by player (RG)
Yes

Max pending withdrawals

Prevents abuse: player creates many pending requests, then reverses

user1–3 (configurable)
Yes

AML reporting threshold

Triggers manual review or CTR flag; values set by regulator

transaction$10 000 (jurisdiction)
No (regulator)

KYC gates

GateThresholdActionDirection
No KYC (anonymous)Cumulative deposits < $XAllow deposit
Deposit
KYC Level 1 requiredCumulative deposits ≥ $XBlock deposit → redirect to KYC flow
Deposit
KYC required for all withdrawalsAny amountBlock withdrawal until KYC approved
Withdrawal
Enhanced Due Diligence (EDD)Single transaction > $X or monthly > $YFlag for manual review + request source of funds
Both

KYC threshold values ($X, $Y) are configured in brand_limits and depend on the brand's jurisdiction. They must be explicitly set at brand launch -- they are not inherited automatically.

Error codes and responses

CodeHTTPDescription (server)Player-facing message
SELF_EXCLUDED403User is self-excluded or on cooling-off periodPlayer sees: "Your account is temporarily restricted"
ACCOUNT_LOCKED403Account is suspended or locked by opsPlayer sees: "Please contact support"
KYC_REQUIRED422KYC verification required before this transactionPlayer sees: "Verify your identity to continue"
AMOUNT_BELOW_MINIMUM422Amount is below the configured minimumPlayer sees min amount in the UI
AMOUNT_ABOVE_MAXIMUM422Amount exceeds the configured maximumPlayer sees max amount in the UI
DAILY_LIMIT_REACHED422Player has hit their daily deposit capPlayer sees remaining cap + reset time
WEEKLY_LIMIT_REACHED422Player has hit their weekly deposit capPlayer sees remaining cap + reset time
MONTHLY_LIMIT_REACHED422Player has hit their monthly deposit capPlayer sees remaining cap + reset time
INSUFFICIENT_BALANCE422Withdrawal amount exceeds available balancePlayer sees current balance
WAGER_NOT_COMPLETE422Active bonus has an unfulfilled wagering requirementPlayer sees wager progress
MAX_PENDING_WITHDRAWALS422Player already has max pending withdrawal requestsPlayer sees pending count
HTTP 422 -- BLOCK response
Blocked
{
  "error": "DAILY_LIMIT_REACHED",
  "message": "You have reached your daily deposit limit.",
  "remaining_cap": 0,
  "reset_at": "2026-06-05T00:00:00Z",
  "limit_type": "daily_deposit",
  "brand_id": "brand_abc"
}
4.

Responsible Gambling (RG)

RG tools are an extension of the limits engine, not a separate module. They always take higher priority than business limits. RG rules are optional from an architecture perspective, but mandatory for licensed jurisdictions. The required set varies significantly by country and license type -- the platform must support all tools, and each operator enables what their license requires.

RG tool catalog

Self-exclusion

Temporary or permanent
Highest priority

Player blocks themselves from all deposits and gameplay for a defined period (1 day -- permanent). The platform must enforce this at the payment layer -- any deposit attempt returns SELF_EXCLUDED error. Exclusion cannot be reversed by the player during the active period -- only by operator support after a mandatory review process.

Mandatory in all licensed jurisdictions

Cooling-off period

Delay on limit increases
Highest priority

When a player wants to raise or remove a self-set deposit limit, the change does not take effect immediately -- it takes effect after a mandatory delay (typically 24--72 hours, jurisdiction-specific). This prevents impulsive reversal of protective limits. The player can cancel the increase request during the cooling-off window.

Required: UK, Sweden, Germany, Malta. Recommended everywhere

Player deposit limits

Daily / weekly / monthly cap

Player sets their own deposit caps -- these can only be decreased immediately; increases require cooling-off. Player limits always take precedence over brand limits -- if the brand daily cap is $500 and the player set $100, the effective cap is $100. Stored in player_limits table.

Mandatory: UK, Sweden, Germany, Malta. Optional: Curacao, Anjouan

Loss limits

Max net loss per period

Player sets a maximum net loss (deposits minus withdrawals) over a period. More complex than deposit limits -- requires tracking game outcomes, not just payment amounts. Calculated as: sum(deposits) - sum(withdrawals) over the rolling window. When the limit is reached, deposits are blocked for the remainder of the period.

Mandatory: UK (UKGC), Sweden. Optional: others

Affordability check

Income vs spend verification

Required by UK UKGC from Sept 2024 (Enhanced Affordability): operators must verify player income when cumulative deposits exceed thresholds (£500/month at tier 1, £150/month at tier 2 for Enhanced). Platform checks accumulated deposits and flags when KYC upgrade is required. Source documents: bank statements, pay slips.

Mandatory: UK (UKGC Enhanced). Not required in other jurisdictions

External exclusion list check

Third-party exclusion registry

Some jurisdictions maintain national self-exclusion registries. Operators must query these at player registration and optionally on each deposit. Integration is per-jurisdiction: GamStop (UK), OASIS (Germany), Spelpaus (Sweden), Spelinspektionen. The platform provides an IExclusionProvider interface -- each jurisdiction has its own adapter.

Mandatory if licensed in UK, Germany, Sweden. Not applicable elsewhere

Jurisdiction requirements matrix

Jurisdiction / LicenseSelf-excl.Cooling-offDep. limitsLoss limitsAfford. checkExt. registry
UK (UKGC)GamStop
Sweden (Spelinsp.)Spelpaus
Germany (GGL)OASIS
Malta (MGA)--
Estonia (MTA)--
Curacao / Anjouan--

Critical implementation rules

Self-exclusion is checked first -- before any other rule

Already rule #1 in the evaluation order. Cached in Redis (TTL 60s). A stale exclusion that allows a deposit through is a regulatory violation -- prefer false positives over false negatives.

Player limits can only be decreased immediately -- increases require cooling-off

Store requested_limit and effective_at separately. A background job applies the new (higher) limit when effective_at is reached. Player can cancel the pending increase at any time during cooling-off.

Self-exclusion cannot be reversed by the player directly

No "undo self-exclusion" button in the player UI. Reversal requires operator support intervention after a mandatory review and waiting period (jurisdiction-defined, typically 24h--7 days). The UI must make this clear at the moment of setting exclusion.

Operator cannot set limits more permissive than regulatory minimums

Example: Germany mandates a hard €1,000/month deposit cap. Operator brand_limits cannot exceed this regardless of what they configure. Platform enforces regulatory floors as a non-configurable layer below brand limits.

All RG events are logged immutably in player_rg_events

Every self-exclusion, limit change, cooling-off trigger, and RG block must be logged with timestamp, player_id, operator_id, and who triggered it (player or ops). This log is the primary evidence in regulatory audits and player dispute resolution.

DB schema additions

New table: player_rg_events (audit log)
player_rg_events (
  id            uuid PK,
  player_id     uuid FK,
  operator_id   uuid FK,
  brand_id      uuid FK,
  event_type    text,    -- SELF_EXCLUSION_SET | SELF_EXCLUSION_LIFTED
                         -- LIMIT_DECREASED | LIMIT_INCREASE_REQUESTED
                         -- LIMIT_INCREASE_APPLIED | LIMIT_INCREASE_CANCELLED
                         -- COOLING_OFF_STARTED | DEPOSIT_BLOCKED_RG
                         -- AFFORDABILITY_FLAG_RAISED | EXT_EXCLUSION_MATCH
  payload       jsonb,   -- event-specific detail (old value, new value, reason)
  triggered_by  text,    -- 'player' | 'ops:{user_id}' | 'system'
  created_at    timestamptz
  -- append-only: no updates, no deletes
)

-- Additions to player_limits:
ALTER TABLE player_limits ADD COLUMN
  requested_daily_deposit_cap    numeric,  -- pending increase, null if none
  requested_effective_at         timestamptz, -- when pending increase applies
  loss_limit_daily               numeric,  -- null if not set
  loss_limit_weekly              numeric,
  loss_limit_monthly             numeric,
  self_excluded_until            timestamptz, -- null if not excluded
  self_excluded_permanent        boolean DEFAULT false,
  affordability_threshold_reached boolean DEFAULT false,
  ext_exclusion_checked_at       timestamptz  -- last external registry check
5.

Best practices

Configuration

  • All limits stored in DB (brand_limits table), not hardcoded -- ops team must be able to change them without a deploy
  • Player self-set limits (responsible gambling) stored separately and can only be decreased, never increased without a cooling-off period
  • Default brand limits applied on brand creation -- never null in production

Performance

  • Cache user KYC level and self-exclusion status in Redis (TTL ~60s) -- these are hot-path reads
  • Pre-compute and cache accumulated totals (daily/weekly/monthly sums) -- recompute on each transaction, not on each check
  • Short-circuit on first BLOCK -- do not run all 8 checks if rule 1 already blocks
  • Total overhead target: < 5ms (rules evaluation only, excluding network)

Compliance & Audit

  • Write an audit log entry for every rule evaluation -- include: user_id, brand_id, direction, amount, rule triggered, result, timestamp
  • Audit logs must be immutable -- append-only, no updates. Required for regulatory audits
  • Self-exclusion changes: 24h propagation delay by law in some jurisdictions -- design accordingly
  • AML flags must not surface to the player -- send to internal monitoring queue silently

Frontend contract

  • Always return remaining_cap in the error payload for limit-related blocks -- frontend shows "You have $X left today"
  • Return reset_at timestamp for cap limits -- frontend shows countdown to reset
  • KYC_REQUIRED must include kyc_redirect_url so frontend can route without hardcoding paths
  • Pre-validate amounts client-side too (fetch limits on checkout load) -- but always enforce server-side; client is UX only
Minimum DB schema for Phase 3
-- Per-brand configurable limits
brand_limits (
  brand_id, method,
  min_deposit, max_deposit,
  min_withdrawal, max_withdrawal,
  daily_deposit_cap, weekly_deposit_cap, monthly_deposit_cap,
  kyc_threshold_deposit, kyc_threshold_withdrawal,
  max_pending_withdrawals
)

-- Player self-set responsible gambling limits (only decreaseable)
player_limits (
  user_id, brand_id,
  daily_deposit_cap, weekly_deposit_cap, monthly_deposit_cap,
  set_at, effective_at  -- effective_at = set_at + cooling_off_period
)

-- Append-only audit log
limits_audit_log (
  id, user_id, brand_id, direction, amount, currency,
  rule_triggered, result,  -- 'ALLOW' | 'BLOCK'
  error_code,              -- null if ALLOW
  created_at
)
DEPO44 | LIMITS & RULES SPEC v1 | PHASE 3