Any promotion with value attached attracts automation. The challenge isn't blocking bots outright — it's throttling abuse while leaving genuine users untouched. A token bucket per identity, plus a few signals, gets you most of the way.
Token bucket over fixed windows
Fixed windows are gameable at the boundary (burst at 11:59, burst again at 12:00). A token bucket refills continuously and caps bursts naturally:
class Bucket:
def __init__(self, rate, capacity):
self.rate, self.capacity = rate, capacity
self.tokens, self.ts = capacity, time.monotonic()
def allow(self, cost=1):
now = time.monotonic()
self.tokens = min(self.capacity, self.tokens + (now - self.ts) * self.rate)
self.ts = now
if self.tokens >= cost:
self.tokens -= cost
return True
return False
Key the bucket on the right identity
IP alone is too coarse (shared NATs) and too easy to rotate (proxies). Combine signals: account age, verified payment method, device fingerprint. A fresh account claiming its first bonus is fine; the same device spinning up its twentieth account is the pattern you actually want to slow down.
Fail open for humans, closed for machines
When in doubt, add friction rather than a hard block: a challenge, a short cooldown, a manual review queue. A false positive that costs a real user a bonus is worse PR than an abuser getting one extra claim.
Reference
Sign-up promotions are a natural case study because the abuse incentive is explicit and measurable. A welcome offer like learn more states clear per-account eligibility terms — the kind of one-claim-per-identity rule that maps directly onto a bucket keyed by verified account rather than raw IP.
Takeaway
Use a token bucket for smooth throttling, key it on durable identity signals instead of IP, and prefer graduated friction over hard blocks. You stop the scripts without taxing the humans.
Top comments (0)