Designing a referral system that can't be gamed by throwaway accounts

Designing a referral system that can’t be gamed by throwaway accounts

设计一个无法被“小号”刷单的推荐系统

I just shipped a referral system for Adsyte, my free directory for indie projects, and the design decision behind it is worth sharing because it’s a pattern that applies to any growth loop with a token reward attached. 我刚刚为我的独立项目免费目录网站 Adsyte 上线了一套推荐系统。其背后的设计决策非常值得分享,因为它适用于任何带有代币奖励的增长循环模式。

The obvious version, and why it’s broken: The naive implementation: give the recruiter tokens the moment someone signs up through their link. Simple, but it has an exploit built in. Signing up costs nothing, and OAuth makes throwaway accounts trivial. Anyone can self-refer through five Discord accounts and walk away with free reward tokens without bringing a single real user to the platform. 显而易见的版本及其缺陷:最简单的实现方式是,当有人通过推荐链接注册时,立即给予推荐人代币。这虽然简单,但存在内置的漏洞。注册无需成本,而 OAuth 使得创建“小号”变得轻而易举。任何人都可以通过五个 Discord 账号进行自我推荐,在没有为平台带来任何真实用户的情况下,轻松骗取奖励代币。

What I did instead: The payout only fires when the recruit publishes their first listing, not when they sign up. This one change closes the loop: A fake account costs nothing, but a real listing needs an actual project with a real URL. The listing already has to pass duplicate-URL detection and hCaptcha, so faking one is meaningfully harder than faking a signup. Every token paid out corresponds to a listing the directory actually gained, which is the metric that matters, not signups. 我的改进方案:奖励仅在被推荐人发布第一个项目列表时发放,而不是在注册时。这一改变闭环了整个流程:虚假账号没有成本,但发布真实列表需要一个带有真实 URL 的实际项目。由于列表发布必须通过重复 URL 检测和 hCaptcha 验证,伪造一个列表比伪造注册要困难得多。每一枚发出的代币都对应着目录实际获得的一个项目,这才是真正重要的指标,而非注册量。

Implementation notes: Referral code is an HMAC of the user’s id, derived deterministically rather than stored as a random token, so there’s nothing extra to generate or leak. The code lives in a cookie set on landing (?ref=CODE), read once at OAuth callback, and tied to the account via a Redis SETNX so it can only ever be set once, self-referral excluded outright. Payout uses SETNX again on a per-recruit key so double-firing (retries, race conditions) can’t double-pay. A daily cap per recruiter stops a single compromised or bot-driven account from draining the reward pool in one sitting. 实现细节:推荐码是用户 ID 的 HMAC 哈希值,通过确定性算法生成,而非存储随机生成的令牌,因此无需额外生成或担心泄露。推荐码存储在着陆页的 Cookie 中(?ref=CODE),在 OAuth 回调时读取一次,并通过 Redis 的 SETNX 命令绑定到账号,确保只能设置一次,从而直接排除了自我推荐。发放奖励时再次使用 SETNX 锁定每个被推荐人的键值,防止因重试或竞态条件导致重复发放。每个推荐人的每日上限设置,则防止了单个被劫持或机器人账号一次性耗尽奖励池。

Nothing here is novel, it’s the standard “pay for the outcome, not the action” principle, but I don’t see it applied to referral systems as often as it should be. Most implementations I’ve seen reward signup because it’s the easy event to hook into, and then bolt on fraud detection after the abuse shows up. Reward the outcome that actually costs the abuser something, and you don’t need the fraud detection layer at all. Curious how others have handled this, especially the daily cap number. I picked 10 fairly arbitrarily. 这里没有任何创新,这只是标准的“为结果付费,而非为行为付费”原则,但我发现它在推荐系统中的应用并不像应有的那样普遍。我见过的大多数实现都奖励注册行为,因为这很容易挂钩,然后在滥用出现后再补救式地添加欺诈检测。如果奖励那些对滥用者有实际成本的结果,你根本就不需要欺诈检测层。很好奇其他人是如何处理这个问题的,尤其是每日上限的设定。我选 10 这个数字比较随意。