Self-hosted service
WebDecoy/FCaptcha avatar
WebDecoy/FCaptcha

FCaptcha keeps the siteverify contract and drops the puzzle, and admits its proof of work is not a cost

Self-hosted, invisible CAPTCHA that detects AI agents driving real browsers. GDPR, WCAG 2.2 AA, and EAA: no cookies, no third-party data sharing, and no visual or audio puzzle. With a false-positive budget enforced in CI. Proof of work, drop-in for Turnstile and reCAPTCHA.

226 stars24 forksJavaScriptMIT

At a glance

What is it?
A self-hosted CAPTCHA server that speaks the same verification contract as Turnstile and reCAPTCHA, scores requests across a documented set of categories, and enforces its own accessibility claims as a CI budget over fourteen human personas. Its proof of work is described by the project as a liveness gate rather than a cost function.
Who is it for?
Use FCaptcha if you are self-hosting, need the verification call to stay the same shape as the hosted services, and have a real accessibility review process, because the WCAG position is only as good as the CI budget that enforces it. Do not adopt it expecting a free spam wall, since the project itself says the proof of work is a liveness gate rather than a cost function, and do not read the absence of a puzzle as the absence of friction.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 1 day ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 22, 2026, and from our analysis. They are not legal advice.

From daily snapshots

Star history

  • WebDecoy/FCaptcha226 ★ · +35
Star history
Repository2026-08-232026-10-22
WebDecoy/FCaptcha191226

35 Stars in the last 60 days, measured from the daily GitHub snapshot. Compare with other projects

Editorial analysis

The verification call is unchanged, so moving across is a base URL change

The migration story is the strongest thing the project has, and it is a boring one on purpose. FCaptcha serves the same `siteverify` contract on the same paths as Turnstile, reCAPTCHA and hCaptcha, so moving an existing backend is a base URL change rather than a code change. There is a second layer that a same-shaped endpoint alone would not give you: tokens carry a signed `hostname` and `action` that the application can check. A signed hostname means a token minted for one site is not accepted for another, and a signed action means a token issued for a sign-in form is not reusable on, say, a password reset, both of which are checks a bare boolean verification does not perform. The widget itself has two shapes: checkbox mode like Turnstile and reCAPTCHA v2, and invisible mode like reCAPTCHA v3, so the client-side integration is also a substitution rather than a rewrite.

Proof of work is described as a liveness gate, and the project says so itself

The proof of work is server-verified SHA-256 hashcash with 256-bit HMAC signing, per-challenge nonces, and a signal commitment that binds the challenge to the signals collected for it. The last part is the interesting one: the commitment ties the work to the request, so a solution cannot be lifted from one challenge and replayed against a different set of signals. What the project does not do is oversell it. The feature list calls it a liveness and timing gate rather than a cost function, and it links to a section on what the proof of work does and does not buy you. That distinction is the one to hold on to. A liveness gate asks whether a client can do work in real time and respond plausibly, which filters the naive scripts. It does not make an attack expensive, because an attacker with a budget pays a budget and gets a proportionally better attack. If your threat is a funded operation rather than a script, the scoring categories and the behavioural signals carry the weight, not the hashcash.

State is process-local until you set REDIS_URL, and the server fails closed without it

The default deployment is a single process, and that is stated rather than implied. Setting `REDIS_URL` is what shares state across replicas, and the list of what it shares is long: proof of work challenges, token replay protection, Siteverify idempotency, rate limits, suspicion state, fingerprint cardinality, and the guards for site-key rotation. With that in place the Go, Node and Python servers can each run more than one instance, and challenge and token consumption becomes atomic, which is what stops a replay from racing a second verifier. Two failure modes are handled, and both are the safe direction. A server refuses to start if the Redis it was configured with is unavailable, so a misconfigured deployment fails at boot rather than at the first attack. And if Redis becomes unavailable later, the server fails closed rather than reverting to process-local state, which would silently give every replica its own replay cache and its own rate limits.

Four deployment routes, and the chart will not install without a signing key

A prebuilt public image is published for Linux AMD64 and ARM64, with no GitHub login and no source build needed, and the quick local start binds to loopback only:

bash
docker run -d --name fcaptcha -p 127.0.0.1:3000:3000 -e FCAPTCHA_SECRET="$(openssl rand -hex 32)" ghcr.io/webdecoy/fcaptcha:latest

That gives an API under `/api/*`, the client script at `/fcaptcha.js`, and a demo page at `/demo/`. Use `:latest` to try the current image, or pin a release tag such as `:1.42.0`, or a digest, for a reproducible deployment. Compose and a Helm chart follow, and the chart is the strict one: it refuses to install without a signing key, which is the same key the tokens are signed with, so a missing key is a configuration error rather than a runtime surprise. The chart documentation also covers `trustedProxies`, described as mattering more behind an ingress controller than anywhere else, which connects directly to the fingerprinting described later. Render and Railway one-click templates are linked from the page as well.

The accessibility claim is a build gate over fourteen human personas, not an assertion

