Skip to main content
Every pull from Buy packs carries a proof: the provably fair record of how it was drawn. You can ask us to replay it with one request that needs no key, or replay it yourself from the published algorithm. It is the same check the Verify button in our app runs. How the draw works: Provably Fair.

The proof

Each entry in a Buy packs response’s pulls has a proof object, or null for a guaranteed pull, which no roll chose:
number
The number the draw started from. For a card, or a consolation pull, it is the first 8 hex digits of pull_id read as an integer. For the Starter or a sealed mystery pack, it is a random seed recorded with the result.
string | null
The result hash: SHA-256, in hex, of the draw’s inputs and its result. For a card, sha256("result:<seed>:<bucket index>:<card index>:<card id>"). null for a consolation pull.
string | null
sha256("seed_commit:<seed>:<timestamp>"), recorded with the pull. Card pulls only.
string | null
When the pull was drawn (ISO 8601). It is part of seed_commit_hash, and of a sealed pack’s result hash.
string | null
The check that replays this pull, on https://catchbackcards.com. null for a pull from the Steph Curry pack, which this check doesn’t cover.
Keep the proof with the pull: Get an order doesn’t return it. The verification_hash you were sent at purchase lets you show later that the seed, bucket, place and card haven’t changed. It doesn’t cover the odds or the card list, and a passing check can be cached for up to a day.

Ask us to check it

Send a GET to verify_path. It needs no key.
Send verify_path as it is. The check takes only transaction_id, card_source and pack_type, and answers any other parameter with a 400. If you no longer have the proof, /api/verify-pull?transaction_id=<pull_id> works too: without the card_source hint, it looks in every catalog. The response replays the draw step by step. Trimmed to the fields this guide uses, a card pull looks like this:
200 (trimmed)
Read verification_type first, then verified: verification_type says what was checked. algorithm names the draw: shuffle-scaled-v3 is the current one, and older pulls name the version they were drawn with. For a consolation pull, the check confirms only that the seed’s draw falls under the recorded consolation odds. It has no result hash. We don’t record whether a consolation roll or an armed chase came before the bucket roll, so the check infers it from the pack’s history. A pull on a pack whose consolation was removed can come back verified: false. Contact us if yours does.

Check it yourself

For a card pull drawn with shuffle-scaled-v3:
1

Derive the seed

Take the first 8 hex digits of pull_id and read them as an integer: fe04349b is 4261688475. It equals proof.seed.
2

Generate the draws

The draws come from a linear congruential generator: state = (0x41C64E6D × state + 0x6073) mod 2^32, starting from the seed, one draw per step. Compute it in IEEE-754 double precision, as JavaScript does. 0x41C64E6D × state can exceed 2^53 and is rounded, every pull was drawn with that rounding, and exact integer arithmetic gives different draws.
3

Take them in order

One draw for a chase card, when one is armed on the pack; one for the consolation roll, when the pack has one; the bucket roll; n − 1 shuffle draws for a bucket of n cards; then the card draw. draws_before_bucket.count says how many came before the bucket roll.
4

Pick the bucket

draw / 2^32 × (sum of the bucket odds) lands in one bucket’s slice of the cumulative odds, taken in the order of step3_bucket_selection.probabilities.
5

Pick the card

The shuffle draws reorder the bucket (for i = n−1 down to 1: j = floor(draw / 2^32 × (i + 1)), swap(i, j)), and the card draw picks place floor(draw / 2^32 × n) in the shuffled list, step4_card_selection.available_cards.
6

Recompute the result hash

sha256("result:<seed>:<bucket index>:<card index>:<card id>") in hex, with the card you received, equals the verification_hash you were sent.
The same steps in Node 18 or later, reading the bucket’s odds and size from the check’s response:
Two things can make the card you received differ from the one at that place, and the response says which in step5_result_verification.delivered_by: chase when the chase draw won the pack’s chase card, and collision_reroll when another pull claimed the selected card a moment earlier, so you got the next available card. The result hash always binds the card you received.

What the check shows

It shows that the seed lands on this bucket and this place in the recorded list, and that the card you received is at that place. The list’s order is only confirmed when step4_card_selection.shuffle.restores_card_id_order is true. Against the verification_hash you were sent, it also shows that the seed, bucket, place and card haven’t changed since. The seed is the pull id, which CatchBack generates when the pull is made.