BricqsBricqs

Pattern: loyalty tier engine

A complete loyalty layer: points earned per purchase, three tiers (Bronze, Silver, Gold) with perks per tier, annual re-qualification. Configured server-side, rendered client-side, audited monthly.

Reading time10 minutes
Last updatedMay 2026

Key takeaways

Quick read
  • Earn rules grant points on purchase events. Server runs the math; you POST the event.
  • Tier qualification reads lifetime points (never decreases). Re-qualify annually.
  • Perks are configured per tier; they apply automatically when the participant transacts.
  • Use useTier on the dashboard, useRewards on the wallet, usePoints in the header.
  • Liability is a real number; track outstanding points and projected redemption monthly.

Anatomy

What you are building

API

POST /admin/earn-rules wires events to point grants. POST /admin/tiers defines thresholds. POST /admin/perks attaches benefits.

SDK

usePoints, useTier, useBadges render the wallet. useRewards exposes claimable perks.

User sees

Points balance in the header, tier badge with progress bar to the next tier, claimable perks section.

Step 1: earn rules

Wire purchases to points

POST /api/v1/admin/earn-rules·bash
curl -X POST https://api.bricqs.co/api/v1/admin/earn-rules \
  -H "Authorization: Bearer bq_live_admin_..." \
  -d '{
    "id": "purchase_base",
    "event_type": "purchase_completed",
    "grant": { "amount_attribute": "amount", "ratio": 0.01 },
    "comment": "1 point per INR spent (rounded down)"
  }'

# Bonus rule for skincare category
curl -X POST https://api.bricqs.co/api/v1/admin/earn-rules \
  -d '{
    "id": "skincare_2x",
    "event_type": "purchase_completed",
    "match": { "attributes": { "category": "skincare" } },
    "grant": { "amount_attribute": "amount", "ratio": 0.02 }
  }'

Step 2: tiers

Three tiers, percentile thresholds

bash
POST /api/v1/admin/tiers
{
  "tiers": [
    { "id": "bronze", "label": "Bronze", "qualifying_lifetime_points": 0 },
    { "id": "silver", "label": "Silver", "qualifying_lifetime_points": 5000 },
    { "id": "gold",   "label": "Gold",   "qualifying_lifetime_points": 25000 }
  ],
  "requalification": { "period": "year", "policy": "rolling_12_months" }
}

Rolling 12-month requalification keeps tiers fair without punitive resets. Members re-qualify when their last 12 months of points hit the threshold.

Step 3: perks

Attach benefits per tier

bash
POST /api/v1/admin/perks
{
  "perks": [
    {
      "id": "free_shipping_silver_plus",
      "label": "Free shipping",
      "applies_to_tiers": ["silver", "gold"],
      "auto_apply_on_event": "checkout_initiated"
    },
    {
      "id": "early_access_gold",
      "label": "Early access to launches",
      "applies_to_tiers": ["gold"],
      "manual_unlock": true
    },
    {
      "id": "birthday_voucher",
      "label": "Birthday voucher",
      "applies_to_tiers": ["bronze", "silver", "gold"],
      "trigger": "birthday",
      "value_by_tier": { "bronze": 100, "silver": 250, "gold": 500 }
    }
  ]
}

Step 4: render

Three hooks for the wallet

components/LoyaltyWallet.tsx·tsx
"use client";
import { usePoints, useTier, useRewards } from "@bricqs/headless-react";

export function LoyaltyWallet() {
  const { available } = usePoints();
  const { current, next, progressPercent, pointsToNext } = useTier();
  const { earned, claim } = useRewards({ filter: { type: "perk" } });

  return (
    <section className="grid gap-6">
      <div>
        <p className="text-sm text-slate-500">Points</p>
        <p className="text-3xl font-bold">{available.toLocaleString()}</p>
      </div>
      {current && (
        <div>
          <p className="font-bold">{current.label}</p>
          {next && (
            <>
              <ProgressBar percent={progressPercent} />
              <small className="text-slate-500">
                {pointsToNext.toLocaleString()} pts to {next.label}
              </small>
            </>
          )}
        </div>
      )}
      {earned.length > 0 && (
        <div>
          <h3 className="font-bold">Perks ready to use</h3>
          <ul className="grid gap-2">
            {earned.map((p) => (
              <li key={p.id}>
                {p.label}{" "}
                <button onClick={() => claim(p.id)}>Use</button>
              </li>
            ))}
          </ul>
        </div>
      )}
    </section>
  );
}

Step 5: audits

Monthly health check

text
Reports to pull monthly (via /admin/reports endpoints):

  Active member share          target 40 to 65%
  Reward percentage             target 1 to 5% of revenue
  Outstanding liability ratio   target 1 to 4% of trailing 90-day revenue
  Tier distribution             watch for over-promotion (Gold > 10% is a flag)
  Redemption velocity           target 55 to 75%

Build a Bricqs Studio dashboard once; review with finance every month.

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.

1 brief to align the room2 mechanics max in version one
What happens next
01
Pick the mechanic
Choose the smallest working system for the brief.
02
Launch without rebuilds
Configure rules and rewards in one place.