Bonus Engine

Bonus issuance, wagering requirement tracking, balance locking and auto-expiry

Phase 3
Spec
Bonus Engine is a dedicated service in the Support Services Layer that manages the full bonus lifecycle: issuance (on deposit), wagering tracking (on each bet), balance locking (via Wallet Engine), and completion or cancellation. It does not store balances -- that is Wallet Engine's responsibility. Bonus Engine only decides when and how much to lock or unlock.
Bonus balance and real money balance are two separate ledgers in Wallet Engine. The player sees both, but only real money can be withdrawn. On each bet, real balance is consumed first, then bonus balance. Never mix balance logic into Bonus Engine -- all balance operations go through Wallet Engine only.
1.

Purpose

Bonus issuance

Grant deposit match bonuses when a qualifying deposit is made and the player has opted in. Creates a bonus record and credits locked balance.

Wagering requirement tracking

Track how much the player has wagered towards the requirement. Each qualifying bet increments progress based on game category contribution rate.

Balance locking

Bonus funds are held in a locked balance that cannot be withdrawn. Only unlocked (converted to real money) when wagering is complete.

Withdrawal block

Integrates with Limits & Rules engine to block withdrawal while an active bonus has an unfulfilled wager requirement.

Auto-expiry

Bonuses expire if wagering is not completed within the configured period. Expired bonuses are cancelled and locked balance is deducted.

Lifecycle audit

Every status transition is logged with timestamp, reason, and actor. Required for compliance and dispute resolution.

2.

Where it sits and how information flows

Position in the stack

1
Wallet Enginenotifies Bonus Engine on deposit confirmed; executes balance lock/unlock commands
2
Bonus Engine ← YOU ARE HEREissues bonuses, tracks wagering, responds to wager lock checks
3
Limits & Rules engine (calls Bonus Engine)asks "is there a wager lock?" before approving withdrawal
4
Game provider webhookssends bet events to increment wager progress

Three main flows

On deposit confirmed

1
Wallet Engine notifies Bonus Engine: deposit confirmed (user_id, brand_id, amount, method)
2
Check: does user have an ACTIVE or PENDING bonus? → if yes, skip (no stacking)
3
Check: does deposit match any active bonus_template? (amount ≥ min_deposit, player opted in)
4
If match → create player_bonus record (PENDING), calculate bonus_amount = min(deposit × pct, max_bonus)
5
Credit bonus_amount to locked balance in Wallet Engine → set bonus to ACTIVE

On each qualifying bet

1
Receive bet event: user_id, bet_amount, game_category
2
Look up contribution_rate for game_category from active bonus template
3
Increment wager_progress += bet_amount × contribution_rate
4
If wager_progress ≥ wager_requirement → auto-complete: convert locked balance to real balance in Wallet Engine, set COMPLETED

On withdrawal request (called by Limits & Rules)

1
Check: does user have an ACTIVE bonus with wager_progress < wager_requirement?
2
If YES → return WAGER_NOT_COMPLETE (with progress % and remaining amount)
3
If NO → return PASS (no active wager lock)

Input data and sources

user_id, brand_id, deposit amount, payment method

from Wallet Engine on deposit confirmed

Active bonuses for user: status, wager progress, expiry

player_bonuses table (Redis cache)

Bonus templates: match %, min deposit, max bonus, wager multiplier, expiry

bonus_templates table (per brand)

Bet amount, game category, game_id (for wagering contribution)

Game provider webhook / Wallet Engine on each bet

Withdrawal request amount (for wager lock check)

from Limits & Rules engine (withdrawal flow)

3.

Required at launch

Bonus lifecycle

PENDING
ACTIVE
COMPLETED
CANCELLED
EXPIRED
FORFEITED
PENDINGBonus created, deposit confirmed, awaiting activation
ACTIVEWagering in progress -- player is playing
COMPLETEDWager requirement met -- bonus funds converted to real money
CANCELLEDPlayer withdrew before completing wager, or ops cancelled
EXPIREDExpiry date passed before wager was completed
FORFEITEDPlayer explicitly opted out and forfeited bonus + winnings

Rules required for launch

