All rewards feature "Very Positive" user ratings and values ranging from $5 to $40. (And it's repeatable!)
Transparency Report
We believe the community deserves to know exactly how SteamDrops works under the hood — from how winners are picked to how we detect and remove bad actors.
Winner Selection Algorithm
Every giveaway uses the same deterministic, server-side random selection process. There is no manual override, no weighting by points balance, and no preferential treatment — every entry has an equal chance.
random_int() — a CSPRNG (cryptographically secure pseudo-random number generator) — is used to select a winner index. This is the same source of randomness used in cryptography.Probability & Odds
Your odds are simple and transparent. Every entry carries exactly equal weight — there is no way to buy extra tickets or boost your probability beyond entering once.
Win Probability = 1 ÷ Total Entries
Live Example — 100 entries
All 100 bars are identical — no entry has any advantage over another.
Odds at different pool sizes
| Total entries | Your chance | 1 in X | Multiple copies (5) |
|---|---|---|---|
| 5 (minimum) | 20.00% | 1 in 5 | 100% (all 5 won) |
| 10 | 10.00% | 1 in 10 | 50.00% |
| 25 | 4.00% | 1 in 25 | 20.00% |
| 50 | 2.00% | 1 in 50 | 10.00% |
| 100 | 1.00% | 1 in 100 | 5.00% |
| 500 | 0.20% | 1 in 500 | 1.00% |
Winner Selection — Source Code
The exact logic that picks every winner on this platform. This is the production code, unmodified.
/**
* Selects winner(s) for all giveaways that have ended and are still pending.
* Called by the cron scheduler every minute.
*/
public function pickWinners(): void
{
// Fetch all giveaways that have ended but have no winner yet
$giveaways = Giveaway::getEndedPending();
foreach ($giveaways as $giveaway)
{
$this->processGiveaway($giveaway);
}
}
private function processGiveaway(array $giveaway): void
{
$id = (int)$giveaway['id'];
$copies = (int)$giveaway['copies'];
// Load all eligible entries — excludes currently banned users
$entries = Entry::getEligibleForGiveaway($id);
// Minimum entry guard — cancel and refund if under threshold
if (count($entries) < self::MIN_ENTRIES)
{
$this->cancelAndRefund($giveaway);
return;
}
$db = Database::getInstance();
$db->beginTransaction();
try
{
$winners = [];
$pool = $entries; // mutable pool — winners are removed after each draw
for ($i = 0; $i < $copies; $i++)
{
if (empty($pool)) break;
// CSPRNG — cryptographically secure random integer
// random_int() uses /dev/urandom on Linux (same source as OpenSSL)
$index = random_int(0, count($pool) - 1);
$winner = $pool[$index];
// Remove winner from pool to prevent duplicate wins
array_splice($pool, $index, 1);
// Record the win — links encrypted key to winner's Steam ID
GiveawayKey::assignWinner($winner['user_id'], $id);
$winners[] = $winner['user_id'];
}
Giveaway::markCompleted($id);
$db->commit();
// Dispatch notifications outside the transaction
foreach ($winners as $winnerId)
{
Notification::create($winnerId, 'win', [
'giveaway_id' => $id,
'title' => $giveaway['game_name'],
]);
}
}
catch (\Throwable $e)
{
$db->rollback();
throw $e;
}
}
/**
* Returns all entries for a giveaway, excluding banned users.
* The result set is the exact pool fed into random_int().
*/
public static function getEligibleForGiveaway(int $giveawayId): array
{
return self::getDb()->fetchAll(
"SELECT e.user_id, e.created_at
FROM entries e
JOIN users u ON u.steam_id = e.user_id
WHERE e.giveaway_id = ?
AND (u.ban_expires_at IS NULL OR u.ban_expires_at < UNIX_TIMESTAMP())
AND u.is_deleted = 0
ORDER BY e.created_at ASC",
[$giveawayId]
);
}
random_int() and not array_rand()?PHP's
array_rand() uses the Mersenne Twister PRNG which is not cryptographically secure — its output can be predicted from previous outputs with enough samples. random_int() draws directly from the OS entropy pool (/dev/urandom) and is the same source used by TLS and password hashing. We use it here so that no statistical analysis can predict or influence winner selection.
Bot & Alt Account Detection
We operate a continuous, multi-layered detection system that runs in the background on every account. Suspicious patterns are surfaced to staff for manual review — no account is ever banned by automation alone.
Referral Abuse Detection
The referral system rewards users who genuinely bring new members to the community. We actively monitor for coordinated attempts to exploit it.
What constitutes referral abuse
- Creating or controlling multiple accounts to claim referral rewards fraudulently.
- Coordinating with others to manipulate giveaway entry pools as a group.
- Using automation or scripts to generate fake account activity.
- Any attempt to inflate one account's winning chances at the expense of legitimate users.
Ban Process
No account is banned automatically. Every ban is issued by a staff member after manual review of the evidence. The process is designed to be auditable and reversible.
Data We Collect
We collect only what is necessary to run the platform. Nothing is sold, nothing is shared with third parties, and no advertising trackers are loaded.
| Data | Source | Stored? | Purpose |
|---|---|---|---|
| Steam ID (64-bit) | Steam OpenID | Yes | Account identity |
| Username & Avatar | Steam API | Yes | Display on site |
| Library Game IDs | Steam API (on registration) | Yes | Eligibility check and Filtering owned games on giveaways |
| Email address | — | No | We never ask for it |
| Password | — | No | Login is Steam OpenID only |
| IP address | — | No | Server logs route to /dev/null |
| Session token | Server-generated | Yes | Keep you logged in |
| Referral origin | Referral cookie | Yes | Referral credit — stored as Steam ID |
auth) and one optional CSRF token (XSRF-TOKEN). No analytics cookies, no advertising cookies, no third-party trackers.
Steam Key Encryption
Every Steam key stored in our database is encrypted. Even in the event of a full database compromise, raw keys cannot be extracted without the encryption key — which is never stored in the database.
class Encryption
{
private const CIPHER = 'AES-256-CBC';
/**
* Encrypt a plaintext string.
* A unique random IV is generated per encryption — the same key
* will never produce the same ciphertext twice for the same input.
*/
public static function encrypt(string $plaintext): string
{
$key = base64_decode(Config::get('APP_ENCRYPTION_KEY'));
$iv = random_bytes(openssl_cipher_iv_length(self::CIPHER)); // CSPRNG IV
$ciphertext = openssl_encrypt($plaintext, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
// IV is prepended to ciphertext and base64-encoded for storage
// Without the APP_ENCRYPTION_KEY env var, the IV is useless
return base64_encode($iv . $ciphertext);
}
/**
* Decrypt — only called at the moment a winner clicks "Reveal Key".
* The decrypted key is returned to the winner's browser over TLS
* and is never stored in plaintext anywhere.
*/
public static function decrypt(string $encoded): string
{
$key = base64_decode(Config::get('APP_ENCRYPTION_KEY'));
$data = base64_decode($encoded);
$ivLen = openssl_cipher_iv_length(self::CIPHER);
$iv = substr($data, 0, $ivLen);
$cipher = substr($data, $ivLen);
return openssl_decrypt($cipher, self::CIPHER, $key, OPENSSL_RAW_DATA, $iv);
}
}
- The encryption key lives only in the
.envfile on the server — never in the database. - Keys are decrypted on-demand only when the winner clicks "Reveal Key" — no background process ever holds plaintext keys in memory longer than one request.
- The decrypted key is transmitted to the winner over TLS (HTTPS) enforced by Cloudflare.
- Only the winning Steam account can trigger decryption — the key ID is validated against the session's Steam ID before any decryption occurs.
Public Ban List
All active bans are visible to the public. We believe transparency deters abuse and reassures legitimate users that their competition is clean.
What the ban list shows
- Username & Steam profile link of the banned account.
- Ban reason — e.g. Botting / Alt Accounts, Fraud, Harassment.
- Date banned — when the ban was issued.
- Expiry — exact date, or "Permanent" for indefinite bans.