Appearance
Dynamic segments
Segments are rule-based audiences. Instead of tagging players by hand, an operator describes who belongs — "players from Germany who deposited more than $100" — and the platform keeps that group up to date as people qualify or fall out. Segments feed targeting for bonuses and campaigns, and they update in response to real events rather than an overnight batch.
The segment tools live at /segments in the admin panel.
The segment list
The list shows the dynamic segments that exist, each with its id, name, a color used to recognize it at a glance, and a live count of the players inside it.

A search box filters by name, and the count moves on its own as players qualify, so a segment that's growing is visible at a glance.
Creating a segment
A segment needs a name and a color; the color is purely for recognition in lists and on player profiles. Description and status are optional, though status matters in practice — an inactive segment stops taking on new players.

Defining the rules
The rule builder is where a segment gets its meaning. Conditions are grouped, and you choose how they combine: conditions within a group join with AND or OR, and groups themselves combine with AND or OR at the top. That two-level structure is enough to express things like "(German OR Austrian) AND has deposited."

Each condition is built on a trigger — a player attribute or a computed metric. The available triggers cover identity, money, and behavior:
| Trigger | Shape | What it matches |
|---|---|---|
| Language, country | Pick from a list | Locale or country |
| Registration date (calendar) | Date range | Fixed signup window (does not slide) |
| Days since registration | Numeric range | Account age in whole days relative to now (e.g. 0–7 for NEW_REG) |
| Loyalty level, VIP status | Pick from a list | Program standing |
| Currency | Pick from a list | Wallet currencies held |
| Partner id, first game, active bonus | Pick from a list | Attribution and activity |
| Deposit sum, deposit count, average deposit | Numeric range | Deposit behavior (USD) |
| Balance, in-out, GGR, NGR, RTP | Numeric range | Financial position (USD / %) |
| Duplicate, has PWA, has free spins | Yes / no | Account flags |
| Email verified, phone verified | Yes / no | Verification state |
Use Days since registration for rolling cohorts such as “registered in the last 7 days” (from: 0, to: 7). The engine compares account age in whole days against “now”, so the window keeps moving. Registration date (calendar) stays for fixed historical cohorts (signup between two ISO timestamps) and will not slide on its own.
A couple of behaviors are worth knowing because they prevent surprises: a segment with no conditions matches nobody (it won't accidentally sweep in every player), and a trigger the engine doesn't recognize simply doesn't match.
The members list
Opening a segment shows the players currently inside it, paged.

On an individual player's profile, the same membership appears on their Segments tab, marked dynamic when the rules put them there or manual when an operator did.
How membership stays current
This is the part that makes segments "dynamic." Membership is kept fresh in three ways:
- Lifecycle events — after registration, a completed deposit, a settled game session, the PWA flag being set, an admin editing the profile, or KYC approval, the platform re-evaluates that player and adjusts their segments within seconds.
- Hourly full sweep — the API scheduler (
maintenance.segment) enqueues a full recalculation for every active dynamic segment each hour. That catches time-based rules (days since registration, rolling deposit windows) and idle players who have no fresh lifecycle event. - Admin actions — creating a segment, changing its rules or status, or pressing Recalculate enqueues a durable full recalculation (not an in-request sync scan).
The scheduler process must be running (pnpm --filter api start:scheduler) or event jobs and the hourly sweep will not drain. Segment catalog updatedAt reflects definition edits only — not membership churn.
Deleting a segment is blocked (HTTP 409) while any bonus, quest, wheel, shop product, cashback campaign, notification campaign, or cashier method still lists that segment key (include or exclude lists). Remove the reference from those configs first; otherwise the delete would leave dangling targeting links. Write-time validators reject unknown segment keys on create/update. Keep the consumer manifest in packages/shared/src/segments-domain/consumers.ts synchronized when adding a new segment-consuming domain.
Editing and recalculating
- Editing the rules or status of a live segment enqueues a background full recalculation automatically (same path as create).
- Recalculate queues another full pass for that segment — useful after a bulk data fix when you do not want to wait for the hourly sweep.
Both require players.edit; viewing a segment and its members only needs players.view. Member counts update as the background job finishes; refresh the detail page to see the new total.
Where it lives in the code
For anyone tracing behavior: the trigger catalog is in packages/shared/src/segments-domain, rule evaluation in packages/db/src/operations/segment/evaluate-logic.ts, membership in .../segment/membership.ts, the job engine in packages/wallet/src/jobs/segment-engine.ts, and the hourly schedule in apps/api/src/scheduler/schedules.ts. The admin screens are under apps/admin/app/(protected)/segments/.