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

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.

GGR needs both sides of the ledger. You already send wager events; add type: "win" with the payout amount per settled round. Without wins, GGR overstates and rakeback overpays players who won.

Endpoint summary

EndpointPurposeBillable
GET /v1/vip/players/{id}tier + progress + claim stateno
POST /v1/vip/claimclaim all claimable benefitsyes (1 token, paying claims only)
POST /v1/eventswager / win / purchase signalper plan
Tiers, benefits, rakeback rates, boosts, ceilings, and caps are all authored in Pulse Studio's VIP tab. Your integration is the two calls above plus win events.

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.