Every major CAPTCHA system in 2026 has been beaten. reCAPTCHA v2 challenges are solved at industrial scale. v3 scores can be gamed with behavioral replay. Even audio challenges are defeated by speech-to-text APIs. The CAPTCHA arms race has a clear winner — and it isn't you.
The good news: you can stop bot signups without CAPTCHA, and without hurting conversion. The key is shifting from "prove you can solve a puzzle" to signals that are genuinely hard to fake.
Why CAPTCHA Is Not the Answer
CAPTCHA was invented to distinguish humans from computers. The problem: that distinction is harder to make every year. In 2026, here's what you're actually up against:
- Bot farms: Human-solve networks that charge $0.50–2.00 per 1,000 CAPTCHAs solved. Your $0.001-per-solve math doesn't work at scale.
- ML solvers: Computer vision models that achieve 97%+ accuracy on standard image challenges. Google's own reCAPTCHA has been beaten by open-source models.
- Behavioral replay: Headless browser scripts that mimic realistic mouse movements, scroll patterns, and timing to fool behavioral analysis.
CAPTCHA creates friction for legitimate users while stopping only unsophisticated bots. That's the worst trade-off possible for a signup form.
5 Methods to Stop Bot Signups Without CAPTCHA
Add hidden form fields that human users never see or fill. Any submission that populates these fields is from a bot. Zero friction, trivially implemented, effective against the dumbest tier of scrapers.
<!-- Hidden from real users via CSS -->
<div style="position:absolute;left:-9999px;opacity:0"
aria-hidden="true">
<label for="website">Leave blank</label>
<input type="text" name="website" id="website"
tabindex="-1" autocomplete="off">
</div>
// Server-side check
app.post('/signup', (req, res) => {
if (req.body.website) {
// Bot filled the honeypot — silently reject
return res.json({ success: true }); // fake success
}
// Continue with real registration logic
});
Limitation: Smart bots skip fields with display:none or suspicious names. Effective against 30–40% of bot traffic, not professional operations.
Limit signup attempts per IP, ASN, and email domain. Combined with blocklists of known bad actors (Tor exit nodes, datacenter IP ranges, disposable email domains), this blocks volume-based attacks at the network layer.
const rateLimit = require('express-rate-limit');
const signupLimiter = rateLimit({
windowMs: 60 * 60 * 1000, // 1 hour
max: 5, // 5 signups per IP per hour
keyGenerator: (req) => req.ip,
handler: (req, res) => {
res.status(429).json({ error: 'Too many requests' });
}
});
// Block datacenter IP ranges (Tor, AWS, DigitalOcean, etc.)
const DATACENTER_ASNS = ['AS14061', 'AS16509', 'AS14618'];
function isDatacenterIp(req) {
// Integrate with ip2asn or ipinfo.io
return false; // implement lookup here
}
app.post('/signup', signupLimiter, async (req, res) => {
if (isDatacenterIp(req)) {
return res.status(403).json({ error: 'Signup not available' });
}
// Continue registration
});
Limitation: Residential proxy networks (botnets) rotate IPs faster than rate limits can track. Stops volume attacks; doesn't stop targeted fake-account creation.
Block signups from disposable email services (Mailinator, Guerrilla Mail, 10minutemail, etc.) and flag addresses with suspicious patterns — high-entropy local parts, new domains, or known bulk-sender domains.
const DISPOSABLE_DOMAINS = new Set([
'mailinator.com', 'guerrillamail.com',
'tempmail.com', 'throwaway.email',
'10minutemail.com', 'yopmail.com',
// Add more from https://github.com/disposable/disposable-email-domains
]);
function isDisposableEmail(email) {
const domain = email.toLowerCase().split('@')[1];
if (DISPOSABLE_DOMAINS.has(domain)) return true;
// High-entropy local part check (random-looking strings)
const local = email.split('@')[0];
if (local.length > 20 && /^[a-z0-9]+$/.test(local)) return true;
return false;
}
app.post('/signup', (req, res) => {
if (isDisposableEmail(req.body.email)) {
return res.status(400).json({
error: 'Please use a permanent email address'
});
}
});
Limitation: Attackers use real Gmail/Outlook addresses in bulk-created accounts. Disposable detection catches lazy operators; not sophisticated campaigns.
Measure how long the form took to fill. Bots submit instantly; humans take 15–90 seconds. Track time-on-page, field interaction sequence, and whether JavaScript ran (bots often skip JS execution).
// Client-side: embed timing token in form
document.addEventListener('DOMContentLoaded', () => {
const startTime = Date.now();
const form = document.getElementById('signup-form');
form.addEventListener('submit', (e) => {
const elapsed = Date.now() - startTime;
document.getElementById('form-timing').value = elapsed;
});
});
// Hidden field in form
// <input type="hidden" id="form-timing" name="_timing" value="0">
// Server-side validation
app.post('/signup', (req, res) => {
const timing = parseInt(req.body._timing || '0', 10);
// Under 3 seconds = almost certainly a bot
if (timing < 3000) {
return res.status(400).json({ error: 'Please complete the form' });
}
// Over 1 hour = session replay attack or form pre-fill
if (timing > 3600000) {
return res.status(400).json({ error: 'Session expired. Please reload.' });
}
});
Limitation: Modern headless browsers simulate realistic timing. Still catches many scripted attacks, but not a dedicated adversary.
The only method that's technically unfakeable: require a 2-second face liveness check at signup. TrueLens verifies that a real human face is present in real-time — not a photo, not a replay, not a deepfake. No CAPTCHA, no puzzles, no friction spike. Bots cannot pass liveness because they don't have faces.
// After TrueLens verification completes, user gets a token
// Verify it server-side before creating the account
app.post('/signup', async (req, res) => {
const { verificationToken, email, password } = req.body;
if (!verificationToken) {
return res.status(400).json({ error: 'Verification required' });
}
// Verify with TrueLens API
const response = await fetch('https://api.truelens.io/v1/verify', {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.TRUELENS_API_KEY}`,
'Content-Type': 'application/json'
},
body: JSON.stringify({ token: verificationToken })
});
const { verified, liveness_score } = await response.json();
if (!verified || liveness_score < 0.95) {
return res.status(403).json({ error: 'Verification failed' });
}
// Create the account — confirmed human
await createUser({ email, password });
res.json({ success: true });
});
Full documentation at /docs. Free tier includes 50 verifications — enough to test in production before committing.
How to Stop Bot Signups Without CAPTCHA: Effectiveness Matrix
| Method | Bot Block Rate | User Friction | Setup Time | Cost |
|---|---|---|---|---|
| Honeypot Fields | Low (30–40%) | None | 30 min | Free |
| Rate Limiting + IP Rep | Medium (60–70%) | None | 2–4 hours | ~$10–20/mo |
| Disposable Email Block | Medium (50–60%) | Minimal | 1–2 hours | Free |
| Behavioral Timing | Medium (65–75%) | None | 2–3 hours | Free |
| TrueLens Face Liveness | Very High (99%+) | 2 seconds | 30–60 min | From $19/mo |
The Layered Approach
For most applications, the right answer is layers — not a single silver bullet. Here's a recommended stack:
- Honeypot + rate limiting: Free, trivial, catches 50–60% of bot traffic at the network layer.
- Disposable email detection: Adds another filter for low-effort attacks.
- Behavioral timing: Catches scripted form submissions that passed the above.
- Face liveness (high-value signups): For any form where a fake account causes real damage — trial abuse, fraud, spam, fake reviews — add TrueLens at the signup step.
Want to go deeper? See our article on how to stop bot signups for a broader analysis, or check the reCAPTCHA alternatives comparison for a side-by-side of available tools.