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

  1. Pre-commit the hash. Before the hand starts, fetch /api/commit and pin the returned serverHash to the table — UI footer, hand record, whatever. The server cannot change which seed it will use.
  2. Run the shuffle. The deck order returned is fully determined by HMAC_SHA256(serverSeed, clientSeed + ":" + cursor + ":" + nonce).
  3. 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

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

Related