BricqsBricqs

Headless SDK: rewards

useRewards returns the participant's earned, claimable, and redeemed rewards. claim() turns an earned reward into a redemption. The hook handles expiry, partial states, and idempotency.

Reading time6 minutes
Last updatedMay 2026

Key takeaways

Quick read
  • useRewards returns earned (claimable) and redeemed lists. Status is per-reward.
  • Call claim(rewardId) to issue a coupon code or trigger a fulfilment. The result includes the code and expiry.
  • Always show expiry. Hidden expiry is the most common reward complaint.
  • claim is idempotent on the SDK side: clicking twice returns the same code.
  • For physical rewards (merchandise, samples), claim only collects fulfilment data; shipping happens server-side.

Render rewards

Earned and redeemed

components/MyRewards.tsx·tsx
"use client";
import { useRewards } from "@bricqs/headless-react";

export function MyRewards() {
  const { earned, redeemed, claim, isLoading } = useRewards();

  if (isLoading) return null;

  return (
    <section className="grid gap-6">
      <div>
        <h3 className="font-bold mb-3">Ready to claim</h3>
        {earned.length === 0 ? (
          <p className="text-slate-500">Nothing yet.</p>
        ) : (
          <ul className="grid gap-3">
            {earned.map((r) => (
              <li key={r.id} className="rounded-xl border p-4 flex items-center gap-4">
                <div className="flex-1">
                  <strong>{r.label}</strong>
                  <p className="text-sm text-slate-500">{r.description}</p>
                  <small className="text-slate-400">
                    Expires {r.expiresAt}
                  </small>
                </div>
                <button
                  onClick={async () => {
                    const result = await claim(r.id);
                    showCode(result.code);
                  }}
                  className="px-4 py-2 rounded-lg bg-orange-500 text-white"
                >
                  Claim
                </button>
              </li>
            ))}
          </ul>
        )}
      </div>

      {redeemed.length > 0 && (
        <details>
          <summary className="font-bold cursor-pointer">Already redeemed ({redeemed.length})</summary>
          <ul className="mt-3 grid gap-2">
            {redeemed.map((r) => (
              <li key={r.id} className="text-sm text-slate-600">
                {r.label} · {r.code} · used {r.redeemedAt}
              </li>
            ))}
          </ul>
        </details>
      )}
    </section>
  );
}

Claim

What claim() returns by reward type

ts
// Coupon and voucher: server returns a one-time-use code
const r = await claim("rwd_42");
// r = { code: "WELCOME-Q7P2", expiresAt: "2026-05-30T23:59:59Z", url: null }

// Perk (free shipping, member pricing): server applies it on the account
const r = await claim("rwd_freeship");
// r = { code: null, expiresAt: null, url: null, applied: true }

// Physical: server records intent, fulfilment runs separately
const r = await claim("rwd_sample_kit", {
  shipping: { name, addressLines, city, postalCode, country }
});
// r = { code: null, fulfilmentId: "ful_91xx", trackingUrl: null }

Common mistakes

What goes wrong

01Mistake

Claim happens on render. The reward is auto-claimed before the user sees it.

Fix

Bind claim to a user gesture (button click). Never call from useEffect.

02Mistake

Showing the code as plaintext in the URL. Crawlers steal it.

Fix

Show the code in the UI. Send it via a separate authenticated email; never put codes in URLs.

03Mistake

Hiding expiry. Users discover the reward is dead at checkout.

Fix

Render expiresAt on the earned card and on the redeemed list. Send a 7-day reminder via webhook.

04Mistake

Calling claim twice on a slow click. The user thinks two rewards were issued.

Fix

Disable the button after click. claim is idempotent server-side; the second call returns the same code.

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.