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
-
Issue a challenge
Call
POST /v1/challengeto mint a single-use nonce, a binding token, and an ALTCHA-style proof-of-work. The embeddable/check/challengewidget solves the proof-of-work in the background and captures interaction telemetry — no checkbox, no image grid, nothing the user has to do. -
Verify on submit
On form submit, call
POST /v1/challenge/verifywith 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. -
Read the capture summary
verifyreturns a compact summary —pow_valid,token_valid, solve time, interaction counts, and achallenge_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
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.
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:
{
"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.
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.
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.
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.
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.