Daily Login Streaks
A daily claim ladder that rewards consecutive visits — day 1 pays small, day 7 pays big. One endpoint. Pulse tracks the calendar, the grace rules, and the ladder position; you just call claim when the player shows up.
Claim
POST /v1/streaks/{streak_id}/claim
{"player_id": "your-player-ref", "idempotency_key": "uuid"}
-> {"status": "claimed",
"streak": 4, // consecutive days, 1-based
"rung": 4, // ladder position paying out
"currency": "SC", "amount": 500,
"grant": { "grant_id": "...", "signature": "...", ... }}Call it whenever the player logs in or opens their rewards page — the platform sorts out the rest:
- One claim per local day.The day boundary follows the timezone configured for your site (not UTC, not the player's device). Repeat calls the same day return
{"status": "already_claimed", "streak": 4, "next_claim_date": "..."}— an answer, not an error, and never billed. - Grace days.If configured, missing a single day doesn't reset the streak — the platform forgives up to the configured gap.
- Ladder end. Configurable: hold at the top reward for day N+1 onward, or cycle back to day 1.
Rendering a streak calendar
The claim response carries everything a "day 4 of 7" UI needs: current streak, the rung that just paid, and on repeat calls the next_claim_date. Most sites render a 7-day strip with checkmarks up to rung and the upcoming rewards greyed out.
Collecting the reward
Paying claims append a signed grant to your outbox — verify, credit, ack, same as every feature. The response includes the grant inline for immediate UX.
Endpoint summary
| Endpoint | Purpose | Billable |
|---|---|---|
POST /v1/streaks/{id}/claim | claim today's rung | yes (1 token; same-day repeats free) |
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 Rewards "Daily Login Streaks" 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. The Pulse API key (env PULSE_API_KEY, "prw_...") is backend-only:
never in frontend bundles or browser requests.
2. All Pulse calls go frontend -> our backend -> Pulse.
3. Player balances live in OUR wallet. Pulse pays via signed grants we
verify, apply idempotently, and acknowledge.
4. Every claim sends an idempotency_key (UUID per user action).
## Pulse API (base https://api.playwithpulse.com, header
## "Authorization: Bearer <PULSE_API_KEY>")
- POST /v1/streaks/{streak_id}/claim
body {"player_id": str, "idempotency_key": str}
Success -> {"status": "claimed", "streak": int, "rung": int,
"currency": str, "amount": int, "grant": {...}?}
Same local day -> {"status": "already_claimed", "streak": int,
"next_claim_date": "YYYY-MM-DD"} (an answer, not
an error, and not billed). The day boundary uses the timezone
configured for our site on Pulse's side; do NOT compute any streak
or calendar logic locally - render what the API returns. Amounts
are integers in our smallest currency unit (500 = 5.00 SC).
- GET /v1/outbox and POST /v1/outbox/{grant_id}/ack for settlement
(shared with all Pulse features; reuse the outbox worker if one
already exists in this codebase).
## Build these pieces
A. Backend endpoint POST /api/pulse/streak/claim (player must be
authenticated): mint a UUID idempotency key, call Pulse claim with
the player's stable id, return {status, streak, rung, amount_text,
next_claim_date?} where amount_text formats amount/100 with the
currency label.
B. Call it automatically once on login/session start AND expose it to a
"claim today's reward" button. Handle both statuses: "claimed" shows
a reward toast; "already_claimed" quietly updates the calendar UI.
C. A streak calendar UI: a 7-slot strip using the response - checkmarks
for days 1..rung, today highlighted, remaining rungs greyed. (If the
configured ladder is longer/shorter ask me; do not hardcode rewards -
show amounts only for the day just claimed.)
D. Outbox settlement: identical to the Pulse wheel integration. If this
codebase already has the Pulse outbox worker, reuse it unchanged -
streak grants flow through the same pipe. Otherwise implement:
poll GET /v1/outbox; verify HMAC-SHA256(key=PULSE_WEBHOOK_SECRET,
msg=canonical JSON of the grant minus grant_id and signature, keys
sorted, separators ("," ":")) against grant["signature"] in constant
time; apply to the wallet idempotently by grant_id; then ack.
## Acceptance checklist
- [ ] Claiming twice in one (site-timezone) day: first returns
"claimed", second "already_claimed"; exactly one wallet credit.
- [ ] Replaying the same idempotency key returns the identical body.
- [ ] The calendar renders purely from API responses (no local date
math beyond display formatting).
- [ ] Tampered grant signatures are rejected, logged, never acked.
- [ ] PULSE_API_KEY appears nowhere in built frontend assets.
Ask me for: PULSE_API_KEY, PULSE_WEBHOOK_SECRET, our streak_id, the
player id mapping, and the wallet credit function/location.