What it is
A dedicated /api/shuffle endpoint that accepts any list (a 52-card deck, a 60-card TCG deck, a 78-card tarot deck, a draw pile of game tiles) and returns it in a verifiable Fisher-Yates order. The shuffle is driven by the same HMAC-SHA256 stream that backs every other Provable.io endpoint, so any deal can be re-derived from the seed and confirmed against a published serverHash.
The pain point
Online card games run into the same problem every season: a player loses a high-stakes hand, posts on social media, and asks how they're supposed to trust a shuffle they can't see. "Our shuffle is fair, we promise" doesn't end the thread. Publishing the serverHash before the hand starts and letting the player re-derive the shuffled deck after the fact does.
Try it live — shuffle a 5-card hand
curl "https://api.provable.io/api/shuffle?clientSeed=table_7_hand_2026_05_24&items=A%E2%99%A0,K%E2%99%A0,Q%E2%99%A0,J%E2%99%A0,10%E2%99%A0"
Or shuffle a full 52-card deck in one call (POST it as a JSON array — see the API reference for the body shape):
curl -X POST "https://api.provable.io/api/shuffle" \
-H "Content-Type: application/json" \
-H "x-api-key: $PROVABLE_KEY" \
-d '{
"clientSeed": "blackjack_table_9_shoe_42",
"items": ["A\u2660", "2\u2660", "...all 52 cards..."]
}'
Integration snippet
// Deal a 5-card poker hand from a shuffled deck.
function freshDeck() {
const suits = ["\u2660", "\u2665", "\u2666", "\u2663"];
const ranks = ["A", "2", "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K"];
return suits.flatMap((s) => ranks.map((r) => r + s));
}
async function dealHand(handId, n = 5) {
const url = new URL("https://api.provable.io/api/shuffle");
url.searchParams.set("clientSeed", `hand_${handId}`);
url.searchParams.set("items", freshDeck().join(","));
const res = await fetch(url, {
headers: { "x-api-key": process.env.PROVABLE_KEY }
});
const { outcome: deck, serverHash } = await res.json();
return { hand: deck.slice(0, n), deck, serverHash };
}
Why this is fair
- Pre-commit the hash. Before the hand starts, fetch
/api/commitand pin the returnedserverHashto the table — UI footer, hand record, whatever. The server cannot change which seed it will use. - Run the shuffle. The deck order returned is fully determined by
HMAC_SHA256(serverSeed, clientSeed + ":" + cursor + ":" + nonce). - Reveal after the hand. Publish the seed alongside the deck. Anyone can rerun Fisher-Yates locally with the published bytes and confirm the deck matches.
Production tips
- One seed per hand. Use the hand ID (or table ID + hand number) as the
clientSeedso each shuffle is independent and easy to look up. - Multi-deck shoes. Pass the entire shoe (e.g.
6 × 52 = 312cards) as a singleitemslist; it's still one API call. - Custom decks. The endpoint is item-agnostic — Tarot, MTG, Hanafuda, custom indie card games all work the same way.
- Verification permalinks. Every outcome gets a short ID (
/o/<id>) — link it from the hand history so players can audit any past deal.
The full recipe
This page is the keyword-targeted intro. For the deep dive — including a Fisher-Yates implementation that lets you replay the shuffle locally from raw integer draws — see the recipe: Shuffle a Deck of Cards.
Where it fits
- Online poker, blackjack, baccarat — anywhere players want a "show me the seeds" button.
- Digital trading card games where draw RNG can decide a match.
- Solitaire and puzzle games with daily seeds that the whole community plays.
- Tarot, oracle, and divination apps that want to show the deal is real and not curated.