This page is the attack playbook. For the implementation checklist and detection order, read How to detect and prevent free trial abuse; for a watch-mode snippet, see the trial-abuse fix.
How the attack works
Trial abuse is the cheapest fraud in this collection because it barely requires tooling. The whole technique is producing a new identity on a schedule:
- A new email — a catch-all domain or a plus-address on a provider that ignores it, so one mailbox produces any number of addresses that all deliver.
- A new IP — a residential proxy or just a phone hotspot, enough to break any per-address counting.
- A cleared browser, or for the more committed, a fake-browser (antidetect) profile so the device fingerprint does not repeat either.
At the low end this is one person and a bookmark. At the high end it is an agency running a client workload permanently on somebody's free tier. The distinction matters commercially and not at all technically — the detection is identical.
Why the usual defences miss it
Email uniqueness is not identity. Catch-all domains produce infinite valid addresses; plus-addressing does the same on major providers unless you normalise it, and normalising it breaks legitimate users who genuinely rely on tagged addresses. A disposable-domain check does not help much either: a private catch-all domain is on no throwaway list.
Requiring a card works and has a price: fewer honest evaluators start a trial at all. It is a business decision disguised as a fraud control, and it should be made on your own conversion number rather than on the abuse one.
Per-IP limits catch the careless and punish the legitimate: an office, a university or a carrier NAT range shares one address between hundreds of real people.
Matching device fingerprints works right up until someone uses a fake browser, at which point every trial arrives with a new fingerprint. What does not change is the spoofing itself, which is a signal of its own.
What the verdict returns
Screening at trial signup is one server-side call to POST /v1/evaluate, with the new trial's own account id sent as accountId so the linkage is computed against your accounts only. Example response (excerpt) for the low-end resetter: a home connection, an ordinary browser with its cookies cleared, and a device already seen with eight earlier trials:
{
"decision": "allow",
"risk_score": 0,
"reasons": [
"multi_account_device"
],
"network": {
"vpn": false,
"proxy": false,
"datacenter": false,
"tor": false,
"residential": true,
"service": null
},
"device": {
"antidetect": false,
"automation": false,
"tampering_score": 0,
"times_seen": 14,
"returning": true,
"first_seen": "2026-02-14T09:31:07.000Z",
"linked_accounts": 9,
"multi_account": true
}
}
linked_accounts: 9 is the entire finding. It says this device has been seen with nine of your accounts — which no per-account check can see, because each account really was new. It is per customer and hash-only: your accounts are never linked against another customer's. times_seen and first_seen are different: they are Maskbreak-wide history for the device, not a count of your accounts.
Note the decision is allow. Nothing on the connection or in the browser is a threat, and multi_account_device does not change the decision on its own. That is the right default here: nine accounts on one device is a contractor with nine clients as often as it is one person resetting a trial, and the two deserve very different treatment. Route on the reason in your own code, or set a Multi-accounting action on the console’s Rules tab.
The committed resetter looks different. Behind a residential proxy, in a fake browser that mints a new device per trial, the example response comes back like this:
{
"decision": "block",
"risk_score": 93,
"reasons": [
"proxy_detected",
"antidetect_browser"
],
"network": {
"vpn": false,
"proxy": true,
"datacenter": false,
"tor": false,
"residential": false,
"service": null
},
"device": {
"antidetect": true,
"automation": false,
"tampering_score": 0.8,
"linked_accounts": 1,
"multi_account": false
}
}
The count stays at 1, and it does not matter. A residential proxy shows up as proxy_detected with network.proxy true (residential is false whenever a proxy is detected, and service names the operator when known), and the fake browser shows up as antidetect_browser. Either one is a block by default.
A Rules-tab rule changes the decision only for the signal it names. “Multi-accounting = Review” would send the low-end resetter above to review, and it never lowers a proxy, Tor, automation, fake-browser or emulator block on a linked device.
What to do about it, in order
Blocking is the worst first move here, because the population contains paying customers who have not converted yet.
- Start in watch mode. Log every decision for a month, let every trial through and act on nothing. The distribution will not be what you assumed, and you will find a small tail worth looking at by hand.
- Limit the resource, not the account. Cap what a trial can consume — seats, API calls, exports — rather than how many trials exist. A resetter loses the value; an evaluator does not notice.
- Ask for the upgrade. An account on its fifth trial with real usage is a customer telling you your pricing has a gap. The message that converts is an offer, not a warning.
- Refuse only the unambiguous tail. When you switch to enforce, the engine’s
block(proxy, Tor, automation, fake browser) is that tail. High linkage alone, with no meaningful usage, is a business call you make after the watch-mode month, not a default. Keep the refused group small.
The arithmetic
A worked model. Replace the inputs with yours.
| Input | Model value | Yours |
|---|---|---|
| Perpetual-trial accounts identified | 320 | — |
| Your entry plan, annualised | €300 | — |
| Share that would convert rather than leave | 10% | — |
| Infrastructure cost per trial account per year | €14 | — |
Recovered revenue is 320 × 10% × €300 = €9,600, plus €4,480 of infrastructure you stop donating. The third input is the one everybody gets wrong: the instinct is to multiply all 320 by the plan price and call it recovered ARR. Most of that population leaves rather than pays, and a model that assumes otherwise will justify enforcement that costs more than it returns.
Which is why the honest version of this number needs a holdout — a slice you deliberately leave alone. Without one you cannot distinguish revenue you recovered from churn you caused.
The adjacent problem: competitors in your trial
The same signals surface something teams are often more annoyed by than the free usage: competitors and data brokers taking trials to scrape pricing, feature availability or your customer-facing directory. The pattern is distinctive — a trial that consumes read endpoints heavily, touches nothing that writes, and never invites a second user.
That behavioural half is yours to measure; the network and device half is the same call described above, and the combination is much stronger than either.