Headless SDK: engagement components
Quizzes, spins, scratches, and predictions all share one hook: useEngagement. The hook returns the configured experience, the participant's state, and a submit handler that fires the right event with the right idempotency key.
Key takeaways
Quick read- useEngagement loads the engagement and its components. Branch on engagement.type, or read a component with getComponent.
- Config comes from the server. Builder edits propagate without a redeploy.
- For interaction, use the matching activity hook — useSpinWheel, useQuiz, useForm — or the generic useActivity (validate + complete).
- Result data (the spin segment, the quiz score) comes back from the activity hook, not from useEngagement.
- Always render an empty state. Engagements can be paused or deleted server-side.
Quickstart
A working spin in 35 lines
"use client";
import { useSpinWheel } from "@bricqs/sdk-react";
export function SpinWheel({ engagementId, activityId }: {
engagementId: string; activityId: string;
}) {
const { isSpinning, result, rotation, spin, confirmResult } = useSpinWheel({
engagementId,
activityId,
config: { segments: SEGMENTS },
});
async function onSpin() {
// Server draws first; the client only animates to the result.
const outcome = await spin();
await animateToRotation(rotation);
await confirmResult(); // records completion + applies the reward
if (outcome) showRevealAnimation(outcome.segment.label);
}
return (
<button onClick={onSpin} disabled={isSpinning} className="px-6 py-3 rounded-xl bg-orange-500 text-white">
{result ? "Spin again" : "Spin to win"}
</button>
);
}Quiz
A 6-question quiz with branching
"use client";
import { useQuiz } from "@bricqs/sdk-react";
export function Quiz({ engagementId, activityId }: {
engagementId: string; activityId: string;
}) {
const {
currentQuestion, currentIndex, totalQuestions,
selectAnswer, next, isLastQuestion,
submit, isComplete, score,
} = useQuiz({ engagementId, activityId, config: { questions: QUESTIONS } });
if (isComplete) {
window.location.href = `/quiz/result?score=${score?.correct}`;
return null;
}
async function choose(optionIndex: number) {
selectAnswer(optionIndex);
if (isLastQuestion) await submit();
else next();
}
return (
<article>
<p className="text-sm text-slate-500">
Question {currentIndex + 1} of {totalQuestions}
</p>
<h2 className="text-xl font-bold mb-4">{currentQuestion.question}</h2>
<ul className="grid gap-3">
{currentQuestion.options.map((opt, i) => (
<li key={i}>
<button
onClick={() => choose(i)}
className="w-full text-left rounded-xl border p-3 hover:border-orange-500"
>
{opt}
</button>
</li>
))}
</ul>
</article>
);
}Scratch
Server-determined reveal
// The generic activity hook fits scratch cards: validate to learn the outcome,
// then complete to record it + apply rewards.
const { validate, complete } = useActivity({
engagementId, activityId, activityType: "scratch_card",
});
async function reveal() {
const { outcome } = await validate({}); // server decides the prize
animateScratch(outcome); // animate the reveal to that outcome
await complete({}); // record completion + apply rewards
}validate() returns the server-decided outcome so you can animate to it; complete() records it. The client never decides what was won.
Common mistakes
What goes wrong
Determining the prize on the client and sending it as an event. Trivially gamed.
Use validate()/spin() — the server returns the outcome; the client only animates to it.
Skipping confirmResult()/complete() after the animation. Rewards never get applied.
Draw with spin()/validate(), then call confirmResult()/complete() once the reveal finishes.
Hardcoding the question count. Builder updates break the UI.
Read totalQuestions from useQuiz. The hook reflects live config; builder edits show up without a redeploy.
Triggering the draw on every render. spin()/validate() fired in useEffect by mistake.
Call it from a user gesture (click, swipe, scratch). Never from useEffect.
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.
