A CAPTCHA can look reassuring while protecting nothing. Follow the token from browser to backend, validate it before the sensitive action and make missing production configuration fail closed.

A bot challenge beside a button does not protect the button. It protects the request only when the backend receives its token, validates it with the provider and rejects the action if validation fails.
That sounds obvious, yet it is easy to wire a challenge to one step in a flow and visually place it beside another. The review technique is simple: follow the token, not the widget. For every protected action, prove which request carries the token and where the server makes its allow-or-reject decision.
A visitor controls their browser. They can call a public endpoint directly, omit a field or supply an invented string. Cloudflare states this plainly for Turnstile: server-side Siteverify validation is mandatory because a client-side widget alone does not protect a form.
The same principle applies to any CAPTCHA provider. The browser can collect a token and help the user recover when it expires. Only the server, which holds the provider secret, can establish that the token is valid for the action you are about to perform.
For example, an endpoint that verifies a one-time login code should check the anti-automation token before it asks the identity provider to test the code. Doing it afterwards lets automated traffic consume the resource you meant to protect.
Local test environments often omit external service keys. That can be appropriate if a clear test-only switch exists. Production is different. If protection depends on a secret, validate the environment at build or startup and stop the deployment when it is missing.
This is more than operational tidiness. An optional secret is an insecure default waiting for a configuration change. Treat the public site key and the server secret as separate requirements, then monitor both the provider result and the number of rejected requests.
Short-lived, single-use tokens are intentional. Turnstile tokens, for example, expire after five minutes and cannot be reused, so a server should return a recoverable response that prompts the client to obtain a fresh token. The provider documentation describes the expiry and duplicate-token behaviour.
Invisible challenges can preserve a quick code-entry flow, but they introduce a race: a fast paste may arrive before the token. Queue the intended submit in interface state and send it only after the token is available. Derive whether submission is possible from the current token, input and request state rather than maintaining duplicate flags that can fall out of sync.
The safest CAPTCHA review is brief and concrete: show the request, show the server validation call, show the rejection path and show a test that proves the action never ran. If any link in that chain is missing, the widget is decoration.
Ready when you are