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¶
- Compliance approves a Sumsub-passed KYC. Sumsub webhook flips
UserKyc.verificationPending=false; compliance double-checks the docs in/admin/kyc/:userId/info. If satisfied, callsPOST /admin/kyc/:userId/info { level: 2, verificationPending: false }. Player gains higher withdraw limit. - 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. - 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. - 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. - Block a country. Compliance reports new sanctioned jurisdiction. Open
/country-restrictions, find country code, toggle restriction. Backend updatesCountry.restricted=true. New registrations and existing players from that country are blocked at login (depends on geo-IP, seecountry.service.ts).
Edge cases / gotchas¶
PATCH /admin/kyc/:userIdandPOST /admin/kyc/:userId/infodiffer. 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
DELETEon 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-blockper 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. UserKycincludes 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.
Cross-links¶
- Per-user KYC actions on profile: user-profile.md → KYC tab
- KYC swap recipe:
recipes/swap-kyc-provider.md - Sumsub integration:
apps/api/src/kyc/ - Per-user gambling limits source:
libs/_prisma/src/schema/api.prisma(searchUserGamblingLimit) - Customer-comms templates for self-exclusion:
handover/customer-comms/ - Withdrawals approval cross-check: withdrawals.md
- Country restrictions:
/country-restrictionspage (covered inline above) - Audit: admin-logs.md
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