You hit submit on a form, the server replies "captcha validation required," and now your support inbox is filling up. The error message is generic on purpose — it covers about six different failure modes, and the right fix depends on which one you're hitting.
This post walks through every cause we've seen in production and how to tell them apart in your logs. We'll use CaptchaLa as the reference implementation, but the diagnostic flow applies to any modern CAPTCHA service.
The six real causes
| Cause | Frontend symptom | Backend log signal |
|---|---|---|
| Token never sent | Form posts without captcha_token field | Missing header / empty body field |
| Token expired (>120s old) | User left tab open | Validate API returns expired |
| Token already used | Double-submit / retry | Validate API returns already_used |
| Wrong site key | Embed rendered with prod key on staging | Validate returns site_mismatch |
| Network blocked the loader | Corporate firewall, ad-blocker | Loader 404 in browser console |
| Server-side validate skipped | Dev forgot the second hop | No log entry at all |
The first three are user-driven. The last three are integration bugs. Knowing which is happening to you is 90% of the fix.
How to diagnose in five minutes
Open one tab with the failing form, one tab with your server logs. Try to submit. You're looking for three things:
- Did the browser send a
captcha_tokenin the request? - Did your backend call
POST apiv1.captcha.la/v1/validate? - What did that validate call return?
If the answer to (1) is no, your widget didn't render or didn't bind to the form. Check the browser console for loader errors first.
If (1) is yes but (2) is no, your handler is short-circuiting before validation. Common cause: validation behind a feature flag that's off in staging.
If both are yes and (3) returns a specific error code, you've narrowed it to a real CAPTCHA-side issue.
A reference validation handler
// Node/Express
app.post('/signup', async (req, res) => {
const token = req.body.captcha_token;
if (!token) {
return res.status(400).json({ error: 'captcha_validation_required' });
}
const r = await fetch('https://apiv1.captcha.la/v1/validate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
app_secret: process.env.CAPTCHALA_SECRET,
remote_ip: req.ip,
}),
});
const result = await r.json();
if (!result.success) {
return res.status(400).json({
error: 'captcha_validation_required',
reason: result.error_code, // log this!
});
}
// proceed with signup
});Notice the reason: result.error_code field. That single line will save you hours of guessing later. Push it through to your error tracking and you can group failures by root cause.
The "I refresh and it works" trap
Users will tell you "it works after I refresh." This usually means tokens are expiring. Most providers give tokens a 60–120 second TTL. If your form has slow client-side validation (image upload, geolocation, multi-step flow), the token can age out before submit.
Two fixes:
- Reset the widget after long client operations: call
captchala.reset(widgetId)so a fresh token is issued on submit. - Issue the token on submit, not on page load. Modern SDKs support invisible/execute-on-submit modes that handle this automatically.
Don't silently retry
When validation fails, do not auto-resubmit. That turns one failed token into N failed tokens and trips your rate limits. Show the user a clear message, reset the widget, and let them confirm.
Logging tips
- Log the
error_codeand the first 8 chars of the token (never the full token). - Track validation latency separately from form latency; CAPTCHA being slow is a different alert than your DB being slow.
- Sample successful validations too — sudden drops in the success-rate chart are the earliest signal that something upstream broke.
Where CaptchaLa helps
The CaptchaLa dashboard exposes per-app validation metrics including error-code breakdowns, so you can see at a glance whether "validation required" errors are dominated by expired tokens, missing tokens, or replay attempts. That tells you whether to fix your frontend, your backend, or your fraud rules.
Takeaways
- "Validation required" is a surface error; always log the underlying error code.
- Most production cases are token expiry or skipped server-side validation, not bot attacks.
- Reset the widget on long flows and never auto-retry on failure.
- Per-error-code dashboards beat aggregate success rates for triage.