Provably fair random draw API

Pick giveaway winners from a list or a number range with a commit-reveal proof: publish the seed hash first, draw with a public salt, let anyone verify.

GETPOSThttps://apibox.io.vn/v1/random-draw
Developer utilities▶ Try it online

A draw viewers can check, for livestream minigames, comment giveaways and lucky draws. 1) action=commit creates a secret seed (or takes yours) and returns its SHA-256 commitment: publish the commitment before the event and freeze the entry list. 2) action=draw with the seed, the entries (one per line, or [{"name","weight"}]) or a number range min / max, and a public salt chosen at draw time (the number of comments, a lottery result…) returns the winners and reserves. 3) Publish the seed: action=verify lets anyone recompute the draw and check SHA-256(seed) against the commitment.

Winners are drawn with HMAC-SHA256 keyed by the seed over the salt and the hash of the entry list, rejection sampling (no modulo bias) and a partial Fisher-Yates shuffle, so nobody can change the result after the commitment and the organiser cannot steer it once the salt is public. Duplicated entries are removed by default (unique_entries=false keeps them, as extra chances), weights give proportional chances, reserve draws backup winners.

The full algorithm is returned with every draw so it can be reimplemented independently. Prize promotions in Vietnam must still follow the trade promotion rules (registration / notification): this API is only the drawing tool.

Parameters

NameInTypeRequiredDefault
actionpath|body|querystringnocommitcommit, draw or verify (also as path: /v1/random-draw/draw).
seedbody|querystringnoSecret seed (generated by commit; at least 16 characters).
entriesbodystringnoOne entry per line, or an array of names / {"name","weight"}.
minbody|querynumberno1Number range instead of entries (with max).
maxbody|querynumbernoUpper bound of the number range.
saltbody|querystringnoPublic value fixed at draw time (comment count, lottery result…).
winnersbody|querynumberno1Number of winners.
reservebody|querynumberno0Backup winners drawn after them.
commitmentbody|querystringnoverify: the published commitment.
claimed_winnersbodyarraynoverify: the announced winners, in order.

Example

curl -X POST "https://apibox.io.vn/v1/random-draw/draw" \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"seed":"a3f1c9e07b5d2486a3f1c9e07b5d2486","entries":"Nguyễn An\nTrần Bình\nLê Chi\nPhạm Dũng\nHoàng Em","salt":"1532","winners":2,"reserve":1}'

Response

{
  "kind": "list",
  "seed": "a3f1c9e07b5d2486a3f1c9e07b5d2486",
  "commitment": "af24c17ea9f89cc17d2b578b1f965de883c5596b31bb34cdac7077bdc8357696",
  "salt": "1532",
  "entries_count": 5,
  "entries_hash": "4c2ecdb5ece276bb6836ad66df15956d19156d10df68e800ed2f62f6bba74b74",
  "weighted": false,
  "unique_entries": true,
  "winners": [
    {
      "rank": 1,
      "entry": "Hoàng Em",
      "index": 5
    },
    {
      "rank": 2,
      "entry": "Nguyễn An",
      "index": 1
    }
  ],
  "reserves": [
    {
      "rank": 3,
      "entry": "Lê Chi",
      "index": 3
    }
  ],
  "algorithm": "random-draw/v1: entries = trimmed non-empty lines in order (with unique_entries, later case-insensitive duplicates are dropped); entries_hash = SHA-256(entries joined by \"\\n\", each as \"label<TAB>weight\" when weights are used); stream = HMAC-SHA256(key = seed, \"random-draw/v1|\" + salt + \"|\" + entries_hash + \"|\" + counter) as 32-bit big-endian words; index = rejection sampling below n; winners = partial Fisher-Yates (weighted: cumulative integer weights, removed once drawn).",
  "drawn_at": "2026-11-11T03:18:50.167Z",
  "verify": "POST /v1/random-draw/verify with the same seed, salt, entries (or min / max), winners and the commitment"
}

Use cases

  • Pick comment winners on a Facebook or TikTok livestream
  • Run a verifiable lucky draw at a company year-end party
  • Draw raffle ticket numbers from a range
  • Assign random order or groups in a classroom with proof

FAQ

Why is it provably fair?

The seed is fixed by the published commitment before entries close, and the public salt is unknown until draw time, so neither side can choose the result; anyone can recompute it from the revealed seed.

Is the seed stored on the server?

No. The API is stateless: keep the seed secret until the draw, then publish it. Without the seed the commitment reveals nothing.

Can people have more chances?

Yes: send entries as [{"name":"An","weight":3}], or set unique_entries=false so repeated lines count as several tickets.

Related APIs

Start with 1,000 free requests every month. Get free API key →