Pattern: sports prediction
A pre-match prediction game across a tournament. Users pick the outcome, predictions lock at kickoff, scoring runs on the final result, the weekly leaderboard awards prizes. Sponsorable on every screen.
Key takeaways
Quick read- Each match is a contest. Predictions are events with attributes; scoring evaluates after the result is known.
- Kickoff lock is enforced server-side via window.starts_at. Late predictions return 422.
- Weekly leaderboard is a separate contest with a 7-day window; it aggregates the per-match scores.
- Sponsor logos and themes are configured per contest; the SDK reflects them automatically.
- Edge cases (rain, abandonment, retirement) handled via admin endpoints, not by editing the active contest.
Anatomy
What you are building
API
POST /admin/contests creates each match. POST /events submits predictions. PATCH /admin/contests/:id/result records the final outcome.
SDK
useEngagement renders the prediction form; useLeaderboard renders the bracket and the global top.
User sees
Live match list with countdown to lock. Pre-match: pick the winner. Post-match: score, rank, points to next position.
Step 1: config
One contest per match
curl -X POST https://api.bricqs.co/api/v1/admin/contests \
-d '{
"id": "match_qsf_2026_05_03",
"title": "Qualifier Semi-Final, May 3",
"starts_at": "2026-05-03T18:00:00Z",
"ends_at": "2026-05-03T22:00:00Z",
"scoring": {
"event_type": "prediction_submitted",
"score_strategy": "exact_outcome",
"outcome_attribute": "winner",
"rules": [
{ "if": "winner_match", "score": 10 },
{ "if": "score_match", "score": 25 }
]
},
"settings": {
"lock_at": "starts_at",
"result_required_before_scoring": true
}
}'Step 2: aggregate
Weekly leaderboard contest
POST /api/v1/admin/contests
{
"id": "week_18_leaderboard",
"starts_at": "2026-05-01T00:00:00Z",
"ends_at": "2026-05-07T23:59:59Z",
"scoring": {
"event_type": "contest.match_scored",
"score_attribute": "points",
"weight": 1
},
"prize_structure": [
{ "rank_from": 1, "rank_to": 50, "reward": { "type": "voucher", "value": 500 } }
]
}
# When a per-match contest scores, it emits "contest.match_scored" facts.
# The weekly leaderboard sums those facts.Step 3: predict
Submit a prediction
"use client";
import { useEngagement } from "@bricqs/headless-react";
export function PredictionForm({ matchId }: { matchId: string }) {
const { engagement, state, submit } = useEngagement({
engagementId: matchId,
sync: false,
});
async function pick(team: string, score: string) {
await submit({
attributes: { winner: team, score },
attemptId: `prediction:${matchId}:${userId}`,
});
}
if (!engagement) return null;
if (state.locked) return <p>Locked at kickoff.</p>;
return (
<div>
<button onClick={() => pick("home", "2-1")}>Home wins 2-1</button>
<button onClick={() => pick("away", "1-0")}>Away wins 1-0</button>
</div>
);
}Step 4: record result
When the match ends
PATCH /api/v1/admin/contests/match_qsf_2026_05_03/result
{
"outcome": { "winner": "home", "score": "2-1" }
}
# The contest moves to "scoring" state.
# Per-prediction scoring runs server-side using the contest rules.
# When complete, "contest.completed" webhook fires and the weekly leaderboard updates.Step 5: leaderboard
Bracket and global top
"use client";
import { useLeaderboard } from "@bricqs/headless-react";
export function PredictionLeaderboard() {
const top = useLeaderboard({
id: "week_18_leaderboard",
view: "top",
limit: 10,
});
const me = useLeaderboard({
id: "week_18_leaderboard",
view: "bracket",
bracketSize: 7,
});
return (
<div className="grid md:grid-cols-2 gap-6">
<RankList title="Top 10" rows={top.rows} />
<RankList title="Your bracket" rows={me.rows} />
</div>
);
}Step 6: edge cases
Rain, abandonment, walkover
# Match abandoned: void all predictions, no points awarded
PATCH /admin/contests/match_qsf_2026_05_03/void
{ "reason": "abandoned_due_to_rain" }
# Walkover: home team wins by default; predictions for "home" score normal points
PATCH /admin/contests/match_qsf_2026_05_03/result
{ "outcome": { "winner": "home", "score": "walkover" }, "tag": "walkover" }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.
