The Problem with reCAPTCHA
Every time you load a page with Google reCAPTCHA, a 300+ KB JavaScript bundle from google.com runs on your site. It tracks mouse movements, browser fingerprint, and even your Google account state. For EU businesses, this became a compliance nightmare after Schrems II and Google's April 2, 2026 reclassification of reCAPTCHA from data controller to data processor. Now you need a signed DPA, a legal basis in your privacy policy, and processes for data subject requests. The data still goes to US servers.
What Altcha Does Instead
Altcha replaces the "score-the-user" model with proof-of-work (PoW). The server generates a random salt, nonce, and target prefix, signs them with an HMAC key, and sends them to the browser. A Web Worker iterates a counter, combining it with the nonce and running PBKDF2-SHA256 until the derived key starts with the target prefix. On a modern laptop, this takes about one second. A bot farm submitting thousands of forms per second suddenly needs real CPU per submission.
No third-party calls. The challenge endpoint and widget JavaScript serve from your own domain. The HMAC key never leaves your server. No sub-processor to add to a DPA.
Integration Gotchas
1. Don't Cache the Challenge
If you use a CDN (the article mentions Bunny CDN), you must prevent caching of the challenge endpoint. Otherwise, two users get the same signed challenge, the first submission consumes it, and the second user gets a 400 error. The fix is a Cache-Control: no-store header:
@router.get("/altcha/challenge")
async def get_challenge():
challenge = mint_challenge(hmac_key, cost=5000)
return Response(
content=challenge.json(),
media_type="application/json",
headers={"Cache-Control": "no-store"},
)
2. Replay Protection Needs a Real Store
A replay attack captures a valid submission and sends it again. Altcha libraries for Django and .NET ship with replay stores, but for a custom FastAPI integration you need one. Use Redis SETNX with a TTL longer than the challenge expiry:
is_first_use = await redis.set(
f"altcha:used:{solution_hash}",
"1",
nx=True,
ex=600, # 10 minutes
)
if not is_first_use:
raise HTTPException(400, "captcha already used")
An in-memory dict won't survive a deploy or scale across workers. Use Redis or Postgres.
Comparison with Alternatives
| Feature | Google reCAPTCHA v3 | hCaptcha | Cloudflare Turnstile | Altcha (self-hosted) |
|---|---|---|---|---|
| Data leaves your origin | Yes (google.com) | Yes (hcaptcha.com) | Yes (cloudflare.com) | No |
| Behavior tracking | Heavy | Moderate | Light | None |
| JS bundle (gzipped) | 300+ KB | 250+ KB | 85+ KB | 34 KB, your origin |
| User puzzle | Often | Often | Rarely | Never |
| GDPR posture | US sub-processor | US sub-processor | US sub-processor | None, it's your server |
| Self-hostable | No | No | No | Yes |
| Cost | Free + paid tiers | Free + paid tiers | Free | Free (your CPU) |
The "data leaves your origin" row is the whole point.
When Altcha Is the Wrong Call
- Sophisticated bot operators: A bot farm with cloud capacity can absorb PoW costs. Altcha supports Argon2id and Scrypt as memory-hard alternatives to PBKDF2, which resist GPU/ASIC acceleration, but it's not a silver bullet. For high-traffic surfaces under active attack, you still want behavioral ML.
- Very low-end devices: A one-second solve on a modern laptop can become four seconds on a five-year-old budget phone. Benchmark before shipping if your audience is consumer mobile in developing markets.
- No durable key-value store: A captcha without single-use enforcement is theater. If your hosting tier can't run Redis or equivalent, fix that first.
FAQ
- Is Altcha actually free? Yes, MIT-licensed open source. The cost is negligible server CPU and one second of client compute per submission.
- Do I need a separate Altcha server? No. Add two endpoints to your existing backend: one to mint challenges, one to verify.
- Does it work without JavaScript? The default flow needs JS, but Altcha supports a server-side mode for accessibility.
- Will switching break form analytics? Only if you used reCAPTCHA's score as a continuous signal. If it's binary pass/fail, the switch is transparent.
- Can I use it on a static site? You need somewhere to mint and verify challenges. A serverless function works.
What to Do Now
If you run a low-to-moderate traffic form (signup, contact, comments, login) and you care about GDPR or just want to reduce third-party dependencies, self-host Altcha. Add the challenge endpoint, set Cache-Control: no-store, and wire up Redis for replay protection. It's a couple hundred lines of code and one HMAC environment variable.
The article also recommends reading the Vercel April 2026 breach checklist for other US service dependencies. If you want a deploy platform where this kind of choice is the default, the author mentions orkestr Starter (free, EU-hosted).
