Bonus Lifecycle & Chunk Model
State machine
The lifecycle applies at the chunk level. A bonus is the aggregate of its chunks; the grant carries a derived status (e.g. PARTIALLY_RELEASED) summarizing them.
Release trigger event fires (deposit, registration, …)
│ code validated earlier via POST /validate-code (read-only)
▼
GRANT created — split into no_of_chunks chunks, all PENDING ──────┐
│ release trigger fires and/or wagering │ chunk_expiry_days /
▼ progress meets the chunk requirement │ bonus_expiry_days reached
RELEASE (per chunk — spendable) ─────────────┐ ▼
│ consumed against wagers │ EXPIRED (terminal)
▼ ▼
CONSUMED (terminal) EXPIRED / FORFEITED (terminal)Chunk status values (transaction-detail API): PENDING → RELEASE → CONSUMED / EXPIRED. A chunk must be in RELEASE status before it can be consumed. Ledger event type values: grant, released, consumed, expiry, forfeited.
State-to-wallet mapping (PAM client wallet)
grant → PENDING
grant
Increment Pending
PENDING → RELEASE
released
Move chunk amount Pending → Available
RELEASE → CONSUMED
consumed
Decrement Available (PAM debits first; ledger confirms)
→ EXPIRED
expiry
Remove from the relevant bucket
→ forfeited
forfeited
Remove remaining from Pending and/or Available
Wynta's authoritative per-chip-type view (GET /{user_id}/summary): bonus_balance (available = released − consumed), pending_bonus, and wagering_required (remaining wagering to release all pending chunks).
Chunk configuration
Chunking is configured per bonus on the configure node (bonus_create_configure) and snapshotted onto each grant (visible in transaction detail):
no_of_chunks
1
Number of equal chunks the granted bonus is split into
wager_multiplier
0
Wagering requirement multiplier; 0 = no wagering — chunks release on trigger alone
wager_chip_type
CASH
Chip type whose wagers count toward release progress (may be BONUS)
credit_chip_type
CASH
Chip type the bonus is credited in
chunk_expiry_days
—
Days before an unreleased chunk expires
bonus_expiry_days
—
Days before the entire bonus expires
release_bucket
—
Optional release bucket identifier
Chunk attributes (transaction detail API)
GET /api/v1/user-bonuses/{user_id}/transactions/{txn_id} returns each grant with its chunks:
id / chunk_ref
Chunk record ID / human-readable reference (e.g. CHUNK_001)
chunk_amount
Bonus amount in this chunk = grant_amount / no_of_chunks
wager_multiplier
Wagering multiplier for this chunk
status
PENDING | RELEASE | CONSUMED | EXPIRED
wager_amount
Wagering progress toward release, tracked per chunk
created_at / updated_at
Creation / last status-change timestamps
Grant-level rollups: grant_amount, release_amount, bonus_consumed, derived status.
How a chunk releases
Two mechanisms move a chunk PENDING → RELEASE, per the bonus configuration:
Release trigger events — configured rules (
trigger_type=DEPOSIT,LOGIN,BET_PLACED,TOURNAMENT_WON, …) that fire when a matching PAM event arrives and its conditions pass (occurrence, amount band,payment_method,product,trigger_config_json).Wagering progress — when
wager_multiplier > 0, each qualifyingbet_placedevent adds itswager_amountto the current chunk's progress; the chunk releases when progress meets the chunk requirement:
Example: ₹500 bonus, wager_multiplier = 3.00, no_of_chunks = 3 → three chunks of ₹166.67, each releasing after ₹500.01 of qualifying wagering (₹1,500 total). GET /{user_id}/summary exposes the remaining total as wagering_required.
Rules:
Serialization — progress updates are serialized per player-bonus; concurrent
bet_placedevents cannot double-release a chunk.Overshoot — a large wager can complete several chunks at once; the webhook may batch them in a
chunks[]array.Monotonic release — voided wagers reduce progress but already-released chunks are never clawed back.
wager_multiplier = 0— no wagering requirement; chunks release on their trigger conditions alone.Multiple active bonuses — if a player holds several active wagering bonuses, contribution is attributed to the oldest active bonus first; the recommended policy is to enforce a single active wagering bonus per chip type to avoid attribution disputes (open question — see Operations).
Worked example
₹500 grant, 3 × ₹166.67 chunks, 3× multiplier (₹500 wagering per chunk):
Cumulative qualifying wagered
Chunks in RELEASE
pending_bonus
bonus_balance
wagering_required
₹0
0
500.00
0.00
1,500.00
₹500
1
333.33
166.67
1,000.00
₹1,100
2
166.67
333.33
400.00
₹1,500
3
0.00
500.00
0.00
Consumption across chunks
The consume endpoint auto-selects the user's next available released chunk — the client never chooses chunks. Every consumption is recorded per chunk under its consume_txn_id, so transaction detail shows exactly which chunks funded which rounds — this is what makes reversal and reconciliation exact.
Expiry & forfeit at chunk level
Two independent clocks:
Chunk expiry (
chunk_expiry_days) — an unreleased chunk expires on its own; recorded as an expiry event withtype: "CHUNK_EXPIRY".Bonus expiry (
bonus_expiry_days) — the whole bonus expires; non-terminal chunks transition together, recorded withtype: "BONUS_EXPIRY".
Forfeits carry a type (MANUAL, RULE_VIOLATION) and the triggering operator. On expiry or forfeit the webhook carries per-chunk detail so the PAM decrements Pending and Available by the correct split.
Last updated
Was this helpful?