One of the ideas behind CrowdRelay was simple: a verified referral gives you an extra lottery ticket. Someone who brought twenty real people should have a better chance than someone who brought one. Fair enough.
Then the awkward question arrived
Then I asked the less comfortable question: what exactly happens if a new referral is verified while the draw is running?
Reading the current counters and picking a weighted winner looks fine until two workers start the same draw, a late verification changes somebody's weight halfway through or the organiser needs to explain the result a week later. "The database said so at the time" is not a particularly satisfying audit trail.
Freeze the input first
I ended up freezing the input first. Every draw stores an immutable snapshot of eligible participants, their ticket counts and the rules used to calculate them. The winner selection runs against that snapshot, not against live counters. A referral verified five seconds later can join the next draw, but it cannot reach backwards and change the one already in progress.
That also made the system much easier to reason about. I can tell who was eligible, how many tickets each person had and which version of the rule was used. The random part remains random; the input no longer is.
One ledger, several kinds of reward
The same reward ledger handles everything else. Three verified referrals may issue a discount. A weighted draw may select several winners without replacement. An operator may grant a manual reward after fixing a mistake. A physical prize may wait for fulfilment, while an admission pass can later be claimed at the door. Different rules decide what should happen, but every reward receives a stable identity and a visible lifecycle.
Retries are part of the design
Retries needed the same treatment. A browser can time out after the transaction commits. A worker can restart at exactly the wrong moment. Instead of pretending that retries can be eliminated, I give durable operations stable idempotency keys. The first execution records the result; the next one returns it. No extra ticket, no second coupon, no accidental second winner.
A lottery mechanic is easy to demo. A fair draw that can survive retries, concurrent workers and an organiser asking difficult questions is a little less glamorous — and much more interesting.