All articles Bot detection

CAPTCHA-Solving Services: Why a Solved CAPTCHA Isn't Proof of a Human

A CAPTCHA asks one question: can whoever is in front of this puzzle solve it? For years that was a decent proxy for “is this a human using my site?” It no longer is. An entire industry exists to answer the CAPTCHA question on a bot’s behalf, cheaply and at volume, and the token it returns is indistinguishable from one a real person earned.

That does not make CAPTCHAs useless, but it does change what a valid token means. This post explains how solving services work, why the token cannot tell you much, and what can.

How solving services work

The workflow is simple, which is why it scales:

  1. A bot loads your page and finds the CAPTCHA widget and its public site key.
  2. It sends the site key and page URL to a solving service over an API.
  3. The service solves the challenge, either with a human worker in a solver farm or with an automated model, and returns a token.
  4. The bot injects the token into your form and submits.
  5. Your server asks the CAPTCHA provider to verify the token. It is valid, because the challenge really was solved.

Image-recognition and audio challenges have become easier for machine-learning models over time, and behavior-scored “invisible” CAPTCHAs can be farmed by real people in real browsers. Either way, the token says “a challenge was solved somewhere,” not “the session submitting this form is human.”

The mismatch is the signal

The solving happens in one place; the submission happens in another. That split is what you can detect. The bot that submits the token still has to load your page, run your scripts and send the request, and it brings its own environment with it.

Automation in the submitting browser

The solver may be human, but the submitter usually is not. Headless browsers, automation frameworks and patched browser builds leave tells that device intelligence picks up: bot signals, tampering, and inconsistencies between what the browser claims to be and what it actually exposes. The bot detection page covers the categories.

A TLS fingerprint that does not match

Many bots submit forms with HTTP libraries or automation stacks whose TLS handshake does not look like the browser named in their User-Agent. Prynt’s JA4-based tlsFingerprint signal catches that mismatch, reported as TLS_AUTOMATION. A request claiming to be Chrome while negotiating TLS like a scripting library is not a person, whatever the CAPTCHA says.

Timing and interaction

A real person reads, types, moves a pointer or scrolls. A bot that received its token by API often submits with almost no interaction, or with suspiciously regular timing. Prynt’s form protection records time-to-submit, whether the user interacted, whether fields were pasted, and an invisible honeypot field; behavioral analysis adds AUTOMATION_BEHAVIOR when interaction looks robotic. The form protection page covers the setup.

Network and device history

Bots run where hosting is cheap, so datacenter, proxy and residentialProxy signals often fire. And the device itself has a history: the same visitorId submitting fifty signups in an hour is velocity (VELOCITY) no CAPTCHA token can launder.

Putting it together in the request

None of this requires removing your CAPTCHA. Score the session alongside it, and treat the token as one input among several:

// browser: identify with the form's protection signals
const prynt = await Prynt.load({ apiKey: 'pk_live_…', extendedResult: true }); // Smart Signals (JA4, behavioral) on paid plans
const guard = prynt.protectForm(form);
form.addEventListener('submit', async (e) => {
  e.preventDefault();
  const { requestId } = await prynt.identify({
    tag: { action: 'signup' },
    formSignals: guard.signals(),
  });
  form.elements.prynt_request_id.value = requestId;
  form.submit();
});
// server: the token is necessary, not sufficient
const event = await prynt.getEvent(body.prynt_request_id);
log.info('signup risk', { decision: event.decision, reasons: event.risk?.reasons });
if (event.decision === 'block') return reject();
if (event.decision === 'challenge') return requireStepUp();

In practice you branch on decision and log the reasons behind it for analysis. If you see a steady stream of valid CAPTCHA tokens arriving with TLS_AUTOMATION or BOT, you are looking at a solving service at work.

Proof-of-work changes the economics

A solving service is a market: the attacker pays per solved challenge. Your goal is to raise the price per attempt until the attack stops paying.

Proof-of-work does this differently from a puzzle. Instead of asking for a skill that can be outsourced, it asks the device to spend computation, and the cost scales with how suspicious the session is. Prynt’s challenge() runs an invisible proof-of-work in the browser and returns { passed, passToken }. Your server validates the token with your secret key at /v1/challenge/validate. Three properties matter:

  • Risk-adaptive difficulty. Pass the requestId and the difficulty scales with that identification’s risk. Clean sessions solve quickly; a hosting-network session with automation tells pays much more.
  • Single-use tokens. A pass token can be validated once, then returns already_used. A farm cannot solve once and replay.
  • Tenant-bound tokens. A token issued for one Prynt account does not validate on another, so a low-difficulty token from somewhere else cannot be imported.

An attacker can still buy computation, but now every attempt costs real compute, and the expensive attempts are exactly the risky ones. Meanwhile your real users see no puzzle at all. The proof-of-work explainer covers the mechanics in more depth.

A layered setup

A practical configuration for a signup or login form:

  1. Device signals on every submission. Branch on decision; log the reasons.
  2. Proof-of-work for challenge decisions, instead of an image puzzle.
  3. Keep your CAPTCHA if you want, but treat a valid token as necessary, not sufficient.
  4. Velocity caps per device, so even a fully passing session cannot submit at machine speed.

The CAPTCHA alternatives guide and the reCAPTCHA Enterprise comparison cover how to phase a CAPTCHA out entirely if you decide to.

The takeaway

A solved CAPTCHA proves that somebody, somewhere, solved a puzzle. Whether the session in front of you is a person is a separate question, and the answer is in the session: its browser, its network, its timing and its history. Score those, and the solving services stop being worth the money.

Try it free

Prynt is device intelligence with a free tier — visitor IDs, bot & fraud Smart Signals, and behavioral biometrics, powered by a cross-site network. Start free.

Keep reading