VIP & RakeBack
Operator-authored VIP tiers on total play, with recurring bonuses that are a percentage of each player's own play — not a set amount. Tiers are computed live from the event stream; rakeback settles on closed books; every rate is capped, bounded, and explainable.
Tiers — computed, never stored
GET /v1/vip/players/{player_id}
-> {"tier": "Gold", "wagered": 6200000,
"tiers": [{"name": "Bronze", "threshold": 0}, ...],
"next_tier": "Diamond", "needed": 43800000, "progress_pct": 12,
"benefits": [ ... claim state per benefit ... ]}Tiers threshold on total wagerevents (or GGR, or purchases — LTV tiers), lifetime or rolling-90-day. Because the tier is computed from events you already send, it's correct retroactively the moment the program is configured — and the response is a ready-made VIP progress bar.
RakeBack — % of the player's own play
- Base: X bp of the player's GGR (wagers − wins), wagered, purchases, or net purchases (purchases − redeems: cash-anchored rebates, requiring the reserved
redeemevent) — over the previous completed period (closed books, no window gaming). A non-positive base earns nothing. - Signal-sized: the rate can rise per player — loyalty (streak ≥ N days), re-engagement (played less than the prior period), depositors (purchased ≥ X). Clamped by a ceiling, then a hard cap. The response carries
effective_bpandboosts_applied, so every rate is explainable. - Activity-gated: benefits can require recent play — engaged VIPs claim automatically; lapsed VIPs are winback-campaign territory.
Claiming
POST /v1/vip/claim
{"player_id": "...", "idempotency_key": "uuid"}
-> {"status": "claimed", "tier": "Gold",
"claimed": [{"tier": "Gold", "cadence": "weekly",
"period_id": "2026-W29", "rakeback_amount": 4200}],
"rewards": [{"type": "currency", "currency": "SC", "amount": 4200,
"label": "12% ggr rakeback (2026-W29)"}, ...]}One call claims everything currently claimable — once per benefit per period, enforced server-side. Currency settles through your grant outbox as always; spins and tickets follow their usual paths.
wager events; add type: "win" with the payout amount per settled round. Without wins, GGR overstates and rakeback overpays players who won.Endpoint summary
| Endpoint | Purpose | Billable |
|---|---|---|
GET /v1/vip/players/{id} | tier + progress + claim state | no |
POST /v1/vip/claim | claim all claimable benefits | yes (1 token, paying claims only) |
POST /v1/events | wager / win / purchase signal | per plan |
Integrate with your coding agent
Building with Claude, Cursor, Copilot, or another AI agent? Paste this prompt — it contains the complete contract, the security rules, and an acceptance checklist, so your agent can implement the integration without reading these docs.
Integrate the Pulse "VIP & RakeBack" feature into this codebase. Pulse is a
gamification API for sweepstakes sites. Follow this architecture exactly -
it is a hard security contract.
## Non-negotiable rules
1. PULSE_API_KEY is backend-only; all Pulse calls go frontend -> our
backend -> Pulse. The browser never talks to Pulse.
2. The tier and every bonus amount are COMPUTED BY PULSE. Never calculate
tiers, progress, GGR, or rakeback locally - render what the API returns.
3. rewards[] on a claim is a manifest: currency rows settle via the signed
grant outbox (same worker as every Pulse feature); free_spins add open
spins; raffle_tickets are ledger-only (no credit, toast only).
4. GGR needs both sides of the ledger: we must report wins as events.
## Pulse API (base https://api.playwithpulse.com,
## "Authorization: Bearer <PULSE_API_KEY>")
- POST /v1/events {"player_id", "type": "wager"|"win"|"purchase",
"value": <smallest units>, "idempotency_key": <unique per round/order>}
Tiers accrue from wagers; GGR = wagers - wins; LTV from purchases.
If we already send wager events, ADD win events (and purchase if absent).
- GET /v1/vip/players/{player_id}
-> {tier, tier_index, wagered, tiers:[{name, threshold}], next_tier,
needed, progress_pct,
benefits:[{tier, cadence, period_id, reward, rakeback?, eligible,
activity_ok, claimed, claimable, ...}]}
rakeback (when configured): {percent_bp, effective_bp, boosts_applied,
of, base, amount, cap, currency,
accruing: {period_id, ends_at, base, effective_bp, amount,
wagered_so_far, activity_ok}}.
amount = claimable NOW (previous closed period); accruing = the CURRENT
period's running tab - render it as "next bonus so far" with a countdown
to ends_at. Display only: it becomes claimable when the period closes.
- POST /v1/vip/claim {"player_id", "idempotency_key": "uuid"}
-> {status: "claimed"|"nothing_to_claim", tier,
claimed:[{tier, cadence, period_id, rakeback_amount}], rewards:[...]}
Claims every claimable benefit at once; once per benefit per period,
enforced server-side. "nothing_to_claim" is an answer, not an error.
## Build these pieces
A. Backend GET /api/pulse/vip (authenticated player): proxy the player view.
B. Backend POST /api/pulse/vip/claim: mint a UUID idempotency key, call
Pulse, return {status, claimed, rewards} with display labels.
C. A VIP page/panel: tier badge, progress bar (wagered/needed/progress_pct
from the API), the tier ladder, and a "claim" button that appears when
any benefit is claimable; one toast per rewards[] row on success.
D. Rakeback display: when benefits[].rakeback exists, show BOTH numbers:
"claimable now: <amount>" (last closed period) AND "accruing:
<accruing.amount> so far - pays <weekday from accruing.ends_at>" so the
bonus visibly grows as they play. Use effective_bp from each block;
never hardcode a percentage - rates are per-player (signal boosts).
E. Win events: in our game-result handler, POST type:"win" with the payout
amount and a per-round idempotency key. Without wins, GGR overstates and
rakeback overpays players who won.
## Acceptance checklist
- [ ] Tier and progress bar render purely from the API.
- [ ] Claiming twice: first pays, second returns nothing_to_claim; exactly
one wallet credit per currency reward (outbox idempotent by grant_id).
- [ ] rakeback amounts match the API view exactly; no local math.
- [ ] win events flow for every settled round.
- [ ] No PULSE_API_KEY in built frontend assets.
Ask me for: PULSE_API_KEY, the player id mapping, our game-result hook (for
win events), and where the wallet credit function lives.