What you need

An outcome is fully described by five fields: clientSeed, cursor, nonce, serverHash, and the outcome values themselves. For a commit-reveal flow you also have the revealed serverSeed. With those you can independently answer:

  1. Did the server publish this serverHash for this clientSeed?
  2. Does the serverSeed hash to that serverHash, and does running the generator against it reproduce the same numbers?
  3. Did this outcome land in the day's published transparency log?

Each step is independent — you can skip (3) for low-stakes draws, or skip (2) until the seed is revealed.

Step 1 — Did the server publish this hash?

This is the cheapest check and the one most users will run. The endpoint accepts the clientSeed and serverHash straight from the outcome:

curl "https://api.provable.io/api/verifyServerHash?clientSeed=my-app&serverHash=9f86d081884c7d65..."

You'll get back either the literal JSON true (the fast path — the live seed state for this clientSeed still carries that hash) or a structured VerifyResult with a verified boolean plus a list of matching outcomes (used when the seed has rolled forward but historical or commit-reveal outcomes were generated against that hash). Both shapes are documented under /api/verifyServerHash.

If verified is false, stop here — the result was either fabricated or attributes the wrong client seed.

Step 2 — Re-derive the result locally

The hash check only proves the server could have generated something against this seed pair. To prove it generated these specific numbers, you need the revealed serverSeed — either from a commit-reveal draw or after the live seed has rotated and the historical seed has been published.

The open-source @provableio/provable-core package implements exactly the same derivation our server runs. Install it and re-run the math:

npm install @provableio/provable-core
import { generateInts, sha256Hex } from "@provableio/provable-core";

const serverSeed = "b1946ac92492d2347c6235b4d2611184...";
const clientSeed = "my-app-user-42";
const cursor = 0;
const nonce = 0;

// 1. The published serverHash must equal sha256(serverSeed).
console.assert(sha256Hex(serverSeed) === "9f86d081884c7d65...");

// 2. Re-running the generator with the same inputs must produce the same outcome.
const recomputed = generateInts({ serverSeed, clientSeed, cursor, nonce, count: 5, min: 1, max: 100 });
console.log(recomputed); // [42, 87, 13, 65, 29]

If either assertion fails, the outcome was tampered with. There is no way to forge a serverSeed whose SHA-256 matches an arbitrary serverHash.

Step 3 — Check the inclusion proof

Daily transparency roots commit to every outcome generated that UTC day. To prove your outcome was in the set the server committed to — and that the server can't have quietly back-dated a different one in its place — fetch the inclusion proof:

curl "https://api.provable.io/api/merkle/2026-05-24/proof/my-app-user-42:0:0"

The response gives you the leaf's canonical bytes (outcomeId|serverHash|clientSeed|timestamp), the leaf hash (SHA256(0x00 || canonical)), the sibling chain, and the published root for that day. Verifying is a small loop — hash the leaf, then for each sibling either SHA256(0x01 || sibling || acc) if the sibling is on the right or SHA256(0x01 || acc || sibling) if it's on the left, until you've collapsed to a single hash. That hash must equal the published root.

import crypto from "node:crypto";
const sha = (buf) => crypto.createHash("sha256").update(buf).digest();
const proof = await fetch(
    "https://api.provable.io/api/merkle/2026-05-24/proof/my-app-user-42:0:0"
).then((r) => r.json());

let acc = sha(Buffer.concat([Buffer.from([0x00]), Buffer.from(proof.leaf.canonical, "utf8")]));
for (const sib of proof.siblings) {
    const sibBuf = Buffer.from(sib.hash, "hex");
    acc = sib.position === "left"
        ? sha(Buffer.concat([Buffer.from([0x01]), sibBuf, acc]))
        : sha(Buffer.concat([Buffer.from([0x01]), acc, sibBuf]));
}
console.assert(acc.toString("hex") === proof.root);

That last assertion is the strongest check available — it ties the outcome to a root that was published, in writing, on a fixed UTC day.

Common failure modes

Next steps