Stop free-tier farming
Stay generous without paying for 10,000 fake accounts. Gate the free claim on a hardware-rooted device id and the marginal cost of a fake account becomes the cost of a physical computer.
The failure mode
You ship a generous free tier: 100GB of bandwidth, $5 of compute credits, a starter API key. Within weeks, finance pings you — a large share of the signups are bots, scraper farms, or arbitrage operators stacking accounts to resell credits.
The usual responses are bad trades. Require a credit card throws away most of your real signup conversion to defeat the bots. Require phone verification costs about a cent per signup and gets defeated by SIM banks anyway. Both tax the honest majority to inconvenience a motivated minority.
Why the device is the right constraint
Every other identifier the attacker controls is cheap to rotate: inboxes, phone numbers, IPs, cookies, browser fingerprints. Hardware is the one input they have to actually buy.
The device id Root Herald returns (device.ueid) is hardware-rooted — reformatting the OS doesn't clear it — per-tenant, so it doesn't leak across products, and anonymous, so you store no personal data to get it.
Farming a free tier stops being profitable when one more account costs one more machine. You are not trying to make abuse impossible — you are moving its unit cost above the value of what it steals.
Verify the device at claim time
Mint a challenge, relay the nonce to your client, and appraise the evidence server-side. Rented cloud servers and software emulators are rejected by strict-hardware, so a farm running in EC2 never reaches your claim logic.
import { RootHerald } from "@rootherald/node";
const rh = new RootHerald({ secretKey: process.env.RH_SECRET_KEY }); // rh_sk_…
// Earlier: rh.issueChallenge() -> send the one-time challenge to your app,
// which collects the sealed hardware proof and posts back { challengeId, evidence }.
const verdict = await rh.verify(req.body.evidence, {
challengeId: req.body.challengeId,
policy: "rootherald:builtin:strict-hardware", // real physical chips only
});
if (verdict.device.enrollmentRequired) {
// First time we've seen this machine — run the one-time enroll relay, then retry.
return res.status(409).json({ error: "enrollment_required" });
}
if (verdict.device.verdict !== "pass")
return res.status(403).json({ error: "device_check_failed" });Enforce one claim per device
Store the ueid against the claim, and count before granting. This is the whole enforcement surface — a lookup and a comparison.
// Already claimed free credits from this device?
const claims = await freeTierRepo.countByDevice(verdict.device.ueid);
if (claims >= 1) {
return res.status(409).json({
error: "device_already_claimed_free_tier",
message: "Free credits are limited to one claim per device.",
});
}
await freeTierRepo.claim({ deviceId: verdict.device.ueid, accountId: req.userId });What this preserves
The integration is silent. Your real signups never see a CAPTCHA, never receive an SMS, and never install anything — the check runs against hardware the OS already ships. Real users get the full free tier on first claim.
What you give up: customers who deliberately switch devices to claim multiple free tiers. That is a small population whose unit economics resemble paid customers anyway, so you are not losing free-to-paid conversion you would otherwise have captured.
Layer it with what you already have
Hardware id is the strong constraint; email-domain rate-limiting is a useful weaker one. Together: one device and one email domain gets one free tier. Two real colleagues in the same office share a domain but have different machines, so both get credits. One attacker rotating inboxes on a single machine is blocked.