There is no visual or audio puzzle, so there is nothing to fail, and the project then does the harder part and tests the result. The widget's WCAG 2.2 AA position is verified in CI across contrast, focus visibility that survives a host stylesheet, target size, reflow, and reduced motion. Alongside that sits a benchmark panel of screen-reader, keyboard-only, motor-tremor and elderly personas whose false-positive budget fails the build when exceeded. The benchmark harness captures real browser traces for fourteen human personas, including keyboard-only, screen-reader, touch, tremor, elderly, DevTools-open and privacy-extension users, and reports a per-signal false-positive budget. The claim that ties it together is that every threshold in the behavioural layer was derived from that harness rather than chosen by hand. This is the part of the project most worth reading, because a detector tuned to real traces is a different thing from one tuned to intuition, and the fact that the budget can fail CI is what makes the accessibility statement falsifiable.

Forty plus behavioural signals, and the exemptions are part of the design

The behavioural layer is large, and the signals are the kind that come from pointer input rather than from headers. There are more than forty, including micro-tremor, velocity and acceleration curves, trajectory analysis, coalesced pointer events, and teleport-click detection, where a click arrives with no movement leading to it. Input forensics add typing cadence and modality, meaning whether a value arrived through keystrokes, through a paste, or by being assigned into the DOM directly, plus scroll morphology and platform contradictions such as a paste event arriving from a browser claiming to be macOS. Mobile is handled separately with touch kinematics and passive device-sensor entropy, and there are accessibility exemptions for keyboard-only and touch users. Scoring itself is described as transparent and evidence-accumulating across roughly twelve categories and fully auditable, which is a claim about being able to read the reasoning after the fact rather than about the score being right.

Web Bot Auth is verified against published keys, and served as its own category

Declared agents and verified agents are treated differently, and the difference is enforced with cryptography rather than with a list. Self-declaring agents including ClaudeBot, GPTBot, ChatGPT-User, PerplexityBot and Bytespider are flagged, and Web Bot Auth signed requests under RFC 9421 are cryptographically verified against the agent's published key directory. The verified case is surfaced as a distinct category rather than folded into a score, which means your application can allow polite, verified agents and block the rest, a distinction a single threshold cannot express. TLS fingerprinting works the same way. JA3 is client-supplied and therefore spoofable, while JA4 is described as un-spoofable but only when it comes from a trusted reverse proxy. That conditional is the whole ballgame: JA4 is only trustworthy when the proxy that reports it is one you control, which is the same reason the chart tells you to configure `trustedProxies` before anything else.

Nothing is written to disk, and the retention windows are written down

The privacy claims are structural rather than promissory. There are no cookies, no third-party data sharing and no cross-site tracking, and nothing is written to disk, which means there is no data directory to secure, no log to rotate and nothing to delete after an incident. The retention claim is backed by enumeration rather than by assertion, with every retention window set out in `COMPLIANCE.md`, and the positioning is stated as GDPR, WCAG 2.2 AA and EAA. Self-hosting is the default route, and if you would rather not host the widget yourself it is available on npm and CDN with Subresource Integrity, so a compromised CDN copy fails to load rather than executing. The same privacy position extends to identity: a self-hosted verification service that keeps nothing has no cross-request record to correlate, which is the point of the design and also the reason you cannot use it to build a behavioural profile of your own users later.

Editorial conclusion

Use FCaptcha if you are self-hosting, need the verification call to stay the same shape as the hosted services, and have a real accessibility review process, because the WCAG position is only as good as the CI budget that enforces it. Do not adopt it expecting a free spam wall, since the project itself says the proof of work is a liveness gate rather than a cost function, and do not read the absence of a puzzle as the absence of friction. Before deploying, decide whether you need one process or several, because everything shared lives behind REDIS_URL and the server fails closed without it, set the trusted proxies setting correctly if anything sits behind an ingress controller, and read COMPLIANCE.md for the retention windows rather than assuming the privacy claims are self-describing.

Frequently asked questions

Is FCaptcha a drop-in replacement for reCAPTCHA and Turnstile?

Yes. It serves the same siteverify contract on the same paths, so migrating an existing backend is a base URL change, and the tokens carry a signed hostname and action your application can check. The widget offers checkbox mode and invisible mode.

Does FCaptcha set cookies or write anything to disk?

No. There are no cookies, no third-party data sharing and no cross-site tracking, and nothing is written to disk. Every retention window is enumerated in COMPLIANCE.md.

Do I need Redis to run more than one FCaptcha server?

Yes, for shared state. FCaptcha state is process-local by default, and setting REDIS_URL shares proof of work challenges, token replay protection, Siteverify idempotency, rate limits and rotation guards. The server refuses to start if configured Redis is unavailable and fails closed rather than reverting to process-local state.

Does the proof of work in FCaptcha make attacks expensive?

No, not on its own. The project describes it as a liveness and timing gate rather than a cost function. It is server-verified SHA-256 hashcash with 256-bit HMAC signing, per-challenge nonces and a signal commitment binding the challenge to the collected signals.

Official sources

  1. Official documentation
  2. Official README
  3. Project repository
  4. Release notes
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/webdecoy-fcaptcha.svg)](https://hysenlabs.com/projects/webdecoy-fcaptcha)

Star history chart for your README

markdown
[![Star history](https://hysenlabs.com/chart/webdecoy-fcaptcha.svg)](https://hysenlabs.com/projects/webdecoy-fcaptcha)