The simplest free-trial abuse rule is also one of the easiest ways to punish legitimate users:
One IP address, one trial.
That rule treats an address as a person. It is not.
A family, university, office, mobile carrier, hotel, or public Wi-Fi network can place many people behind one public address. At the same time, a determined abuser can change addresses more easily than they can change the device and behaviour involved in the abuse.
The design problem is not "How do I block repeat users?" It is "How do I increase the cost of repeated abuse without turning normal shared infrastructure into a denial of service?"
Start with the entitlement
The free evaluation is an entitlement with rules, not merely a route that creates an account.
I want the system to answer:
- Which identity is requesting the evaluation?
- Has this device already redeemed one?
- Is the surrounding network showing unusual redemption velocity?
- Is the request rate itself suspicious?
- Should this request be allowed, denied, or reviewed?
These questions need a consistent decision at the moment the entitlement is granted. Recording signals after granting access is too late when several requests arrive together.
Use multiple bounded signals
No individual signal should pretend to be a perfect identity.
An email address is cheap to create. A public IP is shared and changes. A browser fingerprint can be unstable and invasive. A cookie disappears. Payment details may not exist for a free evaluation.
A practical gate combines a small number of purpose-limited signals:
- Verified product identity.
- A privacy-preserving device identifier.
- A one-way network identifier with limited retention.
- Redemption history.
- Request velocity.
- Known operator decisions.
The result is not "this is certainly the same human." It is a bounded risk decision about whether another free entitlement should be issued automatically.
Prefer review over broad punishment
I like three outcomes:
- Allow: no relevant reuse or velocity concern exists.
- Review: the pattern may be abuse, but legitimate shared use is plausible.
- Deny: a strong, already-established entitlement rule has been violated.
Network reuse often belongs in review rather than automatic denial. A household with several real users should have a path that does not require changing internet providers.
The operator interface should explain which policy triggered without exposing raw sensitive identifiers. "Repeated evaluation activity from the same protected network signal" is usually more appropriate than displaying an address to every support user.
The decision must be concurrency safe
Imagine two signup requests arrive almost simultaneously. Both check the database, both see that the device has not redeemed an evaluation, and both grant one.
The rule was correct. The transaction boundary was not.
The check and grant need serialization around the relevant decision key. In PostgreSQL, a transaction-scoped advisory lock can be useful when the protected key is not naturally represented by one row that can be locked.
textbegin transaction -> acquire lock derived from protected signal -> read current redemption state -> evaluate policy -> record decision and entitlement commit
The lock does not replace database constraints. It coordinates the decision, while uniqueness and state constraints preserve the invariants.
Rate limiting and entitlement limiting are different
Rate limiting controls request volume. Entitlement rules control who receives the scarce benefit.
A person can stay below ten requests per minute and still create a new account every day. Another person can accidentally retry a signup several times in one minute without attempting abuse.
I use both controls, but I do not confuse them:
- Request rate limits protect service capacity and slow automation.
- Redemption rules protect the business entitlement.
- Review workflows handle ambiguous legitimate cases.
Each needs its own reason code and operator response.
Privacy is part of correctness
Abuse prevention can become a justification for collecting everything. That creates a second risk larger than the trial being protected.
The system should minimise what it stores:
- Hash or otherwise protect network and device signals.
- Use a dedicated secret or key separation so ordinary database access does not reveal raw identifiers.
- Retain signals only for the policy window and audit needs.
- Restrict operator access.
- Record policy versions so old decisions remain understandable.
- Avoid claiming a device or network signal identifies a human.
If a signal is collected for evaluation abuse, it should not silently become an advertising profile.
Make the policy explainable
Security controls become difficult to operate when the only output is 403 forbidden.
A useful decision record contains:
- The outcome.
- A stable, non-sensitive reason code.
- The policy version.
- The time window evaluated.
- The entitlement affected.
- The review or override status.
This makes support possible and gives engineering enough evidence to tune false positives.
Abuse prevention is a product system
The control is not finished when the backend rejects a request. The interface needs a truthful message, support needs a review path, operators need bounded evidence, and the product needs a way to distinguish a final denial from a temporary hold.
IP blocking is attractive because it compresses all of that into one rule. The cost is that the rule is wrong about what an IP address represents.
A better system admits uncertainty. It combines signals, makes one concurrency-safe entitlement decision, and gives ambiguous cases somewhere to go besides a dead end.