RuleDetailsRequired?
Deposit match bonusTrigger: deposit ≥ min_deposit + player opted in. Credit: deposit × match_pct, capped at max_bonus.
Yes
Wager requirement trackingEvery qualifying bet increments wager_progress by bet_amount × contribution_rate. When progress ≥ requirement, auto-complete.
Yes
Bonus balance lockBonus funds are held in a separate locked balance in Wallet Engine. Cannot be withdrawn until COMPLETED.
Yes
Withdrawal block integrationLimits & Rules engine calls Bonus Engine before approving withdrawal. If active bonus with incomplete wager → WAGER_NOT_COMPLETE.
Yes
Bonus auto-expiryScheduled job runs daily: mark expired bonuses as EXPIRED, deduct locked balance from Wallet Engine.
Yes
One active bonus per playerPhase 3: no bonus stacking. New bonus offer is blocked if player already has an ACTIVE or PENDING bonus.
Yes
Bonus cancellation by opsOperations team can cancel any bonus. On cancel: set CANCELLED, deduct bonus balance, log reason.
Yes
Configurable contribution ratesRates per game category stored in bonus_templates.game_contributions. Not hardcoded.
Yes
Wagering on real money firstReal money balance is wagered before bonus balance. Prevents bonus abuse where players spin only with bonus funds.
Recommended
Max win cap per bonusTotal winnings from bonus funds capped at max_win_multiplier × bonus_amount. Excess deducted on completion.
Recommended

Wagering contribution by game category (typical defaults)

CategoryContributionNote
Slots100%--
Live casino10%High RTP games contribute less
Table games (RNG)20%--
Sports betting10%Only bets with odds ≥ 1.5 count
Video poker10%--
Scratch cards / Keno50%--
Jackpot slots0%Excluded from wagering by default

Error codes

CodeHTTPDescriptionPlayer message
WAGER_NOT_COMPLETE422Active bonus has an unfulfilled wagering requirement"Complete your wagering requirement to withdraw. Progress: X%"
BONUS_ALREADY_ACTIVE422Player already has an active or pending bonus — cannot issue another"You already have an active bonus"
BONUS_EXPIRED422Bonus expired before player completed wagering"Your bonus has expired"
BONUS_CANCELLED422Bonus was cancelled by ops team"Your bonus has been removed. Contact support for details."
DEPOSIT_TOO_LOW422Deposit amount below min_deposit threshold for bonus eligibility"Minimum deposit for this bonus is $X"

Minimum DB schema for Phase 3

-- Bonus offer templates (configured per brand by ops)
bonus_templates (
  id, brand_id, name, type,        -- 'deposit_match'
  match_pct, min_deposit, max_bonus,
  wager_multiplier,                -- e.g. 30 means 30× bonus amount
  expiry_days,
  max_win_multiplier,              -- null = no cap
  game_contributions               -- JSON: { slots: 1.0, live: 0.1, ... }
  is_active
)

-- Active bonuses per player
player_bonuses (
  id, user_id, brand_id, bonus_template_id,
  status,             -- PENDING | ACTIVE | COMPLETED | CANCELLED | EXPIRED | FORFEITED
  bonus_amount,       -- credited to locked balance
  wager_requirement,  -- bonus_amount × wager_multiplier
  wager_progress,     -- incremented on each qualifying bet
  deposited_amount,   -- the deposit that triggered this bonus
  expires_at,
  completed_at, cancelled_at, cancel_reason,
  created_at
)

-- Append-only wager event log
wager_transactions (
  id, player_bonus_id, transaction_id,
  game_category, bet_amount,
  contribution_rate, contribution_amount,
  wager_progress_after,
  created_at
)
4.

Best practices

Separation of concerns

  • Wallet Engine tracks balances (real, locked, bonus). Bonus Engine tracks eligibility, progress, and lifecycle. Never mix these concerns.
  • Wagering progress is tracked per-bonus (not per-player) to support multiple bonuses in future phases without refactoring.
  • Bonus Engine never modifies Wallet balances directly -- it sends commands to Wallet Engine (credit_locked, unlock, debit_locked).

Phase 3 scope -- what to build now

  • One bonus type for Phase 3: deposit match bonus. Free spins, cashback, referral bonuses -- Phase 5+.
  • No bonus stacking for Phase 3. One active bonus per player per brand is sufficient for launch.
  • Wagering tracking can be event-driven (bet webhook) or batch (nightly reconciliation of bets). Event-driven is preferred.

Performance & reliability

  • Cache active bonus per user in Redis (TTL 60s) -- checked on every withdrawal request.
  • Wagering updates are high-frequency (every bet). Use optimistic locking or atomic increment to avoid race conditions on wager_progress.
  • Expiry job must be idempotent -- safe to run multiple times without double-deducting balances.

Compliance & audit

  • Log every state transition with reason: PENDING→ACTIVE, ACTIVE→COMPLETED, ACTIVE→CANCELLED(reason), ACTIVE→EXPIRED.
  • Bonus terms (wager multiplier, expiry, max win) must be shown to player at opt-in -- regulatory requirement in most jurisdictions.
  • On withdrawal with active bonus: present player with a clear choice -- forfeit bonus and withdraw, or keep bonus and continue wagering.
DEPO44 | BONUS ENGINE SPEC v1 | PHASE 3