How one signup becomes allow, challenge or deny
Below is a decision recorded from the live API, replayed a signal at a time. Nothing about the person is unusual on its own. The answer comes from what happens when five ordinary observations turn out to describe the same someone.
A signup arrives
A new address, a browser with no history, an IP you have not sold to before. It looks like any other Tuesday.
The browser has been here
This device already finished a free trial, under a different name. That alone proves little, because people share machines.
The address is new, the person is not
Canonicalized, stripped of the plus tag and the dots, it resolves to an account you already have.
The mailbox is temporary
It is a disposable domain. Nobody opens a renewal notice at an address built to expire.
Same block, different door
The subnet has already been used to consume a trial. A VPN moves the address, not the neighbourhood.
And always at this hour
The accounts this one links to started their trials in the same part of the day. People have habits. So do their scripts.
Five signals. Four families. One answer.
out of 100
The real response carries a weight on every reason. Those numbers, and the two lines that turn a score into a verdict, belong to your dashboard and your API, not to this page.{"decision": "deny","score": 81,"reasons": [{ "code": "D_DEVICE_REPEAT", "message": "Device previously used for a consumed trial" },{ "code": "E_EMAIL_REPEAT", "message": "Email matches a prior account" },{ "code": "E_DISPOSABLE_DOMAIN","message": "Disposable email domain" },{ "code": "N_IP24_REPEAT", "message": "IP subnet previously used for a consumed trial" },{ "code": "B_ABUSE_HOUR_MATCH", "message": "Signup hour matches prior abuse in linked cluster" }]}
POST /api/v1/decisionsThe token comes from the browser snippet and is good for five minutes.{"email": "d.mercer+t7@mailinator.com","ip": "45.83.220.7","visitorToken": "…"}
D_DEVICE_REPEATE_EMAIL_REPEATE_DISPOSABLE_DOMAINN_IP24_REPEATB_ABUSE_HOUR_MATCHFive families, because one signal is never enough
A single repeat is usually innocent. Households share a laptop, offices share an IP, and people genuinely forget which address they signed up with last year. Every signal OneTrial reads belongs to one of five families, and the answer depends far more on how many families agree than on how loud any one of them is.
That is also the protection against false positives. A shared office network on its own moves a signup very little. The same network, plus a device that finished a trial last month, plus an address that canonicalizes onto an existing account, is a different claim entirely.
- Payment
Card fingerprints from your own Stripe account, so one card cannot quietly start a second trial. The number itself never reaches us.
- Device
A device hint computed in the browser, plus a signed token that survives a cleared cookie jar. Treated as a hint, never as proof.
The address canonicalized: plus tags stripped, dots folded where the provider ignores them, disposable domains recognised.
- Network
The subnet rather than the address, whether the IP belongs to a datacenter, VPN or Tor exit, and where in the world it resolves.
- Behaviour
How the form was filled: the pace, whether the visitor arrived cold, and whether the hour matches earlier abuse in the same cluster.
What linking means, and what it does not
Two accounts are linked when they share something that identifies a person rather than a place: a card fingerprint, a device token, a canonicalized address. Weaker hints, like a subnet, need company before they link anything, because a coffee shop is not a person.
Linking stops at the accounts directly connected to the signup and the ones connected to those. It does not crawl outward until half your customer base is one cluster, which is how graph based tools end up denying an entire university.
Everything decays. A trial consumed eighteen months ago says less about today than one consumed last week, and the older evidence fades rather than being deleted, so a returning real customer is not treated as a repeat offender forever.
The answer is yours, never the visitor's
Your server receives the verdict, the score and the reason codes. The visitor receives a trial, a request for one proof, or a paid plan. They never see a score, a reason, or which signal gave them away, because anyone told exactly why they were blocked knows exactly what to change next time.
The real response carries a weight on every reason. Those numbers, and the two lines that turn a score into a verdict, belong to your dashboard and your API rather than to this page.{"decision": "challenge","score": 53,"reasons": ["D_DEVICE_REPEAT", "N_IP24_REPEAT"],"challengeOptions": ["card", "email"],"challengeUrl": "https://onetrial.dev/c/9f3c1a"}