What it is
A weighted prize wheel built on /api/pick. Pass the segment labels (10% off, free shipping, $5 credit, grand prize) and segment weights (their angular share of the wheel), and the API returns the winning segment plus a serverHash a player can verify after the spin.
The wheel animation in the UI is purely visual; the segment that lands under the pointer is whichever segment the API selected.
The pain point
Spin-to-win promotions get accused of two things: "the grand prize never actually lands" and "the wheel landed on what the promotion needed for margin." Both accusations vanish when the segment weights are public and the per-spin outcome is verifiable. The wheel becomes UI decoration over a transparent calculation.
Try it live — one spin on a 6-segment wheel
curl "https://api.provable.io/api/pick?clientSeed=spin_user_42_2026_05_25_001&items=10pct_off,20pct_off,free_ship,5_credit,try_again,grand_prize&weights=35,15,25,15,9.5,0.5"
A second spin (different nonce → independent outcome):
curl "https://api.provable.io/api/pick?clientSeed=spin_user_42_2026_05_25_002&items=10pct_off,20pct_off,free_ship,5_credit,try_again,grand_prize&weights=35,15,25,15,9.5,0.5"
Integration snippet
// 1. Publish the wheel segments + weights on a public /wheel-rates page.
const WHEEL = [
{ id: "10pct_off", weight: 35.0 },
{ id: "20pct_off", weight: 15.0 },
{ id: "free_ship", weight: 25.0 },
{ id: "5_credit", weight: 15.0 },
{ id: "try_again", weight: 9.5 },
{ id: "grand_prize", weight: 0.5 },
];
async function spin(userId, spinIndex) {
const url = new URL("https://api.provable.io/api/pick");
url.searchParams.set("clientSeed", `spin_${userId}_${spinIndex}`);
url.searchParams.set("items", WHEEL.map((s) => s.id).join(","));
url.searchParams.set("weights", WHEEL.map((s) => s.weight).join(","));
const res = await fetch(url, {
headers: { "x-api-key": process.env.PROVABLE_KEY }
});
const { outcome: segmentId, index, serverHash, shortId } = await res.json();
// 2. Drive the wheel animation toward segmentId at index `index`.
// The UI rotation is cosmetic — the result is already decided.
return { segmentId, serverHash, permalink: `/o/${shortId}` };
}
Why this is fair
- Public segment weights. The "rates page" is the equivalent of the loot-box odds disclosure: nobody can argue about the math because the math is published.
- One spin, one seed. Each spin uses a fresh per-user, per-spin
clientSeedso spins are independent and individually re-derivable. - Pre-spin commitment (optional). For high-stakes campaigns, use
/api/commit+/api/revealso the server's seed is hashed and pinned before any user spins.
Common patterns
- Daily spin cooldowns. The seed key encodes the day (
spin_${userId}_2026-05-25) — same user, same day, same result (a property players can verify). - Tiered wheels. Different segment lists for VIP / standard tiers. Publish each tier's weights so users in either pool can compare.
- Inventory caps. If the grand prize is a single physical item, gate the segment server-side after the verified spin — the user still sees the segment they landed on and "out of stock" is the published consolation rule.
Where it fits
- Ecommerce checkout promotions (post-purchase spin for a discount).
- Loyalty programs with daily reward wheels.
- Conference / event booths running live spins on a kiosk.
- Mobile games with daily login wheels or banner-tied bonus spins.