Architecture of Crypto Casino Bonus System: Freebets and Free Spins
A player registered, received a free $10 bet — but couldn't use it due to a bug: the freebet service returned a ConflictError for every second request. Each lost freebet means missed LTV averaging $10 per user. When scaling to 10,000 RPS without atomic claim, losses become catastrophic: thousands of unused freebets per hour, tens of thousands of dollars in losses. We'll break down how to build a system that handles load, prevents fraud, and provides analytics for marketing. The average ROI of a well-tuned freebet campaign is 400–500%. In this article we'll cover the data model, wagering mechanics, atomic operations, and typical implementation mistakes. We draw on experience implementing bonus systems for crypto casinos handling up to 10,000 requests/sec.
Freebet Mechanics
Standard mechanics: the player gets a $10 freebet, places it on an event with odds 2.0. If they win, the casino pays $10 (profit), not $20 (stake + profit). If they lose, the player loses nothing. The slot equivalent is a Free Spin: a certain number of spins without deducting from real balance.
Freebets differ in wagering conditions and target channel. Welcome freebets are given at registration with simplified wagering — often 1x for sports, 30x for casino. Promo freebets for loyal players may have minimum odds and market restrictions.
Why Atomic Claim Operation is Critical?
Problem: two parallel requests to use the same freebet. Solution — atomic claim with status and version check. In PostgreSQL it's UPDATE ... WHERE status='AVAILABLE' AND version=1 RETURNING *, in Redis — SETNX. In our code — FreeBetService.apply_free_bet uses a transaction and claim, which returns ConflictError if the record is already taken. This approach is described in PostgreSQL documentation.
async with self.db.transaction(): updated = await self.freebet_repo.claim(free_bet_id, bet_params.bet_id) if not updated: raise ConflictError("Free bet already used") Atomic operations are tens of times faster than optimistic locks — the difference is especially noticeable at 10k RPS.
Freebet Data Model
class FreeBet(BaseModel): id: str user_id: str type: str # 'SPORTS_BET', 'CASINO_BET', 'FREE_SPIN' status: str # 'AVAILABLE', 'USED', 'EXPIRED', 'WON', 'LOST' amount: Decimal currency: str # For free spins spin_count: int = 0 spins_used: int = 0 eligible_games: list[str] = [] # For sports bets min_odds: Optional[Decimal] eligible_markets: list[str] = [] # Results bet_id: Optional[str] winnings: Decimal = Decimal(0) # Wagering on winnings wagering_required: bool = True wagering_multiplier: int = 1 expires_at: datetime issued_at: datetime source: str # 'WELCOME', 'PROMO', 'REWARD', 'REFERRAL' Details on model fields
-
typeaffects validation logic: for SPORTS_BET checksmin_odds, for CASINO_BET checkseligible_games. -
statusswitches atomically; transitions: AVAILABLE → USED → WON/LOST. -
wagering_multiplieris applied only ifwagering_required=True.
How Wagering Requirement Affects ROI?
Without wagering, the player can immediately withdraw freebet winnings, making the campaign unprofitable. Typical multipliers: 1x for sports, 30x for slots. Our settle_free_bet service on win creates a bonus with wagering requirement: await self.bonus_service.create_winnings_bonus(...). For free spins, similarly but with spin_count. Properly configured wagering increases campaign ROI by 20–30% by reducing instant withdrawals.
How We Do It: Stack and Case
We use Python 3.11, PostgreSQL, Redis for status caching. For validation — Pydantic v2. In one project, the freebet system handled 10,000 requests/sec. The bottleneck was eligibility checking — we optimized by caching expires_at in Redis. Result: latency dropped from 50 ms to 3 ms.
| Freebet Type | Parameters | Wagering | Application |
|---|---|---|---|
| SportsBet | min_odds, markets | 1x | Sports betting |
| CasinoBet | eligible_games | 30x | Slots, table games |
| FreeSpin | spin_count, eligible_games | 30x | Specific slot machines |
Let's compare two asynchronous processing approaches: Jedis pool vs Lettuce. Lettuce has twice the throughput under high load — important for real-time freebet wagering.
| Characteristic | Jedis (synchronous) | Lettuce (asynchronous) |
|---|---|---|
| RPS at 100 concurrent | 5000 | 12000 |
| P99 latency | 30ms | 8ms |
Development Process
- Analytics: define freebet types, budget, target ROI.
- Modeling: create ER diagram, API specification (OpenAPI).
- Implementation: write
FreeBetService, cover with unit tests (pytest + mock). - Testing: load testing via Locust, fuzzing on wagering.
- Deployment and monitoring: deploy in Docker/K8s, configure metrics (Prometheus).
Timeline and Cost
Timeline — from 2 weeks (MVP) to 2 months (full cycle with analytics and fraud protection). Cost is calculated individually — depends on mechanics complexity and integrations. Savings from freebet buyout can be substantial, covering development costs within the first months.
What's Included in the Result
-
FreeBetServicecode with atomic operations - API documentation (Swagger)
- Configured monitoring (latency, error rate)
- Operations manual and role model
- Code warranty — 6 months of support
Our experience — 8+ years in Web3 development, over 40 implemented crypto casinos. Get a consultation: tell us about your freebet mechanics, and we'll design the architecture. Contact us to order an audit of your current implementation.
Typical Implementation Mistakes
- Ignoring race condition on
claim— leads to duplicate bets - No limit on
spin_count— free spins could be infinite - Tight coupling to a single database — sharding easily loses atomicity
Fix these bugs before launch — save millions on freebet buyouts. Order an audit of your current implementation — we'll find bottlenecks in a day.







