Cactus v10 — Architecture

Cactus v10 — Architecture Reference

Plataforma multi-tenant iGaming 100% Cloudflare edge. Target: 100 brands, 30M usuários, 400M tx/dia.

Workers ativos
33+
D1 databases
5
Durable Objects
3
Queues
10+

1. Topologia global

Tudo no edge Cloudflare. Apex Worker é o único entry-point externo; faz service bindings pra todos os Workers especializados.

graph TB subgraph "Edge (player)" Browser[Player Browser/PWA] --> CFNET[Cloudflare CDN + WAF] end CFNET --> Apex[igaming-apex Worker] subgraph "Apex Middlewares" Apex --> RL[Rate Limit KV] Apex --> Geo[Geo Block per-tenant] Apex --> Tenant[Tenant detection] end Apex -->|service bindings| Auth[igaming-auth] Apex --> Wallet[igaming-wallet] Apex --> Games[igaming-games + CrashDO] Apex --> Casino[igaming-casino-orchestrator] Apex --> Payments[igaming-payments] Apex --> Push[igaming-push] Apex --> Comms[igaming-comms] Apex --> Chat[igaming-livechat + ChatDO] Apex --> Social[igaming-social] Apex --> Reports[igaming-reports] Apex --> KYC[igaming-kyc] Apex --> Bonus[igaming-bonus] Apex --> VIP[igaming-vip] Apex --> RG[igaming-rg] Apex --> Affiliate[igaming-affiliate] Apex --> WalletDO[igaming-wallet-do Worker] Apex --> GDPR[igaming-gdpr] subgraph "Storage Layer" D1Global[(D1 platform-main)] D1Ledger[(D1 player-ledger)] D1Brands1[(D1 cactus-brands-s1)] D1Brands2[(D1 cactus-brands-s2)] D1LedgerS1[(D1 cactus-ledger-s1)] R2Iceberg[(R2 cactus-iceberg)] R2Exports[(R2 cactus-player-exports)] KV[(CONFIG_KV + SESSIONS_KV + RATELIM_KV)] end Wallet --> D1Ledger WalletDO -.-> D1LedgerS1 Games --> D1Ledger Auth --> D1Global subgraph "Async Pipeline" Q1[Queue igaming-events] Q2[Queue igaming-d1-writes] Q3[Queue igaming-payments-events] Batcher[igaming-d1-batcher] Archiver[igaming-archiver cron] Consumer[analytics-consumer] AE[Analytics Engine] end WalletDO --> Q2 --> Batcher --> D1Ledger Wallet --> Q1 Games --> Q1 Q1 --> Consumer --> AE Q1 --> D1Global Payments --> Q3 Archiver -.->|nightly 03:00 UTC| R2Iceberg style Apex fill:#5865f2,color:#fff style WalletDO fill:#22c55e,color:#000 style R2Iceberg fill:#f59e0b,color:#000

2. Workers index

Inventário completo. URLs internas via service binding; URLs externas via apex proxy.

