How do you prove a giveaway winner was picked fairly?
"How do we know it wasn't rigged?" is the first comment under every giveaway post, and OpoShop merchants running their first prize draw hit it immediately. Most brands answer with a shrug and a claim. You can do better than a claim, and the technique is old, simple, and completely free to implement.
Why "we picked at random, trust us" is not enough
The problem is not that people think you are dishonest. It is structural, and it applies to every store on OpoShop equally. The problem is that from the outside, an honest draw and a rigged draw look identical.
If you announce a winner and nobody can check anything, the only evidence anyone has is your word. That is fine when the prize is small and your audience already knows you. It becomes a real problem when the prize is worth a few hundred dollars, when the winner happens to have an unusual name, or when someone in the comments decides to be suspicious. You have no way to answer them.
There is also a version of this that catches out honest merchants. If you draw a winner, do not like the result for some reason, and draw again, you have rigged it even if you never intended to. A system that lets you re-roll silently is a system that will eventually be used to re-roll.
- A screenshot of a spreadsheet proves nothing. Anyone can sort a spreadsheet after the fact.
- A random number generator on a website proves nothing. Nobody saw the inputs.
- A video of you spinning a wheel proves very little. The list on the wheel is unverifiable.
- A published seed and entrant list proves a great deal, because the result can be recomputed.
The commit-then-reveal method, in plain English
Here is the whole idea. Before you know who is going to enter, you lock in the randomness. Then you show everyone a fingerprint of it. Later, you reveal the actual value and everyone checks it against the fingerprint.
A cryptographic hash is the tool that makes this work, and it is the same primitive that secures most of the software your OpoShop store already runs on. A hash function takes any input and produces a fixed-length string. It is one-way, so publishing the hash gives away nothing about the input. It is also collision-resistant in practice, so you cannot find a different input that produces the same hash. That combination is exactly what you need.
So: generate a long random number, hash it, publish the hash on day one. At the end, publish the number. Anyone can hash the number themselves and confirm it matches what you posted weeks earlier. Since the hash was public before entries opened, you could not have chosen the number after seeing who entered.
For OpoShop merchants this is invisible plumbing. The app does it. What you get is a page you can link to when someone asks the awkward question.
Pinning the entrant list so it cannot move
The seed is only half of it, and this second half is the part most OpoShop giveaway tools skip. You also have to pin down who was in the draw, because a seed is useless if the list of entrants can change afterwards.
The technique is the same. When entries close, take the full list of entrants in a fixed order (sorted by entry ID, which never changes), write down each entrant's ID and how many entries they hold, and hash that whole list. That hash goes into the published record.
Now the entrant list is pinned too. If anyone adds an entrant, removes one, or changes a single person's entry count, the hash changes and the record stops verifying. Nobody has to trust that the list was accurate. They can recompute the hash and see.
Notice what this list does not contain: email addresses. It only needs an identifier and a weight. Publishing a list of your entrants' email addresses to prove fairness would be a privacy disaster that solves one problem by creating a worse one. Masked addresses in the human-readable view, IDs and weights in the hashed record.
How the winner is actually calculated
Once you have a sealed seed and a pinned entrant list, picking the winner is arithmetic. Nothing about it depends on which OpoShop store is running the giveaway. The point is that it is arithmetic anyone can repeat.
A workable method: combine the seed and the entrant-list hash using HMAC-SHA256, treat the result as a very large number, and take it modulo the total number of entries. That gives you a ticket number somewhere in the range of all entries. Walk the entrant list adding up each person's entries until you pass the ticket number. That person wins.
Because entries are weighted, someone with nine entries occupies nine ticket numbers and someone with one entry occupies one. Odds are exactly proportional to entries earned, which is what you promised.
For multiple winners, remove the person who just won and repeat with a different message ("winner 2" instead of "winner 1"). Same seed, same list, different position, different result. Still completely reproducible.
1. Freeze the entrants
The moment you draw, take a snapshot of every active entrant sorted by ID. Nothing that happens afterwards can change the snapshot.
2. Hash the snapshot
SHA-256 over one line per entrant containing position, ID, and entry count. Store the hash in the draw record.
3. Derive the ticket
HMAC-SHA256 with the seed as the key and the snapshot hash plus the winner position as the message, interpreted as a number, modulo the total entries remaining.
4. Publish everything
Seed, snapshot hash, the derived ticket numbers, and the winners. Anyone with those four things can check your work in a few lines of code.
Making a re-draw impossible rather than discouraged
Here is a detail that separates a system that is fair from a system that is merely fair-looking: a giveaway should be drawable exactly once, and that should be enforced by the database rather than by a setting or a checkbox.
The difference matters under pressure. If "already drawn" is a flag your code checks, then a bug, a race between two clicks, or a well-meaning support fix can produce a second draw. If it is a unique index that physically cannot hold two initial draws for the same giveaway, then a second attempt fails at the storage layer no matter what the application code does.
What about a winner who never claims the prize? That is a real situation and it needs an answer that is not "quietly draw again". The right approach is a replacement draw recorded as a new, separate entry in the history that names the original draw it supersedes and lists exactly who was excluded and why. The original record is never edited or deleted. Anyone reading the history sees the full sequence.
| Approach | Can the merchant re-roll? | Can anyone verify? | What a sceptic sees |
|---|---|---|---|
| Spreadsheet and a screenshot | Yes, invisibly | No | A picture that proves nothing |
| Third-party random picker | Yes, by running it again | Not really | An unverifiable claim |
| Committed seed with a published record | No, the fingerprint was public first | Yes, independently | Numbers they can recheck themselves |
What this method does not prove
Being honest about the limits is part of what makes the rest credible.
The commitment proves ordering. It proves the seed existed and was fixed before anybody entered, so it could not have been chosen to produce a particular winner. That is the attack you actually care about, because it is the one a merchant could perform.
It does not prove that the server generated the seed honestly in the first place. There is no third-party randomness beacon involved. A sufficiently determined and technical operator who controlled the code could, in principle, generate a seed non-randomly. Nothing about the published record would reveal that.
You should say this out loud rather than let someone discover it. In practice it is a distinction that matters to cryptographers and almost nobody else, and claiming more than you can prove is exactly the behaviour that makes people suspicious in the first place. The published record still removes every practical way to rig a result after seeing the entrants, which is the thing your audience is actually worried about.
Best answer: Publish a hash of your random seed before entries open, publish the seed and the pinned entrant list after the draw, and use a calculation anyone can repeat. That turns "trust us" into "check it yourself", and it takes zero extra effort from you once the app is doing it.
Turning the proof into marketing
Most merchants treat fairness as a compliance box. On OpoShop it converts better as a feature. It converts better as a feature.
Put the verification link in the announcement post and in your OpoShop store's giveaway page. When someone asks how they know it was fair, reply with the link instead of an explanation. That single exchange does more for your credibility than any amount of copy about how much you value your community.
It also protects the next giveaway. People who watched you prove the last one enter the next one without hesitation, and the entrants who did not win are much more likely to stay subscribed when the loss feels legitimate rather than arbitrary. Stores on OpoShop that publish their draw record tend to find the second giveaway outperforms the first, largely because the audience has stopped wondering.
FAQs
Do I need to understand cryptography to run a verifiable draw?
No. The app generates the seed, publishes the fingerprint, runs the draw and produces the record. Your only job is to link to the results page when someone asks.
Can I still pick the winner myself if I want to?
You can run a giveaway that way, but you cannot then call it random or verifiable. If you want to choose a winner on merit, run a contest with published judging criteria instead and describe it accurately.
What if someone claims the draw was rigged anyway?
Send them the results page. It contains the seed, the fingerprint published before entries opened, the entrant snapshot hash, and the method. Anyone can recompute the winner from those four things.
Should I publish the entrants' email addresses?
Never. The verification only needs entry identifiers and entry counts. Show masked addresses to humans and keep full addresses in your own records where they belong.
What happens if the winner does not respond?
Draw a replacement, recorded as a new entry in the draw history that names who forfeited. Never edit the original record. The history should read as a sequence of events, not a final state.
Does this work for multiple winners?
Yes. Each winner position uses the same seed and the same entrant snapshot with a different position number, and each winner is removed before the next is drawn, so you always get distinct people.
Publish the fingerprint on day one, publish the seed at the end, and the awkward question answers itself.