Waitlist Fraud: Move Queue Logic to the Server or Lose the Queue
Attaching a reward to a countable action creates a game. The three attacks are predictable, and one architectural decision defeats most of them.
A waitlist with a referral reward is structurally a game with a prize. That is not a cynical framing — it is the mechanical consequence of attaching value to a countable action. As soon as the prize is worth anything, some fraction of participants will try to win it cheaply, and the fraction is not small.
Founders usually respond by adding checks: a disposable-domain blocklist here, a captcha there. Those help at the margin and none of them address the actual vulnerability.
The vulnerability is location. Any queue logic that runs in the visitor's browser is both wrong and editable, so the defence is to move all of it server-side. Do that and the three standard attacks reduce to a rate-limiting problem, which is a solved one.
Three attacks account for essentially all of it
They are worth naming precisely, because each has a different fix and founders tend to defend against only the first.
Self-referral farming. The most common by a wide margin. A subscriber opens their own referral link in a private window, signs up with a disposable address, closes it, repeats. Twenty minutes of tabbing puts them at the top of your queue. No technical skill required, which is why it is the one that actually happens.
List flooding. A competitor, or someone bored, scripts thousands of signups against your page. Your subscriber count looks excellent and your list is worthless. If you pay per contact anywhere downstream, it is also directly expensive.
Position squatting. Someone opens the network tab, notices that position is submitted by the client, and asks to be number one. This one is purely an implementation failure and it is more common in the wild than it should be.
Note that the first and third are only possible because a decision that belongs to the server was delegated to the client.
Nothing about the queue may be computed in the browser
This is the whole defence, and everything else is refinement.
Position, duplicate detection, referral attribution and plan limits all require the full subscriber set for a page. A browser has exactly one row — its own submission. Any logic placed there is therefore working from a sample of one, which makes it wrong before it is even insecure.
The security consequence follows for free: code that runs on the visitor's machine is code the visitor can edit. A position computed client-side is a position the client chooses. A duplicate check performed client-side checks only that person's own history.
Concretely, the signup handler should accept an email, a page identifier and an optional referral code, and nothing else. Everything the queue depends on is derived server-side from data the client never sees. If your request body contains a position, a referral count, or a status, the queue is already compromised.
Once that holds, flooding reduces to rate limiting. Five signups per IP per minute and thirty per hour is a comfortable envelope: invisible to a shared office network, tedious enough that farming stops being worth the time.
Make self-referral structurally impossible rather than merely checked
The tempting fix is a comparison — reject if the referrer's identity matches the new subscriber's. That works until someone refactors the handler and drops the check, which is the failure mode of every rule that depends on being remembered.
The structural version falls out of ordering. Resolve the referrer before creating the new subscriber. The referrer must already exist in the list; the new row does not yet. There is no sequence of operations in which a row refers itself, so no check is needed and no check can be forgotten.
Scope referral codes to the page they belong to, for the same class of reason. An unscoped lookup means a code from any list on the platform can be redeemed against yours, and your queue ends up denominated in someone else's traffic.
Enforce uniqueness at the database level rather than in application code. A unique constraint on the page and email pair rejects duplicates even under concurrent requests, where an application-level check has a race window.
Keep the queue reversible, or fraud becomes permanent
This is the part that is usually got wrong, and it is the one that cannot be fixed after the fact.
If a referral decrements the referrer's stored position directly, the original join order is destroyed. You can then never undo a fraudulent referral, revise the boost factor, break ties fairly, or reconstruct what the queue should have been. The information is simply gone.
Store join order as its own immutable value and derive the effective position from it — join order minus referrals times the boost, floored at one. A bad referral is now a subtraction you can reverse, and the queue can be rebuilt exactly at any point in its history.
This also makes the boost a tunable rather than a permanent commitment, which matters more than it sounds: most founders get the number wrong the first time.
Monitor two signals, and resist auto-banning
Two patterns catch most of what survives the architecture. A cluster of signups from one address inside a short window, and a referrer whose count grows in a step rather than a curve.
Neither should trigger an automatic ban. On a waitlist, a false positive costs you a real subscriber and generates a support conversation that starts with an accusation. The asymmetry favours review over enforcement — flag, look, act manually.
Start on infrastructure that already does this. Every Qubed page resolves position, duplicates and referral attribution server-side, rate-limits at five per minute and thirty per hour per address, scopes referral codes to your page, and stores join order separately so your queue stays reversible. On every plan, including free — because a queue you cannot trust is not worth building.
Architecture is the only defence that does not have to be remembered.