Bot detection for signups and logins, without a wall of captchas.
How to detect bots on signup and login with a bot detection API: where to check, what to read on your server, and how to challenge scripts, not people.

A bot detection API answers one question for every signup or login attempt: is a person driving this browser or app, or is it a script? The dependable way to use one is to run the check at the moment of the attempt, read the result on your own server, and choose a response in proportion to the evidence, so people pass straight through and scripts meet friction.
This guide shows how to detect bots on signup and login with Fingerly: where to put the check, what to read, what to do with it, and how to keep real customers out of the blast radius.
Why do bots go after signups and logins?
Because that is where automation gets paid. A signup form creates accounts that can claim trials, bonuses and referral credit, post spam or hold stock. A login form receives credential stuffing: lists of leaked email and password pairs replayed at scale in the hope that some customers reused them. Add one-time passcodes sent by text message and every scripted request becomes a bill.
These attacks arrive from many addresses, so a limit per IP address rarely slows them. The devices and scripts behind the addresses are far fewer than the addresses themselves, which is why the useful thing to count is the device, not the address.
What does a bot detection API actually look at?
It looks for evidence that a browser or app is being driven rather than used. On every identification, Fingerly reports browsers driven by automation frameworks, clients that declare themselves a bot or scraper, browsers running inside virtual machines, reports too thin to identify anyone, and a device that is far busier than your own traffic normally is. In native apps it also reports a debugger or test runner attached to the app.
Each finding carries a confidence and a weight, and the weights add up to a suspect score with a level of low, medium or high. Serious automation hides its obvious flags, so tampering with the browser is reported alongside it. Fingerly never asks the visitor for anything, so there is no captcha or prompt from us: your application decides whether a challenge is needed.
High activity is not a fixed number. It is learned for your organisation, environment and platform, and it stays in shadow for the first seven days and at least 1,000 samples, so a launch week is not mistaken for an attack.
Where should you run the check?
Run it when the visitor submits the form, not when the page loads, and tag it with the action it protects. The five steps below are the whole integration.
- Identify on submit, with a tag such as signup or login, using the browser or native SDK. If identification fails, carry on and send no request ID.
- Send the request ID to your backend together with the form fields. Send nothing else from the SDK.
- On your server, read the stored event by request ID with a secret key. Check that it exists, carries the expected tag, is recent (two minutes is a good window) and has not been used before.
- Decide before you check the password, so a script learns nothing from the attempts you refuse.
- Record the request ID and visitor ID beside the outcome, so you can explain the decision later.
A script that posts straight to your endpoint never ran the SDK, so it arrives with no request ID. Treat a missing request ID as missing evidence and challenge it, rather than letting it through or refusing real customers whose identification merely failed.
What should you do with the result?
Match the response to the evidence, with the mildest friction that works. The Fingerly docs give a starting policy for login that you can adapt, with numbers you should tune on your own traffic:
- Automation found: refuse the attempt.
- No usable identification, or an unscored request: show a challenge, such as a CAPTCHA or an emailed link, before the password is checked.
- Many failed attempts, or many different accounts, from one device in a short window (for example 10 failures or 5 accounts): refuse for the rest of the window.
- A medium level, or a few failures: add a challenge.
- Otherwise: check the password as you do today.
Answer a refused attempt the same way, and with the same timing, as a wrong password. If the response differs, the script learns which credentials were valid.
How do you avoid blocking real people?
Treat every signal as evidence, not as a verdict, and observe before you enforce. Search engine crawlers and monitoring tools often identify themselves, and a declared crawler is reported at medium confidence with a weight you can lower or set to zero. Screen readers and other assistive technology are never counted as automation. A single weak clue fires nothing, because a finding needs a decisive clue or several that agree.
- Run the check for a week or two and log the decision it would have made next to what actually happened.
- Review the sessions it would have stopped, and adjust the risk weights and your own thresholds until they match what you see.
- Turn on friction a real customer can pass before you turn on outright refusals.
- Give challenged customers a way through, such as email verification or a support route.
What does it cost to check every attempt?
Pricing is per identification, from $0.0025, with graduated discounts as your monthly volume grows. Requests Fingerly cannot identify cost nothing, and so does everything made with a development key. The $1 signup credit covers about 400 identifications, enough to run the check on a real flow before you add funds.
Count the moments that matter, such as signups, logins and code sends, rather than page views. That keeps the bill proportional to the decisions you actually make.
