When to use commit-reveal

A regular /api/ints draw is verifiable after the fact, but the server picked the seed in the same moment you sent the request — a sufficiently motivated server operator could in principle pick a seed favorable to them. Commit-reveal eliminates that worry by splitting the draw into two HTTP calls separated by time:

  1. Commit. The server picks a fresh serverSeed, publishes its serverHash, and locks the seed away. You publish that hash somewhere durable (a pinned post, a tweet, a blockchain transaction) before entries close.
  2. Reveal. Later, when it's time to run the draw, you submit your clientSeed and parameters. The server runs the generator against the committed seed and returns the outcome plus the seed itself, so anyone can check the math.

Because the hash was published before anyone knew the clientSeed or the entry list, the server couldn't have picked a seed designed to produce a particular winner.

Step 1 — Commit

curl -X POST https://api.provable.io/api/commit

Response:

{
    "commitId": "8c3d7b9e-1f2a-4c5d-9e8f-7a6b5c4d3e2f",
    "serverHash": "9f86d081884c7d65...",
    "expiresAt": 1716567000000
}

Save the commitId on your side (you'll need it to reveal) and publish the serverHash and expiresAt publicly. Most teams pin a post that reads "Tonight's draw will use serverHash 9f86d0… revealed at 22:00 UTC. commitId on file."

Step 2 — Reveal at draw time

Submit the commitId together with the clientSeed, the generator endpoint, and its params. The reveal call is single-use — a second call against the same commitId returns 409 Conflict.

curl -X POST https://api.provable.io/api/reveal \
    -H "x-api-key: $PROVABLE_API_KEY" \
    -H "Content-Type: application/json" \
    -d '{
        "commitId": "8c3d7b9e-1f2a-4c5d-9e8f-7a6b5c4d3e2f",
        "clientSeed": "raffle-2026-05-24",
        "endpoint": "ints",
        "params": { "count": 1, "min": 1, "max": 5328 }
    }'

Response (truncated):

{
    "outcome": [3142],
    "clientSeed": "raffle-2026-05-24",
    "serverHash": "9f86d081884c7d65...",
    "serverSeed": "b1946ac92492d2347c6235b4d2611184...",
    "commitId": "8c3d7b9e-1f2a-4c5d-9e8f-7a6b5c4d3e2f",
    "endpoint": "ints",
    "min": 1, "max": 5328, "count": 1,
    "cursor": 0, "nonce": 0,
    "created": 1716566400000
}

Publish the response — the clientSeed, the serverSeed, the params, and the resulting outcome. Together with the previously committed serverHash that's everything a third party needs to audit you.

Step 3 — How participants verify the reveal

Anyone — your community, an auditor, a journalist — can run two checks independently:

  1. Hash check. Compute sha256(serverSeed). It must equal the serverHash you published at commit time. If it doesn't, the seed isn't the one you originally committed to.
  2. Derivation check. Plug the four published inputs into the open-source generator and confirm you get the same outcome array.
import { generateInts, sha256Hex } from "@provableio/provable-core";

const PUBLISHED_HASH = "9f86d081884c7d65...";
const serverSeed = "b1946ac92492d2347c6235b4d2611184...";
const clientSeed = "raffle-2026-05-24";

if (sha256Hex(serverSeed) !== PUBLISHED_HASH) throw new Error("hash mismatch");
const recomputed = generateInts({ serverSeed, clientSeed, cursor: 0, nonce: 0, count: 1, min: 1, max: 5328 });
console.assert(recomputed[0] === 3142);

What can go wrong

The TTL expires before you reveal

Commits are held for COMMIT_TTL_MS milliseconds — 10 minutes by default. After the TTL the commit is swept and a reveal call returns 410 Gone. Two practical workarounds:

Your operations runbook should also have a fallback: if a reveal fails (network outage, expired commit), don't quietly re-commit and pretend nothing happened. Publish a postmortem with the old commitId noted as void.

The reveal is delayed indefinitely (selective abort)

A bad-faith operator could refuse to reveal if they don't like the would-be winner. Commit-reveal can't prevent that — but it makes it visible: every observer can see that you published a hash and then never honored it. Publish your commit policy ("we will always reveal — if we ever skip a draw, we'll burn the seed publicly and re-commit") so the social cost is clear.

Reveal fails after the network already saw the response

Reveal is single-use. If your client times out after the server already wrote the outcome, your retry will get a 409. Send an Idempotency-Key on the reveal call so retries replay the original response instead of erroring out — see Using Idempotency-Key.

Participants worry the published hash isn't the one you used

Push the hash somewhere immutable. Tweeting it is the bare minimum; pinning it in a Discord channel with an edit-history-visible client is better; writing it to a blockchain transaction is overkill but unambiguous. Whatever you do, do it before entries close.

Next steps