How Plinko's provably-fair RNG actually works

After writing about how crash-game multipliers are generated, the most-asked follow-up was about Plinko: "If every bounce looks random, how can you verify a drop?" Good question. Plinko is the most visual provably-fair game because you can literally watch each peg-decision happen — and that makes it the best one for understanding what "provably fair" actually means at a mechanical level. I implemented a Plinko variant inside ZimBet, so here's the honest walkthrough of how a single hash turns into a full path down the board.

The board is simpler than it looks

Forget the bouncing animation for a moment. A Plinko board with 16 rows of pegs is really just a sequence of 16 binary choices: left or right. At row 1 the ball hits a peg and goes left or right. At row 2 it hits the next peg and goes left or right again. Repeat until row 16, and the ball lands in one of 17 possible slots at the bottom. The physical "bounce" you see on screen is purely cosmetic — the outcome was decided before the ball was ever drawn.

That means the entire game reduces to one question: where do those 16 left-or-right decisions come from? In a provably-fair implementation, the answer is a hash.

From seeds to bounces

Just like in a crash game, the inputs are three values:

  • Server seed — a secret random string the casino generates and locks in before you play. You're shown a SHA-256 hash of it as a commitment.
  • Client seed — a value you control (or your browser generates). This ensures the casino can't have pre-computed the result in isolation.
  • Nonce — a counter that increments with each bet, so the same seed pair never produces the same outcome twice.

Those three are combined — typically as HMAC-SHA256(server_seed, client_seed:nonce) — producing a 64-character hex string. That hex string is the raw material for every bounce.

Extracting directions from a hash

A hex character represents 4 bits, and each bit is a 0 or a 1 — a natural binary choice. The simplest mapping: take the first 16 bits of the HMAC output, and for each bit, if it's 0 the ball goes left, if it's 1 the ball goes right (or the reverse — the convention doesn't matter as long as it's consistent and published).

Some implementations use a slightly different approach — they divide the hash into groups of characters, convert each group to a float between 0 and 1, and decide left or right based on whether the float is above or below 0.5. The effect is identical: each peg produces a deterministic, uniformly distributed binary outcome from a slice of the hash. The key property is that every bit of the HMAC output is uniformly random if the server seed has enough entropy, so the left/right split at each peg is exactly 50/50.

server seed 🔒 client seed nonce HMAC-SHA256 deterministic a3f91011…c1e7 64-char hex → extract bits bits bit 0 = 1 → R bit 1 = 0 → L bit 2 = 1 → R bit 3 = 1 → R 0.5× 0.7× 1.5× 0.5× landing slot = sum of right-bounces
Each bit of the HMAC output decides one peg. The ball's entire path — and its final slot — is fixed before the drop animation starts.

Why the path follows a bell curve

If each peg is a fair 50/50, the number of "right" bounces across 16 rows follows a binomial distribution — the same statistics as flipping a coin 16 times and counting heads. Most of the time you'll get something close to 8 rights and 8 lefts, landing the ball near the middle. Hitting the far-left or far-right slot (0 rights or 16 rights) happens with probability 1/216 ≈ 0.0015% — roughly once every 65,536 drops.

