Pattern: streak with freeze
A daily streak with a one-freeze-per-week safety net and milestones at 7, 30, 100, and 365 days. Server-side config, server-side ticks, client-side rendering. Roughly 80 lines of code total.
Key takeaways
Quick read- Streak config lives server-side. Period (day), grace (24h), freeze allowance (1/week), milestones (7/30/100/365).
- Tick from the server when the qualifying action lands. Idempotency key on the date.
- Client renders count, freeze inventory, grace state, and the next milestone with one hook.
- Milestones fire webhooks; route them to your ESP for celebration emails.
- Recovery: when the user misses a day, surface the freeze option for the duration of the grace window.
Anatomy
What you are building
API
POST /admin/streaks defines the streak. POST /events ticks it. Webhook fires on milestones.
SDK
useStreak({ streakId }) renders count, freezesAvailable, gracePeriodEndsAt, nextMilestone, and exposes spendFreeze().
User sees
A flame counter that grows each day, a 'use a freeze' button when grace kicks in, and confetti at 7, 30, 100, 365 days.
Step 1: config
Define the streak once
curl -X POST https://api.bricqs.co/api/v1/admin/streaks \
-H "Authorization: Bearer bq_live_admin_..." \
-d '{
"id": "daily_practice",
"label": "Daily practice",
"period": "day",
"qualifying_event": "practice_completed",
"grace_hours": 24,
"freeze_allowance": { "per_period": "week", "max": 1, "stockpile_max": 4 },
"milestones": [
{ "days": 7, "reward": { "type": "badge", "id": "streak_7" } },
{ "days": 30, "reward": { "type": "voucher","value": 200 } },
{ "days": 100, "reward": { "type": "badge", "id": "streak_100" } },
{ "days": 365, "reward": { "type": "experience", "id": "streak_year" } }
]
}'Step 2: tick
Server-side, on every qualifying action
import { emitToBricqs } from "@/lib/bricqs";
export async function logPractice(userId: string) {
await savePractice(userId);
const today = new Date().toISOString().slice(0, 10);
await emitToBricqs(
userId,
"practice_completed",
{ practice_id: "daily" },
`p_${userId}:practice_completed:${today}`
);
}Idempotent on the date. Multiple practice events on the same day count once.
Step 3: render
One hook, full state
"use client";
import { useStreak } from "@bricqs/headless-react";
export function StreakWidget() {
const {
count,
freezesAvailable,
gracePeriodEndsAt,
nextMilestone,
spendFreeze,
onMilestoneHit,
} = useStreak({ streakId: "daily_practice" });
onMilestoneHit((event) => {
fireConfetti();
showToast(`${event.days}-day streak earned!`);
});
return (
<section className="rounded-xl border p-5">
<header className="flex items-baseline justify-between mb-2">
<h3 className="font-bold text-2xl">{count} day streak</h3>
{freezesAvailable > 0 && (
<span className="text-sm text-slate-500">
{freezesAvailable} freeze{freezesAvailable === 1 ? "" : "s"} left
</span>
)}
</header>
{gracePeriodEndsAt && (
<p className="text-amber-600 text-sm">
You missed yesterday. Use a freeze before {gracePeriodEndsAt}?
<button onClick={() => spendFreeze()} className="ml-2 underline">
Use freeze
</button>
</p>
)}
{nextMilestone && (
<p className="text-sm text-slate-500 mt-3">
Next milestone: {nextMilestone.days} days · earns {nextMilestone.rewardLabel}
</p>
)}
</section>
);
}Step 4: celebrations
Wire milestone webhooks to your ESP
if (event.type === "streak.milestone_hit") {
const { participant_id, days, reward_id } = event.data;
await sendCelebrationEmail(participant_id, {
template: `streak_${days}_day`,
rewardId: reward_id,
});
}Pre-launch checklist
Before you ship
Configuration
[ ] Streak created (POST /admin/streaks)
[ ] Period, grace, and freeze allowance set per category
[ ] Milestone rewards funded (badge ids exist; voucher inventory ready)
Server
[ ] practice_completed event fires on qualifying action
[ ] Idempotency key uses local date in user timezone (not UTC)
[ ] Webhook handler verifies HMAC
Client
[ ] StreakWidget on home screen
[ ] Freeze button visible during grace
[ ] Confetti / toast on milestone
Recovery
[ ] Email at 24h after miss with freeze CTA
[ ] Email at 48h after miss confirming streak ended (if not frozen)Developer FAQ
Common questions when integrating gamification with Bricqs.
Ready to ship?
Wire it up with the Bricqs SDK or API
Headless SDK for React UIs, REST API for any backend. Same engine behind both.
