From FindIP Engineering

Visitor risk scoring vs CAPTCHA vs rate limiting

Three controls that are often treated as alternatives. They answer different questions, and the useful design uses each one for the question it can actually answer.

A public signup, login or checkout form usually collects its defences in the order the problems showed up: a rate limit after the first burst, a CAPTCHA after the first bot wave, and a risk score after the first abuse that neither of them caught.

That history makes the three look like upgrades of one another. They are not. A rate limit measures volume, a CAPTCHA tests whether a browser session can pass a challenge, and a risk score describes the network a request came from. Each one is blind to what the other two see.

The short version

ControlQuestion it answersWhat it seesWhat it missesCost to a legitimate user
Rate limitingHow many requests came from this key in this window?Counts per IP, account, token or routeSlow, distributed attempts; where a request came fromNone until the limit is hit; shared networks hit it first
CAPTCHACan this session pass a challenge right now?One interaction in one browserSolving services and real browsers driven by a person; anything that posts to your API directly unless the token is checked server-sidePaid by everyone it is shown to
Visitor risk scoringWhat kind of network is behind this action?Proxy, VPN, Tor, hosting, malicious-IP and rotation signals, with reasonsIntent. A risky network can carry a good customer and a clean one can carry abuseNone when you only observe; whatever response you attach otherwise

Rate limiting: a volume control

Rate limiting belongs on every public endpoint. It is cheap, it runs on your own infrastructure, and it protects capacity as well as business logic. Keep it whatever else you add.

Its limit is the key it counts by. Per-IP limits stop one address sending a hundred signups a minute. They do not stop a hundred addresses sending one each, which is what a proxy pool does. They also punish the wrong people: an office, a university or a mobile carrier can put thousands of legitimate users behind a handful of addresses.

A rate limit also carries no explanation. It can tell you a threshold was crossed. It cannot tell you that the last forty accounts came from the same hosting provider.

CAPTCHA: a challenge, not a classification

A CAPTCHA raises the cost of automation, and modern challenges such as Cloudflare Turnstile do it with far less user effort than image puzzles did. It is the right tool when you have a reason to doubt a specific session.

Used as a blanket control it has three problems:

  • Everyone pays. A challenge on every signup adds a step for the large majority who were never a risk.
  • It tests the moment, not the source. A person creating their fifth trial account through a proxy passes a challenge as easily as a new customer does.
  • It only counts if the server checks it. A challenge widget whose token is never validated by your backend stops nothing that posts to the endpoint directly.

Visitor risk scoring: context about the connection

Risk scoring looks at the network behind an action: whether the address belongs to a proxy, VPN, Tor exit, relay or hosting provider, whether it has a history of malicious activity, and whether the session's IP or network changed while it was open. The output is a score, a recommendation and the reasons that produced them.

That is information the other two controls do not have. It is also only information. A VPN flag describes a connection, not a person, and a score is an assessment, not proof of abuse. Corporate networks, privacy-conscious customers and travellers all produce the same signals as an abuser does.

A score from the browser is a hint

Anyone can edit browser code or skip it entirely. If a result will stop a signup or a payment, confirm it from your server first. In FindIP Shield that is one call to the verify endpoint with your secret key.

How they fit together

The three controls work best in sequence, each one narrowing the job of the next.

Rate limitAlways on. Caps volume per key on every endpoint.
Risk scoreOn every valuable action. Decides who needs a closer look.
ChallengeOnly for the sessions the score picked out.
Server checkBefore the account is created or the payment is taken.
  1. Rate limit everything. It is your floor and it protects you when every other signal is unavailable.
  2. Score the actions that matter. Signup, login, lead and checkout. Start by observing, with no response attached, until you know what your normal traffic looks like.
  3. Challenge selectively. Show a CAPTCHA to the sessions with a challenge recommendation instead of to everyone. Most visitors never see it.
  4. Verify on the server. Whatever the browser did, the backend makes the decision that counts.

What this looks like with Shield

Shield scores each event and returns a recommendation of allow, monitor, challenge or block. A recommendation on its own stops nothing. You choose a response per form in the dashboard: monitor only, slow down, a Turnstile challenge, stop, or redirect. What the page actually did is recorded as an outcome next to the recommendation.

The server side is the same whichever response you picked. Send the Shield session ID with the form, then verify it:

const response = await fetch(
  'https://shield.findip.net/v1/shield/sessions/verify',
  {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.FINDIP_SHIELD_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      session_id: req.body.findip_session_id,
      event: 'signup_attempt',
    }),
  },
);

const verified = await response.json();

if (!verified.verified || verified.risk_status === 'unknown') {
  // No usable risk result. Fall back to your rate limit and normal checks.
  return continueWithoutRiskBasedEnforcement();
}

if (verified.recommendation === 'challenge') {
  return requireAdditionalVerification();
}

Note the first branch. When intelligence is unavailable the answer is "we do not know", and the rate limit you kept in step one is what still protects the endpoint.

Which one to add next

If the problem isStart with
One source hammering an endpointRate limiting
Scripted form posts from simple botsA CAPTCHA with server-side token validation
Fake signups or trial abuse spread across many addressesRisk scoring, then a challenge for the high scores
Conversion dropping since a CAPTCHA went on every formRisk scoring to decide who sees the CAPTCHA
No idea where bad signups come fromRisk scoring in observe-only mode

Mistakes we see most often

  • Removing the rate limit because a smarter control was added.
  • Treating a single signal, usually a VPN flag, as a block rule.
  • Trusting a browser-side score or an unvalidated CAPTCHA token for a decision that costs money.
  • Letting an unavailable lookup quietly count as a safe visitor.
  • Turning on blocking before watching a week of ordinary traffic.

See what the score would say about your traffic

Add Shield to one form and watch in monitor mode. Nothing is challenged or stopped until you choose a response.

Start with Shield free See an example dashboard How responses work