The trial farm that hit you last week probably looked clean on paper. Every email was a fresh Gmail or a real domain. Every IP was residential. Every card, if there was one, authorized. What was wrong was how the accounts behaved: forty signups completed in under nine seconds each, straight from the landing page to the API key page, all inside one afternoon.
Behavioral fraud detection is the name for scoring that second thing. It means judging a session by what it does rather than by what it says about itself. The term covers a lot of ground, from a per-IP counter in your Postgres to a vendor script that records how your pointer wobbles before a click, and the ground is not evenly valuable. This post sorts it: which behavioral signals you already have, which need a client-side script, which are privacy liabilities, and the one question none of them can answer.
Four families of behavioral signal
Behavior gets collected in four places, and the place decides both the cost and the evidentiary weight.
Velocity is counts over a window: signups per IP per hour, trials per device per day, distinct cards per device per fifteen minutes. It lives entirely in your server logs.
Timing is the gap between events for one actor: page load to form submit, account creation to first API call, trial start to the action that costs you money. Also server-side.
Navigation is the sequence of pages and the way the form was filled: whether the user visited pricing before signup, whether every field was pasted in one burst, whether focus moved through inputs in tab order at machine-constant intervals. This needs a browser script, because your server only sees the final POST.
Input behavior is the fine-grained stuff: pointer trajectories, keystroke intervals, scroll acceleration. This is what "behavioral biometrics" means in vendor decks. It also needs a script, and it needs a legal review the other three do not.
The OWASP Automated Threats handbook classifies what you are up against as OAT-019 Account Creation, "create multiple accounts for subsequent misuse," with OAT-001 Carding and OAT-012 Cashing Out as the payment-side siblings. Every one of those is defined by bulk and automation. That is the clue for where behavior is strongest.
Velocity and timing: the signals already in your logs
Start here.
An attacker running OAT-019 against your free tier wants throughput. A script can complete your signup form in two seconds and create a hundred accounts an hour from one machine. Every one of those accounts is individually plausible and collectively absurd. A per-device counter that says "eleven trial starts in the last hour from this fingerprint" does not need to know who the attacker is. It only needs to notice that no human customer does that.
Timing works the same way. A real founder who lands on your pricing page, reads it, clicks signup, types an email, and confirms takes somewhere between forty seconds and several minutes. A script does it in the time the round trips take. Time-to-action is a single subtraction between two timestamps you already store, and it separates scripted signups from typed ones better than most of what a browser script will tell you.
The reason these two beat the fancier signals is what the attacker has to give up to defeat them. To pass a per-device rate limit they need more devices and more residential proxies per account. To pass a time-to-action floor they need to slow down. A trial farm's economics depend on the marginal account being nearly free. Make it cost thirty seconds and a fresh proxy session, and the farm moves to a softer target.
Two rules if you build these yourself:
- Key the counters on something the attacker cannot rotate for free. Device fingerprint and card fingerprint are expensive to rotate. IP is cheap to rotate and is shared by whole offices and carrier networks, so a per-IP counter alone produces both misses and false positives. Use IP as a soft signal that raises suspicion, never as the thing that decides.
- Set the windows to match the abuse, not the calendar. Card testers run in bursts of minutes. Trial farmers run in batches of hours to days. One counter with one window catches neither.
Navigation and form-fill: what the browser script buys you
The server sees a POST. It does not see whether a human filled the form.
A browser script can see that. It can record that all five fields were populated within 40 ms of each other, which means a paste or a script, not typing. It can see that no pointermove event fired before the submit click, a strong tell for a headless driver. It can check for the usual headless fingerprints: navigator.webdriver set to true, a missing chrome object in a browser claiming to be Chrome, a canvas hash that matches a known Puppeteer default. Stripe's advanced fraud detection docs describe the same logic: bots "tend to move through a website and checkout form much faster than a real person would; card numbers are also frequently copy-pasted rather than typed," and Stripe.js reads "mouse activity indicators and how long a user spends on different pages" as predictors of bot-like behavior.
This is real signal against scripts. Understand what it is not signal against.
A click farm is a room of people, or a marketplace of people, paid a fraction of a cent to complete a form in a real Chrome on a real phone. Their pointer moves. Their keystrokes have jitter. Their navigator.webdriver is false because there is no driver. Every navigation and form-fill check passes, because the check is answering "is this a human in a real browser" and the answer is yes. The account is still fraudulent.
So the browser script distinguishes scripted traffic from human traffic, and it stops there. Once an attacker upgrades from Puppeteer to paid humans, you are back to velocity, timing, and identity.
What behavior cannot prove
Three things, and the third is the one most teams get wrong.
Automation is not fraud. Your legitimate customers script your signup for CI, integration tests, and internal tooling. A headless browser hitting your API-key page is often a developer doing exactly what you built the product for. A behavioral flag on automation is grounds for a closer look, not a decline.
A slow session is not a person. An attacker who has read this post adds await sleep(randomBetween(30_000, 90_000)) before the submit. The time-to-action floor then costs them time rather than stopping them. That is still a win in economic terms, but a slow, wobbly, humanlike session is only a session that was not cheap.
And the one that matters most: no behavioral pattern can tell you this is the same actor who charged back last month. Behavior is a property of a session. Identity is a property that persists across sessions. A keystroke-rhythm profile does not survive a new laptop, a new keyboard, or a different person at the same farm. The account that came back for its eleventh free trial does not behave like a repeat offender. It behaves like a first-time signup, because that is what it is pretending to be. What links it to the previous ten is that it used the same card fingerprint, the same device, or the same payer wallet as a cluster you already convicted.
Behavior detects automation and bulk. An identity graph over hashed keys detects repetition. The common failure is asking behavior to answer the identity question, then being surprised when the repeat offenders are humans on new devices.
Why a reCAPTCHA v3 score is not a fraud decision
The most common advice in every "fake signups" thread is to add reCAPTCHA v3 and move on.
reCAPTCHA v3 is score-based behavioral bot detection. Google's reCAPTCHA v3 docs describe a score where "1.0 is very likely a good interaction, 0.0 is very likely a bot," suggest a default threshold of 0.5, and recommend loading it in the background of your pages so it can observe both legitimate and abusive behavior. It is the navigation-and-input family, run by Google, returned as a single float.
The float has two weaknesses. The first is that automation with a real browser profile passes. A public issue on Google's own client library, recaptcha v3 always 0.9 even with Bot, has @ertejaspatel reporting a Selenium bot with a modified user agent that "still returns 0.9." The second is that a score is a commodity. Solver services sell reCAPTCHA v3 tokens at $1.45 to $2.99 per 1,000, with the target score as an API parameter. A trial farm that wants a 0.9 buys a 0.9 for well under a cent per account.
In a public write-up on trial abuse, Prantik Chatterjee lists the first round of defenses as "CAPTCHAs → bots solved them anyway" before moving on to device and repeat-visitor identification.
None of this makes the score worthless. It makes it one behavioral input among several, with a known price to defeat. Do not block on a 0.3. Let the signup through, put the event in a review queue, and let the score raise the weight of your other signals. If the same device also has six signups this hour, the combination is far more damning than either alone. Blocking on the score by itself throws away real users on privacy browsers and VPNs, and does nothing to the farm that bought its tokens.
The privacy line: biometrics, consent, and what to keep
The fourth family, keystroke and pointer biometrics, is the one that can cost you more than the fraud did.
Two regimes matter. Under the GDPR, Article 22 gives a person "the right not to be subject to a decision based solely on automated processing, including profiling, which produces legal effects concerning him or her or similarly significantly affects him or her." Blocking someone from a paid service on an automated score is arguably such a decision. The exceptions require the processing to be necessary for the contract, authorized by law with safeguards, or based on explicit consent, and the Article 29 Working Party guidance endorsed by the EDPB is blunt that a human who rubber-stamps the machine's output does not count as human involvement. Pointer and keystroke profiling is profiling, and fine-grained biometric data has a much harder time fitting under "necessary for the contract" than a rate limit does.
In the United States, the Illinois Biometric Information Privacy Act requires written consent before collecting a biometric identifier and carries statutory damages of $1,000 per negligent violation and $5,000 per intentional or reckless one, with the 2024 amendment limiting accrual to one violation per person rather than one per scan. BIPA's enumerated identifiers are retina, iris, fingerprint, voiceprint, and hand or face geometry. Keystroke dynamics are not on that list, and whether behavioral biometrics fall under it is contested rather than settled. A small team should read "contested" as "not worth it." You are collecting the data on a signup form, for a marginal gain over a device counter, and taking on class-action exposure to do it.
Compare what sits on the other side of the line. A count of signups per hashed device fingerprint is a number. A timestamp delta is a number. A hashed email or card fingerprint is a one-way key that cannot be reversed. None of these describe a person's body. They describe an actor's rate of interaction with your service, the same category of data as a login attempt log.
Retention finishes the argument. Biometric profiles are only useful if you keep them, because a keystroke signature with no history to compare against is noise. Counters and hashed keys work with short windows and can be scrubbed on a schedule. If your fraud data has a retention policy you can state in one sentence, you are almost certainly on the right side of the line.
Behavior sets the suspicion, identity sets the confidence
Behavioral signals are your suspicion dial. Twelve signups from one device in an hour, a form submitted 1.8 seconds after page load, a headless fingerprint, a reCAPTCHA score of 0.1: each says "something automated or bulk is happening." None says "this is fraud," because each has a legitimate explanation somewhere in your user base.
Identity signals are your confidence dial. A card fingerprint that matches an account you refunded as fraudulent, a device that belongs to a cluster with three chargebacks, a payer wallet that funded a convicted account: these say "this is the same actor" with a confidence that behavior can never reach.
The operating rule follows directly. A behavioral flag alone gets review: let the flow proceed, queue the event, and revoke later if the identity side or a human confirms it. The real customer whose signup looked scripted never sees a wall. An identity-confirmed link to a convicted cluster gets block, because repetition by a known bad actor is the one thing you can decline with a straight face. Behavior tells you where to look. Identity tells you what you found.
This split is how Portreeve is built. Velocity counters per IP, per device, per card, and per cluster are the behavioral side, evaluated in one verdict call at signup, trial_start, trial_convert, checkout_attempt, and login. The identity graph over hashed keys (email, device fingerprint, card fingerprint, phone, payer wallet) is the repetition side, and IP never links accounts on its own. Behavioral shapes by themselves return review, which never blocks the user and lands in a dashboard queue with a signed webhook for a later deny, while block is reserved for high-confidence identity-linked signals; each verdict carries reason codes that name which side fired. Device-keyed counters need the @portreeve/browser snippet described in the device fingerprinting guide. Portreeve does not collect keystroke or mouse biometrics, identity keys are hashed from day one with raw payloads scrubbed to hashes after 90 days, and the free tier covers 1,000 screened events a month with no card.
If you are cleaning up after a trial farm this week, the order of operations is: pull time-to-action and per-device counts from the logs you already have, decide what review means operationally for your product, and only then consider a browser script. The identity half is what catches the farm when it comes back.
You can start screening signups on the free tier without a card and see which side of the split your traffic is failing on.