FCaptcha
Self-hosted, MIT-licensed CAPTCHA service that combines SHA-256 proof-of-work with behavioural and environmental bot detection (mouse trajectory, micro-tremor, click precision, WebDriver probes, typing rhythm). Offered in interactive-checkbox and invisible zero-click modes.
Upstream project: github.com/WebDecoy/FCaptcha. The faucet integrates FCaptcha as a thin driver — we never fork the detection code. Deploy FCaptcha as a sidecar, point the faucet at it.
How it works
- The ClaimUI loads
/fcaptcha.jsfrom the operator's FCaptcha service and renders the widget. - The widget collects behavioural signals, solves the PoW challenge, and exchanges them at FCaptcha's
/api/verifyendpoint for a verification token. - The client includes the token in the claim request as
captchaToken. - The faucet server calls FCaptcha's
/api/token/verifywith{ token, secret }and reads{ valid, score, … }. - If
validis true, the claim proceeds; otherwise it's denied.
All traffic stays between the operator's faucet, the operator's FCaptcha service, and the end user's browser. No third-party calls.
Configuration
| Env var | Default | Description |
|---|---|---|
FAUCET_FCAPTCHA_URL | (unset = disabled) | Base URL of the FCaptcha service, e.g. http://fcaptcha:3000 |
FAUCET_FCAPTCHA_SITE_KEY | (unset) | Public site key; passed to the browser widget |
FAUCET_FCAPTCHA_SECRET | (unset) | Server-side secret for token verification |
All three are required to enable the layer. If any is missing the driver isn't registered and the fcaptcha entry in /v1/config.abuseLayers stays false.
Deploy FCaptcha alongside the faucet
A compose overlay is provided:
cd deploy/compose
docker compose -f docker-compose.yml -f fcaptcha.yml up -dSet FCAPTCHA_SECRET, FAUCET_FCAPTCHA_URL, FAUCET_FCAPTCHA_SITE_KEY, and FAUCET_FCAPTCHA_SECRET in .env before bringing the stack up. See the upstream FCaptcha README for site-key generation.
Decision logic
- No token provided:
denywith reason "missing captcha token". /api/token/verifyreturnsvalid: false:denywith the upstream error surfaced in signals.- Verify succeeds:
allowusing FCaptcha's returnedscore(clamped to [0, 1], 0 = clean, 1 = abusive).
Trade-offs
- Self-hosted, no third-party calls — FCaptcha runs as a sidecar container you own.
- Richer than pure-PoW alternatives (hashcash, ALTCHA, Cap) — behavioural + environmental signals alongside the proof-of-work backbone.
- Requires running a second service — one container, optional Redis for distributed state. For single-node deployments, the in-memory store is sufficient.
- Mutually exclusive with Turnstile / hCaptcha — the claim UI renders one captcha widget at a time. Pick the provider that matches your threat model.
Reuse, not reinvent
FCaptcha's behavioural scoring, PoW issuance, and token verification all live in its own service. The faucet's packages/abuse-fcaptcha driver is ~60 lines — it's an HTTP client, nothing more. If you want to tune detection (difficulty, signal weights, datacenter-IP heuristics), configure FCaptcha itself. If you find a gap, contribute upstream.
See also
- FCaptcha upstream
@faucet/abuse-fcaptcha— driver package- Turnstile, hCaptcha, Hashcash — sibling layers