WorkerFunçãoBindingsEndpoints públicos
igaming-apexEntry point, WAF, rate limit, geo, proxy+15 service bindings, CONFIG_KV, RATELIM_KVevian.bet/*
igaming-authJWT, login, OTP, password resetD1, CONFIG_KV, SESSIONS_KV, EVENTS_QUEUE/api/auth/*
igaming-walletWallets, balances, transfersD1, EVENTS_QUEUE/api/wallet/*
igaming-wallet-doWalletDO single-writer per playerWALLET_DO (DO), SYNC_QUEUE, EVENTS_QUEUE/wallet-do/{id}/{op}
igaming-gamesCrash/Mines/Tower + CrashRoundDOCRASH_DO, WALLET_SERVICE, EVENTS_QUEUE/api/games/*
igaming-casino-orchestratorCasino aggregator frameworkD1, WALLET_SERVICE, EVENTS_QUEUE/api/casino/*
igaming-paymentsPayment adapter frameworkD1, WALLET_SERVICE, PAYMENT_EVENTS_QUEUE/api/payments/*
igaming-pushVAPID push notificationsD1, CONFIG_KV/push/*
igaming-commsEmail (Resend) + SMS (Twilio) + WhatsAppD1, CONFIG_KV, SESSIONS_KV/comms/*
igaming-livechatWebSocket chat + DM + ChatDOCHAT_DO (DO), D1/api/chat/*
igaming-socialFeed, posts, likes, commentsD1, EVENTS_QUEUE/api/social/*
igaming-reportsAnalytics, BI reports MCP-shapeD1, AE (Analytics Engine)/api/reports/*
igaming-kycKYC tiers via SumsubD1, R2 (kyc docs), EVENTS_QUEUE/api/kyc/*
igaming-bonusBonus engine + wagering trackerD1, WALLET_SERVICE, EVENTS_QUEUE/api/bonus/*
igaming-vipVIP tiers + loyalty pointsD1, EVENTS_QUEUE/api/vip/*
igaming-rgResponsible Gaming limitsD1, EVENTS_QUEUE/api/rg/*
igaming-affiliateReferral + CPA + RevShareD1, EVENTS_QUEUE/api/affiliate/*
igaming-gdprData export + right-to-deletionD1, R2 exports, cron daily/api/me/data-export
igaming-backofficeAdmin auth + tenants + auditD1, CONFIG_KV/api/bo/*
igaming-monitorStatus page + uptime cronD1, CONFIG_KV, cron 2min/monitor/status
igaming-d1-batcherQueue → batched INSERT D15x D1 (all shards), CONFIG_KVqueue consumer
igaming-archiverD1 hot → R2 Iceberg cron5x D1, R2, cron 03:00 UTC/archive/*
igaming-do-backupWalletDO snapshot weeklyWALLET_DO_SERVICE, R2, cron SUN 04:00/backup/*
igaming-analytics-consumerQueue events → D1 + AED1, AEqueue consumer
igaming-kanban-apiKanban backend (boards/cards)D1, CONFIG_KV/api/kanban/*

3. Request flow — apex middleware stack

Toda request HTTP entra pelo apex e passa por 4 camadas antes de chegar no Worker downstream.

sequenceDiagram participant Client participant CF as Cloudflare CDN+WAF participant Apex as igaming-apex Worker participant DS as Downstream Worker participant KV as CONFIG_KV / RATELIM_KV Client->>CF: HTTPS request CF->>CF: Zone WAF rules
(scanner UA, threat_score) CF->>Apex: forward Apex->>KV: rate limit check (KV-backed sliding window) alt rate limit excedido KV-->>Apex: 429 Apex-->>Client: 429 Too Many Requests end Apex->>KV: tenant lookup (hostname → tenant_id) Apex->>KV: geo-block check (CF-IPCountry vs tenant.restricted_countries) alt geo blocked Apex-->>Client: 451 Unavailable For Legal Reasons end Apex->>Apex: enrich headers (X-CF-Country/City/ASN) Apex->>DS: service binding fetch (proxyReq) DS-->>Apex: response Apex-->>Client: response + CORS headers

4. Crash WebSocket — bet + cashout em ~30ms

Player abre WS persistente. Bet/cashout viajam via WS já aberto (sem TCP handshake) com ACK direto pro socket. Wallet credit em waitUntil.

sequenceDiagram participant P as Player Browser participant A as igaming-apex participant G as igaming-games participant DO as CrashRoundDO participant W as igaming-wallet P->>A: GET /api/games/crash/connect?token=JWT (WS Upgrade) A->>G: forward WS G->>G: getPlayer(req) → decode JWT G->>DO: stub.fetch(ws/?pid=PLAYER_ID) DO->>P: state + history (initial) Note over DO,P: WS established, tag=PLAYER_ID loop Round em "running" (a cada 200ms) DO->>P: {type: tick, multiplier, elapsed_ms} end P->>DO: {type: bet, client_id, amount, currency} DO->>DO: validate phase, amount, pid (not "anon") DO->>W: walletDebit(pid, amount) via WALLET_SERVICE W-->>DO: success DO->>DO: _doBet → INSERT bets, _bcast bet_placed DO->>P: {type: bet_ack, client_id, success: true, bet_id} Note over P: UI mostra bet ativo IMEDIATO P->>DO: {type: cashout, client_id, bet_id} DO->>DO: _doCashout síncrono → calcula multiplier no instant DO->>P: {type: cashout_ack, multiplier, win_amount} Note over P: UI mostra "saiu em 2.34x" sem esperar wallet DO->>W: walletCredit (em ctx.waitUntil, async)

5. WalletDO sync pipeline

Single-writer per player. SQLite local no DO (consistency forte). Sync periódico pra D1 via queue.

graph LR Player[Player] -->|/bet /credit| DO[WalletDO instance] DO -->|local SQLite| LocalDB[(SQLite storage)] DO -->|alarm 60s OR 100 ops dirty| Sync[forceSync] Sync -->|sendBatch| Q[Queue igaming-d1-writes] Q --> Batcher[igaming-d1-batcher] Batcher -->|INSERT OR IGNORE batch| D1[(D1 LEDGER_DB)] cron[Cron weekly SUN 04:00] -.->|do-backup| Backup[igaming-do-backup] Backup -->|service binding| DO Backup -->|gzip + put| R2[(R2 cactus-iceberg/wallet-do/)] style DO fill:#22c55e,color:#000 style D1 fill:#5865f2,color:#fff style R2 fill:#f59e0b,color:#000

6. Payment routing engine

Adapter pattern + routing por priority+weight+circuit breaker+failover. 7 providers no catálogo (mock, pix-direct, stripe, mercadopago, btag, coinbase, nowpayments).

flowchart TD Start([Player POST /api/payments/deposit]) --> Q1{tenant_payment_config} Q1 -->|filter currency+country+method| Cand[Candidates list] Cand -->|ORDER BY priority| Sorted[Sorted by priority] Sorted -->|same priority?| AB[Apply weight A/B] AB --> CB{Circuit Breaker check} CB -->|open| Skip[Skip provider] Skip --> Next{Next candidate?} Next -->|yes| AB Next -->|no| Fail([Return no_provider_available]) CB -->|closed| Call[adapter.createDeposit] Call -->|success| Done([Return instructions+qr+url]) Call -->|fail| FB{failover_to set?} FB -->|yes| Explicit[Use explicit failover] FB -->|no| Next Explicit --> CB Done -.->|webhook from PSP| Webhook[POST /webhook/:provider] Webhook --> Normalize[adapter.parseWebhook] Normalize --> Queue[(igaming-payments-events)] Queue --> Consumer[Consumer] Consumer -->|deposit.confirmed| WC[WALLET_SERVICE credit] style Done fill:#22c55e,color:#000 style Fail fill:#ef4444,color:#fff style CB fill:#f59e0b,color:#000

7. Casino launch + callback

Player lança game → orchestrator gera session → provider iframe URL. Provider chama nosso wallet via callback HMAC.

sequenceDiagram participant Player participant Orchestrator as igaming-casino-orchestrator participant Adapter as Pragmatic/SoftSwiss adapter participant Provider as Provider API participant Wallet as igaming-wallet Player->>Orchestrator: POST /api/casino/launch/{game_id} Orchestrator->>Orchestrator: lookup tenant_casino_config (enabled?) Orchestrator->>Adapter: launchGame({player, game, currency}) Adapter->>Provider: API call (auth + token) Provider-->>Adapter: iframe_url + session_ref Adapter-->>Orchestrator: result Orchestrator->>Orchestrator: INSERT casino_sessions Orchestrator-->>Player: {iframe_url} Player->>Provider: iframe loads Note over Player,Provider: Player joga Provider->>Orchestrator: POST /api/casino/callback/{provider} (bet/win/refund) Orchestrator->>Adapter: handleCallback (validate HMAC + normalize) Adapter-->>Orchestrator: {type, session_token, amount, currency} Orchestrator->>Wallet: debit/credit (via WALLET_SERVICE) Wallet-->>Orchestrator: balance Orchestrator-->>Provider: adapter.formatCallbackResponse

8. D1 sharding strategy

Tenant_id → brand shard. Player_id → ledger shard via crc32(). Mapeamento em CONFIG_KV[arch:shard_map] com cache 60s.

graph TD ShardMap{{CONFIG_KV[arch:shard_map]}} Router[lib/d1router.js] ShardMap --> Router subgraph "Brand shards (per-tenant data)" B0[platform-main
s0_legacy
evian_bet] B1[cactus-brands-s1
brands 1-20] B2[cactus-brands-s2
brands 21-40] end subgraph "Ledger shards (per-player_id)" L0[player-ledger
s0_legacy
crc32 mod 2 = 0] L1[cactus-ledger-s1
s1
crc32 mod 2 = 1] end subgraph "Global (small tables)" G1[(tenants)] G2[(admin_users)] G3[(payment_providers)] G4[(casino_providers)] end Router -->|getBrandDb tenant_id| B0 Router --> B1 Router --> B2 Router -->|getLedgerDbForPlayer pid| L0 Router --> L1 Router -->|getGlobalDb| G1

9. Archive D1 hot → R2 Iceberg (nightly)

Cron 03:00 UTC. Move rows com archived=0 AND created_at < 90d pra R2 em layout Iceberg-compatible (JSONL.gz). DELETE com grace 10d.

graph TB Cron[Cron 03:00 UTC] --> Archiver[igaming-archiver] Archiver --> Foreach{Pra cada rule} Foreach -->|bets_recent| Shards1[Lista brand shards] Foreach -->|events_recent| Shards1 Foreach -->|ledger_entries| Shards2[Lista ledger shards] Foreach -->|transactions| Shards2 Shards1 --> Select[SELECT * WHERE archived=0 AND created_at < 90d LIMIT 5000] Shards2 --> Select Select --> Group[Group by tenant/year/month/day] Group --> Gzip[JSONL gzip via CompressionStream] Gzip --> R2Put[R2 put
warehouse/tenant=X/database=Y/table=Z/year/month/day/part-uuid.jsonl.gz] R2Put --> Snapshot[Iceberg snapshot manifest
_metadata/snapshot-ts-shard.json] Snapshot --> Mark[UPDATE archived=1] Mark --> Delete[DELETE WHERE archived=1 AND created_at < 100d] style Cron fill:#f59e0b,color:#000 style R2Put fill:#22c55e,color:#000

10. GDPR data export self-service

Player baixa ZIP JSON.gz com TODOS seus dados em <30s. Right-to-deletion com 14d cooldown.

sequenceDiagram participant Player participant GDPR as igaming-gdpr participant D1 participant R2 as R2 cactus-player-exports Player->>GDPR: POST /api/me/data-export GDPR->>D1: check existing job (rate limit 7d) GDPR->>D1: INSERT gdpr_export_jobs (status=pending) GDPR-->>Player: {job_id, status: pending} Note over GDPR: processExportJob (inline OR queue) GDPR->>D1: SELECT profile, wallets GDPR->>D1: SELECT ledger_entries (LIMIT 50000) GDPR->>D1: SELECT transactions, bonuses, kyc, casino_sessions, audit GDPR->>GDPR: JSON.stringify + CompressionStream gzip GDPR->>R2: put(exports/{player_id}/{job_id}.json.gz) GDPR->>D1: UPDATE status=ready, download_url, expires_at=+7d Player->>GDPR: GET /api/me/data-export/jobs/:id GDPR-->>Player: {status: ready, download_url} Player->>GDPR: GET /download/:id GDPR->>R2: get(key) R2-->>GDPR: stream GDPR-->>Player: ZIP file (Content-Encoding: gzip)

11. Push notifications flow (VAPID)

VAPID via Web Crypto (sem deps). Anonymous subscribe permitido (device_id link).

sequenceDiagram participant SW as Service Worker participant Browser participant Push as igaming-push participant CRM as Backoffice CRM participant Browser2 as Browser (online) Browser->>Push: GET /push/vapid → public key Browser->>Browser: registration.pushManager.subscribe(vapidKey) Browser->>Push: POST /push/subscribe {endpoint, keys} Push->>Push: INSERT push_subscriptions Note over CRM: Admin cria campaign no BO CRM->>Push: POST /push/admin/send {template_id, segment} Push->>Push: query subscriptions match segment loop pra cada sub Push->>Push: build VAPID JWT (ES256) Push->>Push: AES128-GCM encrypt payload Push->>SW: POST to subscription.endpoint end SW->>Browser: showNotification (mesmo offline) Browser->>Push: POST /push/track (click attribution)

12. WAF & Security layers

4 camadas de defesa. Per-tenant geo dinâmico via CONFIG_KV[tenant:X:config].restricted_countries.

CamadaImplementaçãoStatus
1. CF Zone WAF5 custom rules (UA scanner, threat_score, geo edge)✅ Active
2. Rate Limit Worker16 rules KV-backed (login 10/60s, withdraw 5/60s, OTP 3/5min, generic 600/min)✅ Active
3. Geo-block per-tenantRuntime check CF-IPCountry vs tenant.restricted_countries → HTTP 451✅ Active
4. CORS + Origin validationApex middleware bloqueia mutating requests com Origin inesperado✅ Active
5. Bot Fight ModeCF nativo + UA validation no apex✅ Active
6. Service Binding gatewayWorkers só falam entre si via service bindings (sem fetch HTTP loopback CF 1042)✅ Active
7. Circuit BreakersKV state machine per (provider×tenant), 5 falhas/60s → open 30s✅ Active

13. Events pipeline (200+ event types)

Toda ação importante emite event. Pipeline: Worker → Queue → Consumer → D1 + Analytics Engine.

graph LR W1[auth Worker] -->|emitEvent| Q[(Queue igaming-events)] W2[wallet Worker] --> Q W3[games Worker] --> Q W4[bonus Worker] --> Q W5[kyc Worker] --> Q W6[social/affiliate/etc] --> Q W7[Frontend /events/track] --> Q Q --> Consumer[analytics-consumer
batch 100 / 5s] Consumer --> AE[(Analytics Engine
5M writes/s capacity)] Consumer --> D1[(D1 events_recent_90d)] AE -->|SQL aggregate| Reports[igaming-reports] D1 -->|recent queries| Reports Reports -->|BI dashboards| BO[Backoffice /bo/reports]

14. Deployment + CI/CD

Tudo via wrangler. Pages Projects pra frontends. D1 + R2 + KV criados via CF API.

AssetTipoURL
evian-bet-frontendPagesstaging.evian.bet
cactus-v10-backofficePagesbo.cactus-v10.com
igaming-docsPagesdocs-new-stack.cactus-v10.com
cactus-kanbanPageskanban.cactus-v10.com
cactus-docs (esta página)Pagesdocs.cactus-v10.com

15. API endpoints index

Snapshot dos endpoints mais importantes. Lista completa em /api/admin/openapi (TODO).

Auth
POST /api/auth/register
POST /api/auth/login
POST /api/auth/logout
POST /api/auth/refresh
POST /api/auth/forgot-password
POST /api/auth/reset-password
POST /api/auth/2fa/setup
GET  /api/auth/me
Wallet
GET  /api/wallet/balance
GET  /api/wallet/balances
GET  /api/wallet/history?limit=&offset=
POST /api/wallet/transfer
POST /api/wallet/exchange
Games (Crash/Mines/Tower)
WS   /api/games/crash/connect?token=JWT
       ws msg: {type: 'bet'|'cashout'|'ping'}
       ws ack: {type: 'bet_ack'|'cashout_ack'|'pong'}
GET  /api/games/crash/state
GET  /api/games/crash/history
POST /api/games/crash/bet (fallback)
POST /api/games/crash/cashout (fallback)
POST /api/games/mines/start
POST /api/games/mines/reveal
POST /api/games/tower/start
POST /api/games/tower/reveal
POST /api/games/verify (provably fair)
Payments
GET  /api/payments/methods?country=BR¤cy=BRL
POST /api/payments/deposit
POST /api/payments/withdrawal
POST /api/payments/webhook/:provider
GET  /api/admin/payments/providers
GET  /api/admin/payments/tenants/:tid/config
PATCH /api/admin/payments/tenants/:tid/config
POST /api/admin/payments/test/:provider
GET  /api/admin/payments/circuit-breakers
Casino
GET  /api/casino/games?category=slots&provider=&limit=
POST /api/casino/launch/:game_id
POST /api/casino/callback/:provider
GET  /api/admin/casino/providers
GET  /api/admin/casino/tenants/:tid/config
POST /api/admin/casino/sync/:provider
GDPR / Player rights
POST   /api/me/data-export
GET    /api/me/data-export/jobs
GET    /api/me/data-export/jobs/:id
GET    /api/me/data-export/download/:id
POST   /api/me/account-deletion
GET    /api/me/account-deletion
DELETE /api/me/account-deletion
Backoffice + System
GET  /api/bo/players?search=&status=
GET  /api/bo/players/:id/full
POST /api/bo/players/:id/lock
POST /api/bo/players/:id/credit
GET  /api/admin/system/tenants
PATCH /api/admin/system/tenants/:id
GET  /api/reports/overview
GET  /monitor/status (público)
GET  /api/kanban/boards
GET  /api/kanban/boards/:slug

16. Events catalog (300+ events, 29 namespaces)

Sistema 100% orientado a eventos no shape bigtec (PostHog/Mixpanel/Amplitude). Source: evian-bet-frontend/src/lib/events.ts. Pipeline: track()POST /api/events/track → Queue igaming-events → Consumer → D1 events_recent_90d + Analytics Engine.

Total namespaces
29
Total event types
300+
Sink writes/s capacity
5M

Universal envelope

Toda chamada de emit.{namespace}.{action}({data}) adiciona automaticamente:

{
  event_type: 'bet.placed',
  ts_client: 1716922800000,
  session_id: 'sess_uuid',
  device_id: 'dev_uuid',
  player_id: 'plr_uuid' | null,
  page_url, page_path, referrer,
  user_agent_hash, viewport,
  utm_source, utm_medium, utm_campaign, utm_content, utm_term,
  // ↓ added server-side from CF
  ip_country, ip_city, ip_region, ip_asn, ip_isp,
  // ↓ event-specific payload
  data: { game_id, bet_id, amount, currency, ... }
}

29 Namespaces

session
start, end, resume, idle, visibility_hidden/visible, focus_lost/gained, before_unload (9)
page
view, leave, scroll_depth, hash_change, route_change, back_button, deeplink, not_found (8)
attribution
first_touch, last_touch, affiliate_link, organic, direct (5)
performance
LCP, FCP, CLS, INP, TTFB, slow page/api/ws, memory, long_task (10)
device
new, change, orientation, resize, theme, language (6)
signup
modal_open, field_focused/blurred/changed, validations, abandoned (17)
auth
login, 2FA, password_reset, email_verified, session_expired, rapid_attempts (19)
funnel
signup→deposit→bet→withdraw lifecycle, generic step_entered/completed (13)
game
discovery (filter/sort/search), thumbnail impressions/clicks, launch, favorites (23)
bet
draft amount + presets, auto-cashout, cashout micro (hover/click/decision_time), big_win, unusual_size (24)
wallet
deposit funnel (method→qr→complete), withdrawal funnel (kyc_block→confirm), currency switch, exchange (26)
social
feed/post lifecycle, follow/unfollow, DM (sent/delivered/read/block) (19)
kyc / rg
page_view, sumsub, documents, approved/rejected, limits, self-exclusion, reality_check (18)
bonus
offer impressions, claim, wagering milestones (25/50/75/90%), completed, forfeit (11)
vip
tier_up/down, perks, points earned/redeemed, cashback (9)
referral
link_copied, share, attribution, conversion (cpa/revshare), payout (7)
push
banner, permission lifecycle, subscribed/unsubscribed, received/displayed/clicked (15)
pwa
install, opened, standalone, offline_loaded, update_available/applied (12)
comms
email/sms/whatsapp lifecycle (sent→delivered→opened→clicked→bounced/unsubscribed), OTP (17)
chat
widget, ticket lifecycle, message, agent_assigned, satisfaction (9)
experiment
assigned, viewed, conversion, feature_flag_evaluated, forced_variant (5)
search
performed, result_clicked, no_results, suggestion (4)
form
started, submitted, abandoned, validation_error, server_error, autofilled (6)
fraud_signal
vpn/proxy/tor, emulator, ip/device/clock_mismatch, multi_account, velocity, geo (11)
error
api, javascript, ws, resource, unhandled_promise, payment/casino_provider, boundary (8)
ws
connecting/connected/reconnected/disconnected, msg sent/received, silence (8)
network
online/offline, connection_change (4G→2G), save_data_enabled (4)
cta
hero, banner impression/click/dismiss, fab, rage_click, dead_click, copy (10)
account
data_export, deletion, consent_changed, profile_edited, avatar, preference (9)

Eventos de uso

import { emit } from '@/lib/events'

emit.bet.placed({ game_id: 'crash', bet_id, amount: 50, currency: 'BRL', via: 'ws' })
emit.funnel.firstDepositCompleted({ time_to_first_deposit_sec: 142, amount_minor: 5000, currency: 'BRL' })
emit.fraud_signal.vpnDetected({ provider: 'NordVPN' })
emit.experiment.assigned({ experiment: 'new-cashout-button-color', variant: 'green' })
emit.performance.webVitalLcp({ value_ms: 2400, path: '/cassino', rating: 'good' })

17. Filters & dimensions (analytics)

Toda query Reports/BI no BO suporta esses filtros. Documenta o esquema de cubo OLAP.

Dimensões (filtros)

CategoriaDimensõesOrigem
Identidadetenant_id, player_id, session_id, device_id, anonymous_idEnvelope universal
Geoip_country, ip_region, ip_city, ip_postal_code, ip_timezone, ip_asn, ip_ispCF cf-data headers
Dispositivodevice_type (desktop/mobile/tablet), os, browser, browser_version, viewport_w, viewport_h, pixel_ratioUA parsing + JS
Attributionutm_source, utm_medium, utm_campaign, utm_content, utm_term, referrer, landing_page, gclid, fbclid, affiliate_codeURL params + localStorage first_touch
Perfilvip_level, kyc_level, language, primary_currency, registration_date_bucket, age_bucket, gender, segment_id[]players table join
Tempohour, day, week, month, quarter, year, day_of_week, hour_of_dayDerivado de ts_client / ts_server
Gamegame_id, provider_id, category, subcategory, currency_played, bet_size_bucket, rtp_bucket, volatilitycasino_games_catalog join
Comportamentosession_count_bucket, ltv_bucket, days_since_last_session, days_since_first_deposit, churn_risk_scoreComputed
Experimentoexperiment_assignments[] (active variants)Workers experiment binding
A/B segmentscustom_segment[] (e.g. "high_value", "at_risk", "vip_diamond")Computed via segmentation worker

Métricas (aggregations)

MétricaCálculoWhere
DAU/WAU/MAUCOUNT(DISTINCT player_id) by day/week/monthevents_recent
SessionsCOUNT(DISTINCT session_id)events_recent
Avg session durationAVG(session.end.duration_ms)events session.end
Page views per sessionSUM(page.view) / sessionsevents page.view
Conversion rate signup→depositfunnel.first_deposit_completed / funnel.signup_completedevents funnel.*
D1/D7/D30/D90 retentionCohort matrix: % returning N days after signupevents signup.completed + session.start
LTVSUM(deposits) - SUM(withdrawals) per playerledger_entries
ARPU/ARPPUNGR / total_users e NGR / paying_usersledger_entries
GGRSUM(bet.amount) - SUM(win_amount)events bet.settled + bet.placed
NGRGGR - bonuses - feescomputed
RTP realSUM(win) / SUM(bet)events bet.*
Churn rate% players inactive > 30dlast_login_at
Latency P50/P90/P99Histogram percentiles em events performance.api_latency_slowAnalytics Engine
Push CTRnotification_clicked / notification_displayedevents push.*
Email open rateemail_opened / email_deliveredevents comms.*
Bet decision timeHistogram bet.cashout_clicked.decision_time_msAnalytics Engine

Exemplos SQL (Analytics Engine)

-- DAU últimos 30d por country
SELECT
  blob1 AS country,
  formatDateTime(timestamp, '%Y-%m-%d') AS day,
  COUNT(DISTINCT blob2) AS dau
FROM events_analytics
WHERE timestamp > now() - INTERVAL '30' DAY
  AND blob3 = 'session.start'
GROUP BY country, day
ORDER BY day DESC, dau DESC;

-- Conversion funnel signup → first deposit por UTM source
SELECT
  blob4 AS utm_source,
  countIf(blob3 = 'funnel.signup_completed') AS signups,
  countIf(blob3 = 'funnel.first_deposit_completed') AS first_deposits,
  countIf(blob3 = 'funnel.first_deposit_completed') / countIf(blob3 = 'funnel.signup_completed') AS conv_rate
FROM events_analytics
WHERE timestamp > now() - INTERVAL '7' DAY
GROUP BY utm_source
ORDER BY signups DESC;

-- Bet decision time histogram (P50/P90/P99)
SELECT
  quantile(0.5)(double1) AS p50_ms,
  quantile(0.9)(double1) AS p90_ms,
  quantile(0.99)(double1) AS p99_ms
FROM events_analytics
WHERE blob3 = 'bet.cashout_clicked'
  AND timestamp > now() - INTERVAL '24' HOUR;

18. Backoffice inventory (12 BOs + System)

BONomeStatusURL
BO-01Player Management (360°)● Live/bo/player · 8 tabs · 22 endpoints
BO-02Finance● Live (refinement pending)/bo/finance · /providers
BO-03Bonus Management● Live/bo/bonus · catálogo + analytics + grant manual
BO-04Marketing / CRM○ StubK-021 (backlog)
BO-05Anti-Fraud● Live (basic)/bo/fraud · 6 tabs
BO-06Risk Management○ StubK-022 (backlog)
BO-07KYC Operations● PartialK-023 (expansão pendente)
BO-08Affiliate Management○ StubK-024 (backlog)
BO-09Content (Games/Banners/CMS/Tema)● Live/bo/content
BO-10Reports & BI● Live/bo/reports · 12 KPIs + cohort + funnel
BO-11Compliance & Regulatory Reports○ StubK-025 (backlog)
BO-12Operations Dashboard○ StubK-026 (backlog)
System (super_admin)
SYSOverview / Admin Users / Audit● Live/system/{overview,users,audit}
SYSTenants Management● Live/system/tenants + editor com 11 tabs
SYSAnalytics & Realtime● Live/system/analytics, /realtime
SYSCMS● Live/system/cms
SYSStatus & Monitor● Live/system/status · 19 services monitored
SYSWAF & Security● Live/system/waf · zone rules + apex middlewares + per-tenant
SYSPayment Providers● Live/system/payment-providers · 7 providers no catálogo
SYSCasino Providers● Live/system/casino-providers · 8 providers

19. Data model — D1 tabelas críticas

erDiagram TENANTS ||--o{ PLAYERS : has TENANTS ||--o{ TENANT_PAYMENT_CONFIG : has TENANTS ||--o{ TENANT_CASINO_CONFIG : has PLAYERS ||--o{ WALLETS : owns PLAYERS ||--o{ LEDGER_ENTRIES : has PLAYERS ||--o{ TRANSACTIONS : has PLAYERS ||--o{ PLAYER_BONUSES : has PLAYERS ||--o{ CASINO_SESSIONS : has PLAYERS ||--o{ PLAYER_LOCKS : has PLAYERS ||--o{ PLAYER_NOTES : has PLAYERS ||--o{ KYC_STATUS : has PAYMENT_PROVIDERS ||--o{ TENANT_PAYMENT_CONFIG : configured_in PAYMENT_PROVIDERS ||--o{ PAYMENT_ATTEMPTS : tracks CASINO_PROVIDERS ||--o{ CASINO_GAMES_CATALOG : produces CASINO_PROVIDERS ||--o{ TENANT_CASINO_CONFIG : configured_in CASINO_PROVIDERS ||--o{ CASINO_SESSIONS : hosts TENANTS { string id PK string brand_name string status string primary_domain string cloudflare_zone_id int config_version } PLAYERS { string id PK string tenant_id FK string email_hash string display_name int kyc_level int vip_level string status } LEDGER_ENTRIES { string id PK string player_id FK string type string direction int amount_minor string currency string ref_type string ref_id int archived } PAYMENT_PROVIDERS { string id PK string type json capabilities json currencies json countries } TENANT_PAYMENT_CONFIG { string tenant_id FK string provider_id FK int enabled int priority int weight string failover_to }

D1 tables: 37 em platform-main + 8 em player-ledger + 11 em cactus-brands-s1/s2 + 4 em cactus-ledger-s1 + 5 em cactus-kanban = ~65 tables distribuídas por sharding.