On this page
Stripe's fraud models found 6.2x more abusive free trials across its network from November 2025 to February 2026, and AI startups that offer self-serve trials with direct API access saw 10x more attempted abuse than enterprise AI companies (Stripe). Every extra trial account needs a new email address, and on a site that limits trials per IP, a new IP address. Most ways to get a new address cost nothing. We counted each in our data on October 27, 2026, looked up addresses from each in our API on November 2, and built a trial signup in Express that asks those visitors for a card instead of turning them away.
Every way to a new IP address, and what it costs
| Where the new address comes from | What it costs | IPv4 addresses | The flag that answers |
|---|---|---|---|
| A VPN service with a free plan | nothing | 1,354,194 | is_vpn |
| A VPN service with paid plans only | a subscription | 1,783,063 | is_vpn |
| Cloudflare WARP | nothing | 99,484 | is_relay |
| Tor | nothing | 1,229 | is_tor |
| A public proxy | nothing | 12,323 | is_pubproxy |
| A cloud server | by the hour | 482,853,305 | is_hosting |
| A datacenter proxy | paid | 571,417 | is_dcproxy |
| A residential proxy | paid, by the gigabyte | 35,199,288 | is_resproxy |
The free-plan row counts every address of the 208 consumer VPN services in our data with a free plan, paid servers included, since 69 of them also sell paid plans. The paid row is the 272 that sell only paid plans; 86 more, whose pricing we haven't recorded, hold 258,564. Firewall vendors' gateways and privacy relays aren't counted as VPN services. WARP's exits are from our Relay IP database, and Tor's from the Tor Project's own list. The proxy rows count the exits seen in the last 30 days, and the cloud row every address in our hosting classification.
A phone on mobile data is one more. It gets the carrier's address, which most carriers share among many subscribers: Richter et al. found carrier-grade NAT in more than 90% of the cellular networks they measured in 2016. No flag marks an address as a phone's: is_mobproxy marks one seen in a mobile proxy pool, and blocking one blocks a carrier's other subscribers.
Why one trial per IP doesn't hold
With a free VPN plan, the next account is a reconnect away, from another of the service's servers. A limit of one trial per address makes the abuser pick another server, and it doesn't make the trial cost them anything.
The same limit falls on people who did nothing. WARP and iCloud Private Relay send many users out through each exit, an office sends all its staff through one gateway, and a carrier puts its subscribers behind shared addresses. Stripe's guide to trial abuse suggests counting "velocity by Internet Protocol (IP), subnet, and autonomous system number (ASN)" and says to "Only slow down high-risk cohorts". A lookup tells you which cohort an address is in.
What each flag catches
We drew 30 addresses at random from the free-plan services' ranges: all 30 answered is_vpn: true. 12 of them answered at confidence low, so a rule that skips low lets much of the free tier through.
A rule on hosting addresses alone misses more of the free tier than of the paid one: 12.4% of the addresses of services with a free plan sit outside our hosting classification, against 4.9% of the paid-only services'. VPN Gate, a free VPN the University of Tsukuba runs on volunteers' servers, shows the gap. Of the 98 servers its server list gave out on October 27, 96 answered is_vpn and 2 answered is_hosting. Does blocking datacenter ASNs stop VPN users? measures the same gap for network lists.
WARP isn't a VPN to our API. Its exits answer is_relay, with relay.provider cloudflare_warp, as iCloud Private Relay's answer is_relay with apple_private_relay, and both answer is_vpn: false. A check on is_vpn alone lets WARP through (Should you block Cloudflare WARP? tells WARP and Private Relay apart). Every Private Relay connection comes from an iPhone, iPad or Mac whose owner pays for iCloud+, so the gate below lets it through (Should you block iCloud Private Relay?). The WARP exit and the Private Relay exit we tried both answered is_resproxy too: an exit carries its other users' traffic, and someone's had been seen in a proxy pool. So the gate reads is_relay before the proxy flags.
The Tor exit 190.120.229.98, from the Tor Project's list, answered is_tor and is_hosting, and is_vpn: false (How to block Tor exit nodes covers the exit list). A public proxy exit answered is_pubproxy and no other flag (Public proxy detection covers the flag). 3.2.0.10, in AWS's published EC2 ranges, answered is_hosting alone.
Residential proxies are the abuser's paid step up, and the biggest of the proxy pools: 35.2 million IPv4 addresses in 30 days. is_resproxy answers for them (How long do residential proxy IPs last?). Both T-Mobile US addresses we tried answered is_resproxy as well. One carrier address carries many subscribers, so the proxy traffic it was seen with may not be the visitor's. That's one reason the gate below asks for a card and never refuses.
A trial gate in Express
Here it is in Express 5.3.0 with our Express middleware, vpndetection-express 2.0.20, vpndetection 5.10.2 and express-rate-limit 8.7.2, on Node.js 22.22:
npm install express express-rate-limit vpndetection vpndetection-express// app.js
import express from 'express';
import { rateLimit } from 'express-rate-limit';
import { VPNDetection } from 'vpndetection';
import { vpndetection } from 'vpndetection-express';
const client = new VPNDetection({ apiKey: process.env.VPNDETECTION_API_KEY });
// Firewall vendors whose gateways answer isVpn: an office, not a VPN service.
const FIREWALLS = new Set([
'barracuda', 'cisco', 'datto', 'draytek', 'forcepoint_ngfw', 'fortinet',
'juniper_net', 'lancomsystems', 'sophos', 'stormshield', 'untangle',
'watchguard',
]);
// Many people behind one address: a privacy relay, a cloud gateway or an office.
function shared(r) {
return Boolean(r?.isRelay || FIREWALLS.has(r?.vpn?.provider));
}
// Why this address needs a card for a trial, or null when it doesn't.
function stepUp({ result: r, error }) {
if (error) return null; // no answer: an outage of ours costs you no signups
if (r.isRelay) return r.relay?.provider === 'cloudflare_warp' ? 'warp' : null;
if (r.isVpn) return FIREWALLS.has(r.vpn?.provider) ? null : 'vpn';
if (r.isTor) return 'tor';
if (r.isPubproxy) return 'public proxy';
if (r.isResproxy || r.isDcproxy || r.isMobproxy) return 'proxy';
if (r.isHosting) return 'hosting';
return null;
}
// Three trials a day from one IPv4 address or IPv6 /64; the fourth needs a card.
const repeats = rateLimit({
windowMs: 24 * 60 * 60 * 1000,
limit: 3,
ipv6Subnet: 64,
skip: (req) => shared(req.vpndetection.result),
handler: (req, res, next) => next(),
});
const app = express();
app.set('trust proxy', 'loopback');
app.post('/trial', vpndetection({ client }), repeats, (req, res) => {
const over = req.rateLimit && req.rateLimit.used > req.rateLimit.limit;
const reason = over ? 'repeat' : stepUp(req.vpndetection);
res.json(reason ? { trial: 'card', reason } : { trial: 'open' });
});
app.listen(3000, '127.0.0.1');Nobody is refused. A visitor whose address needs a step up gets { trial: 'card' } and the reason, and your signup asks for a card, a phone number, or offers a shorter trial. stepUp takes WARP to a card and lets Private Relay and cloud web gateways, Zscaler and the like, through. It does the same for the firewall vendors' gateways, which answer is_vpn with the vendor as vpn.provider. FIREWALLS holds 12 of the 26 such vendors in our data; the VPN Provider database, on the Enterprise plan, lists every one.
The limiter counts trials per IPv4 address and per IPv6 /64, the network one home connection is given at the least (VPN detection on IPv6 explains the /64), and its handler passes the fourth on with req.rateLimit over the limit, instead of answering 429. It skips shared addresses, where three trials a day could be three colleagues. The store is in memory, so use one of its stores once you run more than one process.
The visitors we sent it
trust proxy set to 'loopback' reads req.ip from X-Forwarded-For only when the request comes from a proxy on the same machine, so we sent each visitor as nginx would (our Express post covers the other setups):
curl -s -X POST 127.0.0.1:3000/trial -H 'X-Forwarded-For: 219.100.37.24'{"trial":"card","reason":"vpn"}That's the VPN Gate server. With a key on the Max plan, each visitor answered:
| Visitor | POST /trial |
|---|---|
| Home broadband, US cable ISP | {"trial":"open"} |
Mullvad server 23.165.56.3 | {"trial":"card","reason":"vpn"} |
VPN Gate server 219.100.37.24 | {"trial":"card","reason":"vpn"} |
WARP exit 104.28.209.115 | {"trial":"card","reason":"warp"} |
Private Relay exit 104.28.32.190 | {"trial":"open"} |
Tor exit 190.120.229.98 | {"trial":"card","reason":"tor"} |
| Public proxy exit | {"trial":"card","reason":"public proxy"} |
| Residential proxy exit | {"trial":"card","reason":"proxy"} |
| Mobile proxy exit | {"trial":"card","reason":"proxy"} |
| T-Mobile US address | {"trial":"card","reason":"proxy"} |
AWS address 3.2.0.10 | {"trial":"card","reason":"hosting"} |
| Fortinet gateway | {"trial":"open"} |
The Mullvad server is from Mullvad's own list. We don't print the home line, the proxy exits, the T-Mobile address or the gateway. The home line's fourth trial got {"trial":"card","reason":"repeat"}, and the Private Relay exit's fourth was still open. Four addresses in one /64 of a US cable ISP's IPv6 space went the way of the home line: three open, then repeat on the fourth.
With a wrong key, every visitor got an open trial. With no key, on November 11, 2026, the Mullvad server, the VPN Gate server, the WARP exit, the Tor exit and the Fortinet gateway got a card, and the residential proxy exit and the AWS address an open trial. The proxy flags, is_hosting and vpn.provider come with every paid plan (pricing), and without vpn.provider, FIREWALLS can't name the gateway.
What an IP check can't see
An abuser on their own home line, opening a second account a day later, answers every flag false, and a phone on mobile data answers like the carrier's other subscribers. The address tells you how an account arrived, never who opened it. Pair it with what does: the email address once you've normalized it, a device or browser signal, and the card's fingerprint where you take one.
Every flag the gate reads is in the API docs, and the Express middleware page covers its options. Paid plans start at 250,000 lookups a month (pricing).


