Skip to content

KYC limits

Purpose

The KYC Limits Management screen has two responsibilities: (1) configure the platform-wide KYC tier limits (deposit / withdrawal / wagering caps per Sumsub level 0-3), and (2) override an individual player's KYC level / verification-pending status. Per-user gambling limits and self-exclusions live in the same area.

Audience

Compliance (primary), risk, customer support (escalate to compliance, not act).

Path in admin-fe

Screen URL Page
KYC limits config /kyc-limits-management ebit-admin-fe/src/app/(dashboard)/kyc-limits-management/page.tsx
Per-user KYC override drawer on /users/[id] covered in user-profile.md
Country restrictions /country-restrictions ebit-admin-fe/src/app/(dashboard)/country-restrictions/page.tsx

Backing API endpoints

Endpoint Source
GET /admin/kyc/:userId/info (per-user info + last 5 Sumsub requests) apps/api/src/kyc/controller/admin.kyc.controller.ts:37
PATCH /admin/kyc/:userId (admin edits KYC fields) apps/api/src/kyc/controller/admin.kyc.controller.ts:46
POST /admin/kyc/:userId/info (set level + verification pending) apps/api/src/kyc/controller/admin.kyc.controller.ts:59
POST /admin/user-limits/user-exclusion (create self-exclusion override) apps/api/src/users-limits/admin/admin.user-limits.controller.ts:32
DELETE /admin/user-limits/user-exclusion apps/api/src/users-limits/admin/admin.user-limits.controller.ts:41
GET /admin/user-limits/user-exclusion/:id apps/api/src/users-limits/admin/admin.user-limits.controller.ts:50
POST /admin/user-limits/gambling-limits apps/api/src/users-limits/admin/admin.user-limits.controller.ts:59
GET /admin/user-limits/gambling-limits/:id apps/api/src/users-limits/admin/admin.user-limits.controller.ts:68
GET /admin/country (list with stats) apps/api/src/country/admin.country.controller.ts:34
GET /admin/country/stats apps/api/src/country/admin.country.controller.ts:44
PATCH /admin/country/:code (toggle restriction) apps/api/src/country/admin.country.controller.ts:52

Frontend wiring: ebit-admin-fe/src/queries/kyc-limits-management/, queries/users/index.ts (per-user overrides), queries/countries/.

Key actions

Action Required permission API call DB tables touched Audit-logged?
View per-user KYC info kyc.view GET /admin/kyc/:userId/info UserKyc, last 5 KycRequest (Sumsub) yes
Edit user KYC fields (DOB, name, address) kyc.edit PATCH /admin/kyc/:userId UserKyc yes
Set KYC level / pending flag kyc.edit POST /admin/kyc/:userId/info UserKyc.level, UserKyc.verificationPending yes
Set self-exclusion (timed lockout) user-limit.edit POST /admin/user-limits/user-exclusion UserExclusion yes
Lift self-exclusion user-limit.delete DELETE /admin/user-limits/user-exclusion UserExclusion (deleted / expired) yes
Set gambling limits (deposit / wager / loss / time) user-limit.edit POST /admin/user-limits/gambling-limits UserGamblingLimit yes
Toggle country restriction geo.edit PATCH /admin/country/:code Country.restricted yes
List restricted countries geo.view GET /admin/country Country yes

Filters and views

  • KYC level filter — 0 (none), 1 (basic), 2 (intermediate), 3 (full / sourced-of-funds).
  • Pending verification — Sumsub returned but admin hasn't accepted yet.
  • Limits type — deposit / wager / loss / time-spent / loss-per-session.
  • Self-exclusion duration — 24h / 7d / 30d / 6m / 12m / permanent.
  • Country search.

Common workflows

  1. Compliance approves a Sumsub-passed KYC. Sumsub webhook flips UserKyc.verificationPending=false; compliance double-checks the docs in /admin/kyc/:userId/info. If satisfied, calls POST /admin/kyc/:userId/info { level: 2, verificationPending: false }. Player gains higher withdraw limit.
  2. Force a player to re-KYC. Compliance discovers stale KYC docs (> 12 months). POST /admin/kyc/:userId/info { verificationPending: true, level: previous }. Player must redo the Sumsub flow.
  3. Player requests self-exclusion. CS opens the user's profile (drawer), creates a self-exclusion via POST /admin/user-limits/user-exclusion { duration: 30d }. Player is locked out of the entire platform for 30 days.
  4. Set gambling limits voluntarily on player request. From profile, set deposit limit = 500 USD/week. Backend writes UserGamblingLimit. Bet-place / deposit-place flows reject over-cap.
  5. Block a country. Compliance reports new sanctioned jurisdiction. Open /country-restrictions, find country code, toggle restriction. Backend updates Country.restricted=true. New registrations and existing players from that country are blocked at login (depends on geo-IP, see country.service.ts).

Edge cases / gotchas

  • PATCH /admin/kyc/:userId and POST /admin/kyc/:userId/info differ. PATCH edits user metadata fields; POST flips level / pending status. Using the wrong one looks like a no-op.
  • Self-exclusion is not undoable for some durations. Per-jurisdiction: 6m+ self-exclusions in regulated markets cannot be lifted by ops. Engineering prevents DELETE on those rows.
  • Per-user KYC level overrides the platform-wide cap. If platform allows L1 to withdraw $500/day but admin sets a specific user's per-user limit lower, the lower wins.
  • Country toggle is immediate. Existing logged-in sessions are NOT auto-revoked. They die at next refresh / re-auth.
  • Sumsub "rejected" status doesn't auto-block deposits. It blocks withdrawals only. Deposits continue. To block deposits, set a withdrawals-block per user-profile.md plus a country restriction.
  • Limits are evaluated per-window. Deposit limit "500 USD/week" rolls forward 7 days from each deposit, not Mon-Sun calendar. Behavior pinned in users-limits.service.ts.
  • UserKyc includes only the last 5 Sumsub requests as a join (per controller summary). Older artifacts are in Sumsub itself.
  • Replacing a Sumsub provider — see recipes/swap-kyc-provider.md. Country restrictions and per-user overrides survive a provider swap; raw KYC docs do not.

Sequence — KYC level approval (admin override after Sumsub pass)

sequenceDiagram
    actor compliance
    participant admin-fe
    participant api
    participant sumsub as Sumsub
    participant pg as Postgres
    Note over sumsub,pg: webhook earlier flipped verificationPending=true with new docs
    compliance->>admin-fe: open /users/[id], KYC tab
    admin-fe->>api: GET /admin/kyc/:userId/info
    api->>api: PermissionGuard('kyc.view')
    api->>pg: SELECT UserKyc + last 5 KycRequest
    pg-->>api: data
    api-->>admin-fe: docs + Sumsub state
    compliance->>admin-fe: review, click Approve, level=2
    admin-fe->>api: POST /admin/kyc/:userId/info { level: 2, verificationPending: false }
    api->>api: PermissionGuard('kyc.edit')
    api->>pg: UPDATE UserKyc SET level=2, verificationPending=false
    api->>pg: INSERT AdminActionLog
    api-->>admin-fe: UserKycDto
    admin-fe-->>compliance: now eligible for higher withdraw limit