Notes
Two reCAPTCHA keys, and what breaks when they swap places
One key is meant to be read by anyone, the other by nobody. Google's error codes say which half is wrong, so guessing is never necessary.
5 min read
Google hands you two strings when you register a site, and the difference between them decides whether you have working protection or decoration. The reCAPTCHA site key belongs in your page, where anybody can read it. But the secret key belongs on your server and nowhere else. So when a checkout guard reports itself as active and stops nothing, the keys are the first place to look.
And this is not an exotic failure. It is the ordinary one, because both keys are long, both look alike, and pasting them into the wrong boxes still produces a page that renders perfectly.
What each key is for
The reCAPTCHA site key is public by design. Your page loads Google's script with it, the script watches the visit, and it hands back a token when your form submits. Anyone can read that key by viewing source, and that is fine.
Meanwhile the secret key never leaves your server. Your server posts it, along with the token, to Google's verification endpoint. Google's reference lists what that request takes: secret, described as "The shared key between your site and reCAPTCHA", response, "The user response token provided by the reCAPTCHA client-side integration on your site", and an optional remoteip.
Swap the two and nothing visibly breaks. The widget still loads. And the form still submits. But verification fails on every request, so how your plugin handles that failure decides whether you have blocked everybody or nobody.
The reCAPTCHA site key is tied to your domains
A key pair is not portable. Google's documentation puts it plainly: "A reCAPTCHA key is normally tied to a set of individual domains or package names." And the scope runs wider than a single hostname, because "the API key pair is unique to the domains and first-level subdomains that you specify."
So registering example.com covers www.example.com too. Although that sounds generous, it does not stretch to a staging site on a different domain, which is why a key copied from production starts failing the moment it arrives there.
There is an option to switch that check off. Still, Google is unusually direct about the cost: "Turning off this protection by itself poses a large security risk - your key could be taken and used by anyone, as there are no restrictions." If you do turn it off, the documentation names what you owe in return, which is to "check the hostname/package field and reject any solutions that are coming from unexpected sources."
Read the error codes rather than guessing
Because Google's verification response says which half of the pair is wrong, a stab in the dark is never necessary. The reference lists every code, and four of them cover almost every configuration mistake.
missing-input-secret: "The secret parameter is missing." So your server sent no secret at all.invalid-input-secret: "The secret parameter is invalid or malformed." That points at the secret key field.missing-input-response: "The response parameter is missing." No token arrived, so look at the front end.invalid-input-response: "The response parameter is invalid or malformed." Either the token is wrong, or it does not belong to this key pair.
One more is worth knowing, since it is not a configuration fault at all. timeout-or-duplicate means "The response is no longer valid: either is too old or has been used previously", which is the signature of a token that expired before your server got to it.
Check the hostname and the action too
A successful verification returns more than a pass mark. For v3 the response carries success, score, action, challenge_ts and hostname, and two of those deserve a look every time.
First, the hostname field names "the hostname of the site where the reCAPTCHA was solved". Compare it with your own domain, and a token minted elsewhere stops being useful to anybody.
Then there is the action name. Google's guidance is explicit: "Importantly, when you verify the reCAPTCHA response, you should verify that the action name is the name you expect." Without that check, a token earned on a low-risk page such as a contact form will happily unlock your checkout.
A short checklist
- First, confirm the reCAPTCHA site key in your page source matches the key registered for this domain.
- Then confirm the secret key sits server side, and never gets printed into the page.
- Next, check the registered domain list covers every hostname you serve, including staging.
- Also log the
error-codesarray on failure, because it names the problem for you. - Finally, verify
hostnameandactionon success, rather thansuccessalone.
Once the keys are right, the score becomes worth arguing about. Google suggests you "can use a threshold of 0.5" as a starting point, though choosing a threshold on your own traffic is a separate exercise.
Where Checkout Bouncer fits
Checkout Bouncer takes a reCAPTCHA site key and secret key, scores the WooCommerce checkout with Google reCAPTCHA v3, and checks the result on the request that places the order. The plugin is free, GPL and listed on WordPress.org.
Its boundaries are worth stating. Because it uses reCAPTCHA v3 only, a v2 key pair will not work, and neither will another provider. The plugin guards the WooCommerce checkout and nothing else, which leaves login, registration, comments and contact forms to something else. Nor is it a firewall or a malware scanner.
Google's own pages are the authority on the rest: the v3 overview, the verification reference and the domain validation page. And if you want to know which routes into your checkout currently answer, the free scanner takes about two minutes.