Notes
Why your checkout captcha turns away real customers
Google gives a token two minutes and one verification. A checkout that takes longer than that fails honest shoppers, and the error code says so.
7 min read
A customer writes to say your checkout would not take their order. They tried three times. Nothing was wrong with the card, and nothing was wrong with the address. You place a test order yourself, it goes through in twenty seconds, and the ticket joins the pile marked "cannot reproduce". A reCAPTCHA token expired somewhere in their attempt, and nothing on the page said so.
If your store scores checkouts with reCAPTCHA v3, there is a specific and very ordinary reason both of those things can be true at once. A reCAPTCHA token is valid for two minutes and can be verified only once. Your test order beat the clock. Your customer did not.
What the token actually is
reCAPTCHA v3 asks the shopper nothing. JavaScript in the browser mints a response token, and your server posts that token to Google's verification endpoint with your secret key. Google answers with a score, where in Google's words "1.0 is very likely a good interaction, 0.0 is very likely a bot". The reply also carries the action name, a timestamp for the challenge load, and the hostname of the site where the shopper solved it.
The token is the perishable part. Google's reCAPTCHA v3 documentation states plainly that "reCAPTCHA tokens expire after two minutes". The verification guide adds the second constraint: "Each reCAPTCHA user response token is valid for two minutes, and can only be verified once to prevent replay attacks". Two minutes, one use. Everything below follows from that sentence.
Two minutes is not long at a checkout
Think about how long your checkout really takes when the person filling it in is not you. They type an address they have not typed before. Shipping recalculates. They tab back to fix a postcode. The card is in a coat in the hall. A saved-card field loads inside an iframe. A bank pushes them into an authentication step in an app on their phone.
If the token dates from the page load, the clock started while the basket was still on screen. This is precisely what Google warns against: "If you're protecting an action with reCAPTCHA, make sure to call execute when the user takes the action rather than on page load." Minting at submit time is not a refinement. It is the difference between a working checkout and one that turns away careful shoppers while waving through anything that fills in a form in five seconds.
There is a second version of the same fault that is harder to see. The token may be minted at submit while your validation runs later in the sequence, after a redirect to a payment page or after the shopper returns from an authentication step. By the time anything checks it, the token can be well past two minutes old.
The error code that says a reCAPTCHA token expired
Google's verification response returns error codes, and one of them describes both failure modes at once. Google documents the code timeout-or-duplicate as "The response is no longer valid: either is too old or has been used previously".
Too old is the two-minute clock, and nothing on the page says so. The shopper never learns that a reCAPTCHA token expired between filling the form in and pressing the button. Used previously is subtler, and it is where retries come in. Say a shopper submits, something else on the form fails validation, and the page posts again carrying the token it already sent. That second verification is a replay, and Google refuses it. The customer sees a failure with no explanation, tries again, and if the reCAPTCHA token expired earlier rides along once more, fails again. From where they are sitting, the store simply does not work.
Two other codes are worth telling apart. The code missing-input-response means "The response parameter is missing", the signature of a page where nothing minted a token at all. The code invalid-input-response means "The response parameter is invalid or malformed", which usually means the token did not come from the key you are verifying against.
When no token ever arrives
A reCAPTCHA token expired in the browser is one bug; a token that never existed is another, and it has its own short list of causes. The reCAPTCHA script may never have loaded on the page the shopper used. That is common on stores that have more than one page able to render a checkout form. A browser extension or a strict privacy setting can block the script outright. A key issued for reCAPTCHA v2 will not work in a v3 integration.
Domain restrictions produce a version of this that only bites on staging. Every reCAPTCHA key belongs to a set of domains, and that validation is on by default. A key registered for your live site will not work on a test domain. Google is blunt about turning the protection off: doing so "poses a large security risk". If you do, you must check the hostname field yourself and reject solutions arriving from unexpected sources. Since the verification response reports the hostname the shopper solved it on, that check costs one line and closes the hole.
What your server does when Google does not answer
Verification is an outbound HTTP request from your site to Google, and outbound requests sometimes hang. That leaves your code with a decision nobody enjoys. Fail closed, and a network problem between your host and Google becomes an outage on your checkout. Fail open, and for the duration of that problem every submission goes through unscored.
There is no universally correct answer, and anyone who says otherwise is selling something. What matters is that the choice is deliberate, and that you know which way your integration behaves today. Write the fallback to a log rather than leaving it as a silent guess.
How to tell these apart on your own store
Each fault above leaves a different trace, and the repair follows the trace rather than the symptom.
- Record the error codes the verification endpoint returns, not just a pass or a fail. Three failures that all read
timeout-or-duplicatemean a reCAPTCHA token expired, or the page replayed one. Either way that is a timing bug rather than a bot problem. - Separate "no token" from "token rejected" from "token scored below your threshold". Those are three different repairs, and only the last is about bots.
- Check the action name on the response. Google advises that "when you verify the reCAPTCHA response, you should verify that the action name is the name you expect". That check also catches tokens minted by an unrelated form elsewhere on your site.
- Compare the hostname on the response against your own domain, especially if you have ever relaxed domain validation.
- Time a real checkout slowly, on a phone, with a card that triggers your bank's authentication step. A design that cannot survive that will not survive your customers.
Do this before you touch the threshold, and read how to choose that threshold from your own traffic before you move it. Lowering a threshold to fix what is really an expiry bug leaves you with both problems: the real customers still fail, and the automated attempts now pass.
Where Checkout Bouncer fits
Checkout Bouncer is a free, GPL plugin on WordPress.org that scores WooCommerce checkout submissions with Google reCAPTCHA v3, the only engine it uses. It is a free, GPL plugin on WordPress.org. It records the score, the verdict, the surface and the reason for each submission, so an order rejected because a reCAPTCHA token expired traces back to that cause rather than a shrug. The threshold carries an explicit fail-open or fail-closed setting instead of an unstated default. It is checkout only: not login, not registration, not contact forms, and it is neither a firewall nor a malware scanner. Pro remains a plan rather than a product, so there is nothing to buy. If you have built your own integration, the checks above apply to it just the same.