Headless SDK
Enterprise-grade React hooks that return pure data, state, and action handlers — zero rendering. Build your own UI with your design system while Bricqs handles all backend logic.
Architecture
The Headless SDK is a layer of React hooks that sit on top of the existing SDK infrastructure. Hooks call the Bricqs API through the SDK client, manage state with React, and return typed objects — you render everything yourself.
┌──────────────────────────────────────────────────────┐ │ Your Custom UI (your components, your design system) │ │ │ │ useQuiz() useSpinWheel() useForm() │ │ usePoll() useSurvey() useBadgesHeadless() │ │ usePoints() useTier() useChallenge() │ │ useLeaderboard() useRewardsHeadless() │ │ useReferral() useContest() useGates() │ │ useActivityProgress() useActivityCalendar() │ │ │ │ Returns: data + state + handlers. Zero JSX. │ ├───────────────────────────────────────────────────────┤ │ BricqsProvider (context + SDK client) │ │ │ │ BricqsClient.activities — validate, complete │ │ BricqsClient.challenges — enroll, progress │ │ BricqsClient.leaderboards — rankings │ │ BricqsClient.rewards — claimed rewards │ │ BricqsClient.badges — earned/unearned status │ │ BricqsClient.points — balance, transactions │ ├───────────────────────────────────────────────────────┤ │ Bricqs API Server │ │ │ │ Activity validation + action execution │ │ Points, badges, tiers, rewards (atomic, server-side) │ │ Challenge evaluation + progress tracking │ └───────────────────────────────────────────────────────┘
Quick Start
Build a completely custom quiz UI in 5 minutes. Bricqs handles scoring, point awards, badge unlocks, and reward claims — you handle the rendering.
import {
BricqsProvider,
useEngagementData,
useQuiz,
usePoints,
} from '@bricqs/sdk-react';
// 1. Wrap your app
function App() {
return (
<BricqsProvider config={{ apiKey: 'bq_live_YOUR_KEY' }}>
<MyCustomQuiz />
</BricqsProvider>
);
}
// 2. Load engagement and get quiz config
function MyCustomQuiz() {
const { getQuizConfig, getComponent, isLoading } = useEngagementData({
engagementId: 'YOUR_ENGAGEMENT_UUID',
});
const quizConfig = getQuizConfig();
const quizComponent = getComponent('quiz');
const points = usePoints({ engagementId: 'YOUR_ENGAGEMENT_UUID' });
if (isLoading || !quizConfig || !quizComponent) {
return <div>Loading...</div>;
}
return (
<QuizUI
engagementId="YOUR_ENGAGEMENT_UUID"
activityId={quizComponent.id}
config={quizConfig}
pointsBalance={points.balance}
/>
);
}
// 3. Build your own quiz UI — zero Bricqs styling
function QuizUI({ engagementId, activityId, config, pointsBalance }) {
const quiz = useQuiz({ engagementId, activityId, config });
if (quiz.isComplete) {
return (
<div className="my-results">
<h2>{quiz.feedback?.title}</h2>
<p>Score: {quiz.score?.percentage}%</p>
{quiz.actionResults?.points_awarded > 0 && (
<p className="points">+{quiz.actionResults.points_awarded} points!</p>
)}
<button onClick={quiz.reset}>Try Again</button>
</div>
);
}
return (
<div className="my-quiz">
<div className="progress-bar" style={{ width: quiz.progress + '%' }} />
<p className="balance">{pointsBalance} pts</p>
<h3>{quiz.currentQuestion.question}</h3>
<div className="options">
{quiz.currentQuestion.options.map((opt, i) => (
<button
key={i}
className={quiz.selectedAnswer === i ? 'selected' : ''}
onClick={() => quiz.selectAnswer(i)}
>
{opt}
</button>
))}
</div>
<div className="nav">
{!quiz.isFirstQuestion && <button onClick={quiz.previous}>Back</button>}
{quiz.isLastQuestion ? (
<button onClick={quiz.submit} disabled={quiz.isSubmitting}>
Submit
</button>
) : (
<button onClick={quiz.next}>Next</button>
)}
</div>
</div>
);
}Hook Reference
Detailed API docs for every hook, organized by category.
useQuiz, useSpinWheel, useForm, usePoll, useSurvey — interactive activity lifecycle from config to submission.
usePoints, useTier, useBadgesHeadless, useChallenge, useLeaderboard, useRewardsHeadless — gamification data with auto-refresh.
useEngagementData, useReferral, useContest, useGates, useActivityProgress, useActivityCalendar.
QuizProvider with render-prop slots — customize the look while keeping built-in state management.
Server-Side Actions
When a headless activity hook submits (e.g., quiz.submit()), the server executes all configured on-completion actions atomically. The actionResults object contains the outcomes.
// After quiz.submit() resolves:
const results = quiz.actionResults;
// Points
if (results?.points_awarded > 0) {
showPointsAnimation(results.points_awarded);
}
// Badges
if (results?.badges_unlocked?.length > 0) {
results.badges_unlocked.forEach(badge => {
showBadgeUnlocked(badge.name, badge.icon);
});
}
// Tier upgrade
if (results?.tier_upgrade) {
showTierCelebration(results.tier_upgrade.new_tier);
}
// Reward claimed
if (results?.reward_claimed) {
showCouponCode(results.reward_claimed.code_value);
}
// Challenge progress
if (results?.challenge_progress) {
showProgressUpdate(results.challenge_progress);
}Full Example: Custom Campaign Page
A complete example with a custom quiz UI, points display, badge shelf, and challenge progress — all with your own design system.
import {
BricqsProvider,
useEngagementData,
useQuiz,
usePoints,
useTier,
useBadgesHeadless,
useChallenge,
useLeaderboard,
useReferral,
useContest,
useGates,
useActivityProgress,
useActivityCalendar,
} from '@bricqs/sdk-react';
const ENGAGEMENT_ID = 'your-engagement-uuid';
function App() {
return (
<BricqsProvider config={{ apiKey: 'bq_live_xxx' }}>
<CampaignPage />
</BricqsProvider>
);
}
function CampaignPage() {
const { getQuizConfig, getComponent, isLoading } = useEngagementData({
engagementId: ENGAGEMENT_ID,
});
if (isLoading) return <LoadingSpinner />;
return (
<div className="campaign-layout">
<main>
<QuizSection
config={getQuizConfig()!}
componentId={getComponent('quiz')!.id}
/>
</main>
<aside>
<PointsCard />
<BadgeShelf />
<ChallengeProgress />
<TopPlayers />
</aside>
</div>
);
}
function PointsCard() {
const points = usePoints({ engagementId: ENGAGEMENT_ID });
const tier = useTier({ engagementId: ENGAGEMENT_ID });
return (
<div className="points-card">
<span className="balance">{points.balance}</span>
<span className="label">points</span>
{tier.currentTier && (
<div className="tier" style={{ color: tier.tierColor || '#666' }}>
{tier.tierName} — {tier.pointsToNext} pts to {tier.nextTierName}
</div>
)}
</div>
);
}
function BadgeShelf() {
const { badges } = useBadgesHeadless({ engagementId: ENGAGEMENT_ID });
return (
<div className="badge-shelf">
{badges.map(badge => (
<div key={badge.code} className={badge.earned ? 'earned' : 'locked'}>
<img src={badge.icon} alt={badge.name} />
<span>{badge.name}</span>
</div>
))}
</div>
);
}
function ChallengeProgress() {
const ch = useChallenge({ engagementId: ENGAGEMENT_ID, autoEnroll: true });
if (!ch.challenge) return null;
return (
<div className="challenge">
<h3>{ch.challenge.name}</h3>
<div className="progress-bar">
<div style={{ width: ch.progressPercentage + '%' }} />
</div>
<p>{ch.completedObjectives}/{ch.totalObjectives} objectives</p>
</div>
);
}
function TopPlayers() {
const lb = useLeaderboard({
code: 'main',
engagementId: ENGAGEMENT_ID,
limit: 5,
});
return (
<ol className="leaderboard">
{lb.entries.map(e => (
<li key={e.rank}>
<span className="rank">#{e.rank}</span>
<span className="name">{e.name}</span>
<span className="score">{e.score}</span>
</li>
))}
</ol>
);
}Migrating from iframe to Headless
If you already use the React SDK, keep the same provider setup. Headless hooks work within the same context.
Replace <BricqsEngagement /> with useEngagementData() + activity hooks. Build your own rendering layer.
Instead of onPointsAwarded callback props, read from quiz.actionResults.points_awarded after submission.
Replace any iframe-based progression displays with usePoints(), useBadgesHeadless(), useChallenge(), and useLeaderboard().
