BricqsBricqs

Pattern: spin campaign

A 10-day sale spin wheel with email gate, server-side prize draw, one spin per identity per day, and one-tap redeem. The whole campaign ships in a working day if your auth and ESP are already wired.

Last updatedMay 2026

Key takeaways

Quick read
  • Configure the spin engagement once. Prize segments and odds live server-side.
  • Email gate runs before the spin, not after. Capture rate halves if you wait.
  • Server-side prize draw. The client animates to the predetermined outcome; never decides.
  • One spin per identity per day enforced server-side via idempotency keys.
  • Auto-apply the reward at checkout via API; no copy-paste codes.

Anatomy

What you are building

API

The spin engagement (segments, weights, prizes, window, rate limit) is built in the dashboard. Your app submits each spin; the server runs the weighted draw and returns the prize.

SDK

useSpinWheel({ engagementId, activityId, config }) returns the segments and a spin() that resolves to the server-drawn prize.

User sees

Email field, then a spinning wheel that lands on a prize. Reveal moment, then a one-tap claim button.

Step 1: config

Build the spin in the dashboard

The spin engagement is created in the Builder, not via a public API. You configure it there, publish it, and your app renders it with the SDK. What you set up in the Builder:

text
Spin wheel engagement (Builder -> new engagement -> Spin Wheel):

  Window            2026-10-25 to 2026-11-05
  Rate limit        1 spin per identity per day
  Segments          weight     prize
    Free outfit     1          voucher 5000
    500 INR off     15         voucher 500
    Free shipping   35         coupon / benefit
    5% off          49         coupon 5%

Prizes map to reward definitions you also create in the dashboard. The server
does the weighted draw at spin time; the client never sees the odds.

Step 2: email gate

Capture before the spin

components/SpinGate.tsx·tsx
"use client";
import { useState } from "react";

export function SpinGate({ onPass }: { onPass: () => void }) {
  const [email, setEmail] = useState("");
  return (
    <form
      onSubmit={async (e) => {
        e.preventDefault();
        await fetch("/api/bricqs/identify", {
          method: "POST",
          body: JSON.stringify({ email }),
        });
        onPass();
      }}
    >
      <h2>Spin to win up to 5,000 INR off</h2>
      <input
        type="email"
        required
        value={email}
        onChange={(e) => setEmail(e.target.value)}
        placeholder="you@email.com"
      />
      <button>Spin to reveal</button>
    </form>
  );
}

The /api/bricqs/identify route on your server creates or upserts the participant via the admin API and returns a participant token.

Step 3: spin

Server-determined draw

components/SpinWheel.tsx·tsx
"use client";
import { useSpinWheel } from "@bricqs/sdk-react";

export function SpinWheel() {
  const { isSpinning, result, rotation, spin, confirmResult } = useSpinWheel({
    engagementId: "diwali_spin_2026",
    activityId: "spin_wheel_1",
    config: { segments: SEGMENTS },
  });

  async function onSpin() {
    // Server draws the result first — the client only animates to it.
    const outcome = await spin();
    await animateToRotation(rotation);
    // Records completion + applies the reward's onCompletion actions.
    await confirmResult();
    if (outcome) setRevealedReward(outcome.segment.label);
  }

  return (
    <button onClick={onSpin} disabled={isSpinning}>
      {result ? "Spin again" : "Spin"}
    </button>
  );
}

The server draws before the wheel moves, so the animation always lands on the real prize. confirmResult() applies the reward once the animation completes.

Step 4: claim

Auto-apply the reward

tsx
async function claimAndCheckout(rewardId: string) {
  const res = await fetch("/api/bricqs/claim-reward", {
    method: "POST",
    body: JSON.stringify({ rewardId }),
  });
  const { code } = await res.json();

  // Apply to the cart server-side, route to checkout
  await applyDiscountCode(code);
  router.push("/checkout");
}

Step 5: anti-fraud

What runs automatically

text
Bricqs enforces:
  - One spin per identity per day (configured rate_limit)
  - max_reward_liability cap; jackpot allocation halts when exceeded
  - Server-side draw (segment is decided on the server, never on the client)
  - Disposable email pattern detection
  - IP and device velocity caps (cross-day attempts from same fingerprint)

You should add:
  - hCaptcha or Turnstile on the email gate for cold traffic
  - Email confirmation step before claiming high-value prizes
  - Manual review for jackpot wins above a threshold

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.