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

POST /admin/engagements creates the spin. POST /events submits each spin. The server runs the draw.

SDK

useEngagement({ engagementId, sync: true }) returns config and submit(). Result includes the prize segment.

User sees

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

Step 1: config

Define the spin

POST /api/v1/admin/engagements·bash
curl -X POST https://api.bricqs.co/api/v1/admin/engagements \
  -d '{
    "id": "diwali_spin_2026",
    "type": "spin_wheel",
    "window": { "start": "2026-10-25", "end": "2026-11-05" },
    "rate_limit": { "per_identity_per_day": 1 },
    "segments": [
      { "id": "jackpot",    "label": "Free outfit (jackpot)", "weight": 1,
        "reward": { "type": "voucher", "value": 5000 } },
      { "id": "mid_voucher","label": "500 INR off",           "weight": 15,
        "reward": { "type": "voucher", "value": 500 } },
      { "id": "shipping",   "label": "Free shipping",         "weight": 35,
        "reward": { "type": "perk", "id": "free_shipping_one_order" } },
      { "id": "small_off",  "label": "5% off",                "weight": 49,
        "reward": { "type": "coupon", "value_percent": 5 } }
    ],
    "max_reward_liability": 500000
  }'

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 { useEngagement } from "@bricqs/headless-react";

export function SpinWheel() {
  const { engagement, submit } = useEngagement({
    engagementId: "diwali_spin_2026",
    sync: true,
  });

  async function spin() {
    const result = await submit({
      attributes: {},
      attemptId: `diwali_spin:${userId}:${todayDateString}`,
    });

    // Animate the wheel to land on the predetermined segment
    await animateToSegment(result.outcome.segmentId);

    // Show the reveal moment with the actual prize
    setRevealedReward(result.outcome.reward);
  }

  if (!engagement) return null;
  return <button onClick={spin}>Spin</button>;
}

attemptId binds to userId + day. Server enforces one spin per identity per day; retries return the same outcome.

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.