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.
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
| Name | In | Type | Required | Default | |
|---|---|---|---|---|---|
action | path|body|query | string | no | commit | commit, draw or verify (also as path: /v1/random-draw/draw). |
seed | body|query | string | no | Secret seed (generated by commit; at least 16 characters). | |
entries | body | string | no | One entry per line, or an array of names / {"name","weight"}. | |
min | body|query | number | no | 1 | Number range instead of entries (with max). |
max | body|query | number | no | Upper bound of the number range. | |
salt | body|query | string | no | Public value fixed at draw time (comment count, lottery result…). | |
winners | body|query | number | no | 1 | Number of winners. |
reserve | body|query | number | no | 0 | Backup winners drawn after them. |
commitment | body|query | string | no | verify: the published commitment. | |
claimed_winners | body | array | no | verify: 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}'const res = await fetch('https://apibox.io.vn/v1/random-draw/draw', {
method: 'POST',
headers: {
Authorization: 'Bearer YOUR_API_KEY',
'Content-Type': 'application/json',
},
body: JSON.stringify({
"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
}),
});
const data = await res.json();
console.log(data);import requests
res = requests.post(
"https://apibox.io.vn/v1/random-draw/draw",
headers={"Authorization": "Bearer YOUR_API_KEY"},
json={"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},
timeout=30,
)
print(res.json())<?php
$ch = curl_init('https://apibox.io.vn/v1/random-draw/draw');
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CUSTOMREQUEST => 'POST',
CURLOPT_HTTPHEADER => ['Authorization: Bearer YOUR_API_KEY', 'Content-Type: application/json'],
CURLOPT_POSTFIELDS => '{"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 = curl_exec($ch);
print_r(json_decode($response, true));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
UUID Generator
Generate up to 1,000 UUID v4, time-ordered UUID v7, ULID, Nano ID or random hex IDs per request. Secure randomness, optional uppercase.
⏱Cron Expression Parser
Validate a cron expression and get its next run times in any timezone, with a plain-English description. Supports 5, 6 and 7-field cron syntax.
🕒Time & Timezone Converter
Get the current time in any timezone, convert between timezones and Unix timestamps (seconds or ms), with UTC offset and weekday for up to 30 zones.