WooCommerce Stripe Checkout Blocked by 3D Secure (SCA)
A customer types their card number, the 3D Secure popup flashes for a second, and then checkout just... fails. No charge, no order, sometimes not even an error message they can read. If you run a WooCommerce store on Stripe, this is one of the most common "why did we lose a sale" tickets, and it almost always traces back to Strong Customer Authentication (SCA) not completing properly.
What's actually happening
Since 2019, Stripe requires 3D Secure (3DS2) authentication for most European cards and an increasing number of Indian and international cards too. Instead of just charging a card, Stripe has to open a small verification frame — the bank sends an OTP or a biometric prompt, the customer confirms, and only then does the charge go through. That verification step is where things break.
When you see "This transaction requires authentication" or the payment just spins and times out, it means the authentication frame either never loaded, never got a response back, or WooCommerce never heard that authentication succeeded. The money is usually never captured in these cases — unlike a webhook-delay problem, this is a failure before the charge completes.
Cause 1: You're still on the old Charges API
Symptom: Every card that needs 3DS fails outright, or you see decline code authentication_required in the Stripe Dashboard under Payments → a specific failed payment.
Cause: Older versions of the official "WooCommerce Stripe Gateway" plugin (pre-6.0) used the legacy Charges API, which has no way to trigger and wait for a 3DS challenge. If a card issuer requires authentication, the charge just fails.
Fix:
- In WP Admin, go to Plugins and update "WooCommerce Stripe Payment Gateway" to the latest version.
- Open WooCommerce → Settings → Payments → Stripe and confirm "Enable Payment Request Buttons" and SCA support are both on — current versions default to the Payment Intents API, which handles 3DS natively.
- If you're on a custom or third-party Stripe integration, check with the plugin author whether it uses
PaymentIntentsor the oldChargesendpoint. If it's Charges-only, budget time to migrate — Stripe has started rejecting more Charges-API transactions outright for regulated card networks.
Cause 2: The 3DS iframe is being blocked client-side
Symptom: The popup opens but stays blank, or it never opens at all, and the browser console shows something like Refused to display in a frame because it set 'X-Frame-Options' or a third-party cookie warning.
Cause: The 3DS challenge itself runs inside an iframe loaded from your bank's domain. Two common blockers:
- A security header on your site (usually
Content-Security-PolicyorX-Frame-Optionsset too strictly by a security plugin) is interfering with how the parent page embeds Stripe's iframe. - Browsers blocking third-party cookies (Safari ITP, Chrome's phase-out) break the session state 3DS relies on if your checkout page loads any part of it over plain HTTP instead of HTTPS.
Fix:
- Open your checkout page in an incognito window with all extensions disabled. If it works there, an ad blocker or privacy extension on the customer's end is the real culprit — not your server. Note it in your support macro so you're not chasing a server bug that doesn't exist.
- Check Site Tools → Security Headers (or your
.htaccess) for a CSP rule and make sureframe-srcandconnect-srcallowhttps://js.stripe.comandhttps://hooks.stripe.com:
Header set Content-Security-Policy "frame-src 'self' https://js.stripe.com https://hooks.stripe.com; connect-src 'self' https://api.stripe.com;"
- Confirm your entire checkout flow — cart, checkout, and any redirect URL — loads over HTTPS with no mixed content. One HTTP image or script on the checkout page is enough to break the secure context 3DS needs.
Cause 3: Server-side TLS is too old to talk to Stripe
Symptom: Payments fail before the 3DS prompt even shows, and your PHP error log has a cURL error like SSL certificate problem or error:14094410.
Cause: Stripe requires TLS 1.2+ for all API calls. On older cPanel servers still running PHP 7.2 or earlier with an outdated OpenSSL/cURL build, the handshake to Stripe's API fails before your server can even create the PaymentIntent.
Fix:
- In cPanel, go to Software → MultiPHP Manager and bump the domain to PHP 8.1 or newer (WooCommerce 8+ requires PHP 7.4 minimum, but current Stripe SDKs are much happier on 8.x).
- Confirm cURL's SSL backend is current:
php -i | grep -i "SSL Version"from SSH should show OpenSSL 1.1.1 or later. - If you're stuck on an old shared plan that can't be upgraded, that's usually the moment to move to a VPS where you control the PHP/OpenSSL stack directly.
Cause 4: The order goes "Pending" after a successful 3DS challenge
Symptom: The customer completes the OTP, sees a "payment successful" message from their bank app, but WooCommerce still shows the order as Pending or Failed.
Cause: This is a webhook problem, not an authentication one. After 3DS completes, Stripe fires a payment_intent.succeeded event asynchronously. If your webhook endpoint isn't registered, or a firewall/WAF rule is blocking Stripe's servers, WooCommerce never updates the order.
Fix:
- In the Stripe Dashboard → Developers → Webhooks, confirm an endpoint exists pointing at
https://yourdomain.com/?wc-api=wc_stripeand thatpayment_intent.succeededandpayment_intent.payment_failedare subscribed. - Click into "Recent events" for that endpoint and check the response code your server returned. A 403 usually means ModSecurity or a security plugin blocked the POST — whitelist the specific rule ID rather than disabling the module entirely.
- As a manual recovery, you can re-sync a stuck order from WooCommerce → Orders, opening the order and clicking "Refresh Payment Status" if your gateway version supports it, or checking the PaymentIntent ID directly in Stripe against the order.
Quick diagnosis table
| What you see | Most likely cause |
|---|---|
| "This transaction requires authentication" instantly, no popup | Old Charges-API-only Stripe plugin |
| 3DS popup blank or never loads | CSP/X-Frame-Options blocking the iframe, or mixed content |
| Fails before any prompt, cURL SSL error in logs | Outdated PHP/OpenSSL on the server |
| Bank confirms success, order stays Pending | Missing or blocked Stripe webhook |
| Only fails for some customers, works for you | Customer-side ad blocker/extension — test in incognito first |
Prevention
- Keep the WooCommerce Stripe Gateway plugin on auto-update, or at least check it monthly — SCA rules and card-network requirements change more often than most store owners expect.
- Run a real test transaction with a Stripe test card that requires 3DS after every plugin or PHP version update, not just a normal card.
- Keep your webhook endpoint's "Recent events" tab bookmarked — it's the fastest way to tell a card-decline problem from a your-server-never-heard-back problem.
- If you're still on shared hosting with an old PHP version, plan the upgrade before a payment processor forces your hand mid-sale-season.
Frequently asked questions
Why does Stripe suddenly require 3D Secure when it didn't before?
Card networks and regulators (especially under PSD2 in Europe, and increasingly RBI rules in India) have expanded which transactions require Strong Customer Authentication. Stripe applies this automatically based on the card issuer and region, so a card that worked fine last year can start requiring 3DS without any change on your end.
Is this a WooCommerce bug or a Stripe problem?
Almost never Stripe itself. In our experience it is nearly always one of: an outdated Stripe gateway plugin still using the legacy Charges API, a security header or ad blocker interfering with the authentication iframe, or a missing webhook. Stripe's own infrastructure rarely causes this.
How do I test 3D Secure without charging a real customer?
Use one of Stripe's published test cards built specifically to trigger the SCA/3DS flow in test mode. Run a full checkout with it after every plugin, theme, or PHP update so you catch a broken flow before a real customer does.
The order shows Pending even though the customer says the payment went through. What now?
Check the PaymentIntent ID from Stripe Dashboard against the order, then look at your webhook endpoint's Recent Events log for that same event. A non-2xx response there tells you exactly where it failed, usually a firewall or WAF rule blocking Stripe's servers.