This is why the payout table is shaped the way it is. The centre slots pay low (the ball lands there constantly), and the edge slots pay high (they're genuinely rare). The shape isn't a design choice — it's a mathematical consequence of 16 independent binary events.

What "risk level" actually changes

Most Plinko games let you choose a risk level — low, medium or high. People sometimes assume this changes how the ball bounces. It doesn't. The RNG path is always generated the same way. What changes is the payout table mapped to each landing slot:

  • Low risk compresses the multipliers — the centre slots pay close to 1× and the edges aren't much higher. Less variance, slower losses.
  • High risk pushes value to the extremes — the centre might pay 0.2× while the outermost slot pays 1000×. Much more volatile, same expected value.

In both cases the house edge is baked into the table. The sum of (probability × payout) across all slots is always slightly less than 1.0. The risk toggle is a volatility dial, not an odds dial.

How to verify a Plinko drop

Verification follows the same commit-and-reveal pattern as any provably-fair game. Here's the short version:

  1. Before you play: the game shows you a SHA-256 hash of the server seed. Copy it. This is the casino's commitment — proof the seed exists and is locked.
  2. Play some rounds. Each round uses the same server seed + client seed, with the nonce incrementing by one. Record the landing slot for each round.
  3. Rotate the server seed. The game reveals the raw server seed and generates a new one for future bets.
  4. Verify the commitment. Hash the revealed server seed with SHA-256. It must match the hash you saved — proving the casino didn't swap it.
  5. Replay the round. Compute HMAC-SHA256(server_seed, client_seed:nonce), extract the per-row bits, walk the ball through the pegs, and confirm you land in the same slot the game showed. If you do, the round was honest.

That last step is the real test. You're re-running the exact same deterministic function the server ran, with the same inputs, and checking that the output matches. If it does, the casino couldn't have manipulated the result — because any different server seed would have failed the commitment check in step 4.

A quick Python check

If you want to try this yourself, the core logic is a few lines. Given a revealed server seed, your client seed and the round's nonce:

import hmac, hashlib

server_seed = "your_revealed_server_seed"
client_seed = "your_client_seed"
nonce = 1

message = f"{client_seed}:{nonce}"
h = hmac.new(
    server_seed.encode(),
    message.encode(),
    hashlib.sha256
).hexdigest()

# Extract 16 binary decisions from first 4 hex chars (= 16 bits)
bits = bin(int(h[:4], 16))[2:].zfill(16)
path = ["R" if b == "1" else "L" for b in bits]
slot = sum(1 for b in bits if b == "1")

print(f"Hash:  {h}")
print(f"Path:  {' → '.join(path)}")
print(f"Slot:  {slot} (of 0–16)")

The slot number equals the count of right-bounces. Compare it to what the game displayed — if it matches, the round checks out. Different platforms may use slightly different mappings (some pull floats from larger chunks of the hash), but the principle is always the same: deterministic, reproducible, auditable.

What this doesn't protect you from

Provably fair guarantees one specific thing: the game committed to an outcome before your bet and didn't change it. It does not guarantee that the house edge is small, the payout table is generous, or that you'll win over time. The edge is a separate, published number — and it's always in the casino's favour. A perfectly honest Plinko board with a 10% house edge will still take 10% of your money on average. Provably fair means fair execution, not fair odds.

Why I think Plinko teaches the concept best

When I was building provably-fair mechanics for ZimBet, Plinko was the game that made the hash-to-outcome mapping most intuitive. In a crash game, the hash becomes a single number (the crash multiplier), which feels abstract. In Plinko, each bit of the hash becomes a visible, physical turn — left, right, left, right — that you can literally draw on paper and trace. If someone asks me "what does provably fair mean?", I point them at a Plinko board before anything else. Watch the ball, then replay the hash. When the path matches, you've just understood a cryptographic commitment scheme without needing to know the word "cryptographic".

Related
→ Can you predict a provably-fair crash game? How Aviator works → How provably-fair casino games work → ZimBet project overview
How does provably-fair Plinko decide which way the ball bounces?

Each bounce is a binary left-or-right decision pulled from the HMAC-SHA256 hash of the server seed, client seed and nonce. A 16-row board needs 16 bits — each bit maps to one peg. The entire path is fixed before the animation starts.

Can you verify a Plinko drop yourself?

Yes. After a round, the casino reveals the raw server seed. Hash it with SHA-256 and confirm it matches the pre-round commitment. Then compute HMAC-SHA256(server_seed, client_seed:nonce), extract the per-row bits, and trace the path. If you land in the same slot the game showed, the round was honest.

Does the risk level affect the randomness?

No. The risk level only changes the payout multipliers assigned to each landing slot. The random ball path is always generated the same way. Low risk compresses payouts toward the middle; high risk pushes more value to the edges. The RNG is identical across risk settings.

Is there a strategy to win at Plinko?

No strategy changes the expected value. Each peg is an independent 50/50 event, producing a binomial distribution that clusters near the centre. The house edge is baked into the payout table. No pattern in bet size, risk level or timing can overcome it.