Voight

An invisible CAPTCHA replacement that tells bots and AI agents from humans with proof-of-work, behavioral, and fingerprint signals — no puzzles, no clicks

Voight is the CAPTCHA replacement humans never see. It tells bots and AI agents apart from people using proof-of-work, behavioral, and fingerprint signals — with zero clicks, puzzles, or traffic lights for real users.

reCAPTCHA, hCaptcha, and Turnstile tax every real user with image grids and checkboxes to stop a minority of bots, hurting conversion and accessibility. Voight stays invisible to humans while still building a record of who is actually a bot. Drop the /check/challenge widget where your CAPTCHA used to sit, issue a challenge with POST /v1/challenge, and verify on submit with POST /v1/challenge/verify — real users see nothing.

When to use it

Use Voight anywhere you would reach for a CAPTCHA — signup, login, contact, checkout — but do not want to interrupt real people. It binds to your form_id and origin like a reCAPTCHA site key, but never shows a checkbox or an image grid. Because it is capture-only in beta, today it is a way to build a labeled bot/human corpus from your own traffic without touching conversion, and to have enforcement wired up the day the verdict ships.

How it works

  1. Issue a challenge

    Call POST /v1/challenge to mint a single-use nonce, a binding token, and an ALTCHA-style proof-of-work. The embeddable /check/challenge widget solves the proof-of-work in the background and captures interaction telemetry — no checkbox, no image grid, nothing the user has to do.

  2. Verify on submit

    On form submit, call POST /v1/challenge/verify with the solved proof-of-work, the token, and the nonce. The nonce is single-use, so a replayed or forged challenge is rejected. The captured pointer, scroll, and timing trajectory is recorded for the bot/human model.

  3. Read the capture summary

    verify returns a compact summary — pow_valid, token_valid, solve time, interaction counts, and a challenge_id. In beta this is capture-only: every request passes while the verdict calibrates, so you wire it in now and switch on enforcement when the model is ready.

Quickstart

Issue a challenge, let the widget solve the proof-of-work and capture telemetry, then verify on submit. The widget and the issue call need only your publishable key; verify is a server-side call with your API key.

1. Issue a challenge

bash
curl -X POST https://api.formshield.dev/v1/challenge \
  -H "Authorization: Bearer $FORMSHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "form_id": "signup" }'

You get back a single-use nonce, a binding token, and an ALTCHA-style proof-of-work for the browser to solve. The /check/challenge widget does this for you and solves the proof-of-work in the background.

2. Verify on submit

On form submit, send the solved proof-of-work, the token, the nonce, and the captured voight telemetry to POST /v1/challenge/verify.

bash
curl -X POST https://api.formshield.dev/v1/challenge/verify \
  -H "Authorization: Bearer $FORMSHIELD_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "form_id": "signup",
    "token": "fs2.eyJ...",
    "nonce": "n_8f3a91c2",
    "pow_solution": { "challenge": "a1b2c3...", "salt": "d4e5f6", "number": 18244, "signature": "9c7e...", "took": 62 },
    "voight": { "t0": 0, "ts": 1840, "tgt": [220, 140, 18], "ptr": [[12,40,210,0]], "scr": [], "key": [], "pow": { "d": 18244, "ms": 62 } }
  }'

Response:

json
{
  "version": "1",
  "data": {
    "passed": true,
    "pow_valid": true,
    "token_valid": true,
    "solve_ms": 62,
    "duration_ms": 1840,
    "n_pointer": 1,
    "n_scroll": 0,
    "target_hit": true,
    "challenge_id": "vgt_3f9a2c71b840"
  },
  "error": null,
  "metadata": { "credits": 1 }
}

The signals

Voight fuses several independent signals so no single tell decides the verdict. Each is one input to the bot/human model, never a standalone human/bot gate.

Proof-of-work (ALTCHA core)

The browser brute-forces an HMAC-bound SHA-256 challenge that the server re-derives statelessly. It imposes a real compute cost on bulk automation and is one tamper-resistant signal, never a standalone human/bot gate.

Behavioral trajectory

The widget records timestamped pointer movement, scroll, and interaction timing as the user reaches the target. Trajectory shape feeds a bot/human model — generative cursors are why this is one fusion signal, not the whole verdict.

Single-use nonce binding

Each challenge is HMAC-bound to a fresh nonce and your form/origin. verify consumes the nonce once, so a replayed token reports token_valid=false. This is a corpus-poisoning tax and a tell against scripted reuse.

Fingerprint and network context

Every verify also captures IP, ASN, country, user-agent, and referrer alongside the trajectory, giving the label-later pipeline independent signals to cross-check rather than trusting any single tell.

Verify fields

The verify response is a compact capture summary. Each verify costs 1 credit; issuing a challenge is free.

passed boolean

The verdict. Capture-only in beta — always true. Do not gate enforcement on this field yet.

pow_valid boolean

Whether the submitted proof-of-work re-derives correctly server-side. A valid solution means the browser paid the compute cost.

token_valid boolean

Whether the token and nonce are intact and unused. A replayed or forged challenge reports false — the nonce is single-use.

solve_ms number

Milliseconds the browser took to solve the proof-of-work.

duration_ms number

Total time from challenge issue to submit, derived from the captured trajectory.

n_pointer number

Count of captured pointer-movement samples.

n_scroll number

Count of captured scroll samples.

target_hit boolean

Whether the interaction reached the widget’s target region.

challenge_id string

Identifier for the stored capture, prefixed vgt_. Correlates this verify with its observation.

Endpoints

POST /v1/challenge endpoint path

Issue a challenge: mints a single-use nonce, a binding token, and an ALTCHA-style proof-of-work for the browser to solve. Issuing is free.

POST /v1/challenge/verify endpoint path

Verify on submit with the solved proof-of-work, the token, the nonce, and the captured voight telemetry. Returns the capture summary. Costs 1 credit.

/check/challenge widget path

The embeddable widget. Drop it where your CAPTCHA used to sit; it solves the proof-of-work in the background and captures interaction telemetry — no checkbox, no image grid, nothing the user has to do.

Common questions

How do I replace reCAPTCHA or Turnstile with an invisible CAPTCHA replacement? question path

Drop in the /check/challenge widget where your CAPTCHA used to sit, then call POST /v1/challenge to issue and POST /v1/challenge/verify on submit. There is no checkbox or image grid — the widget solves a background proof-of-work and captures interaction signals, so real users see nothing. It binds to your form_id and origin like a reCAPTCHA site key, but never interrupts a human.

Does Voight block bots today? question path

Not yet. Voight is in beta and capture-only: it observes, scores, and labels traffic to train the bot/human verdict, and every verify currently passes (passed is always true). Wire it in now to start building your own labeled corpus and to be ready to flip on enforcement once the verdict finishes calibrating. Until then, keep an existing gate if you need hard blocking.

What does a verify cost and what do I get back? question path

Each POST /v1/challenge/verify costs 1 credit. You get a compact JSON summary — pow_valid, token_valid, solve_ms, duration_ms, pointer and scroll counts, target_hit, and a challenge_id — plus the captured telemetry is recorded to your corpus. Issuing a challenge is free; you are billed per verify.

Next steps

Type to search…

↑↓ navigate open esc close