Stripe Error: authentication_required — SCA Required
// Failing pattern — confirms server-side, no SCA flow:
const intent = await stripe.paymentIntents.create({
amount: 4999,
currency: 'gbp',
payment_method: 'pm_card_authenticationRequired',
confirm: true,
});
// intent.status === 'requires_action'
// intent.last_payment_error.code === 'authentication_required'
// intent.last_payment_error.decline_code === 'authentication_required'
authentication_required is Stripe’s signal that the card issuer wants the customer to prove they are the cardholder via 3D Secure (3DS), and your checkout flow either skipped that step or the customer abandoned it. Under PSD2 in the EEA — and increasingly worldwide — issuers reject any online charge that bypasses Strong Customer Authentication when they expect it.
The right answer is almost never server-side. Move authentication into Stripe.js (Payment Element or confirmCardPayment), which handles the 3DS modal, fallback to 3DS1 for older cards, exemption requests, and the requires_action polling for you. Once your client-side flow is set up correctly, authentication_required errors should drop to near-zero and your EEA conversion rates climb noticeably.
Why this happens
- Issuer-mandated SCA under PSD2 / EEA rules. Since 14 September 2019 (with rolling enforcement to March 2022), almost every EEA-issued card requires Strong Customer Authentication on most online charges. The issuer flags the transaction as needing 3DS and Stripe surfaces `authentication_required` if your flow doesn't run the challenge.
- Confirming a PaymentIntent server-side without Stripe.js. If you call `paymentIntents.create({ confirm: true })` on the server without first running the customer through Stripe.js's `confirmPayment`, the SCA flow has no UI to render. The intent enters `requires_action` and your charge fails until you bring the customer back in front of Stripe.js.
- Off-session charge on a payment method with no prior authenticated setup. Off-session charges (subscriptions, saved-card retries) only succeed when the customer originally set up the card with SCA *and* you're charging in a way the issuer trusts (recurring with the same merchant, fixed amount). Without a prior authenticated SetupIntent, off-session charges that need SCA fail.
- Issuer fraud signal triggering step-up authentication. Even outside the EEA, issuers may unexpectedly request 3DS for high-value, cross-border, or pattern-anomaly transactions. US/CA issuers are increasingly using 3DS2 for risk-based step-up. Your code must always be prepared to handle `requires_action` regardless of card country.
- Customer abandoned the 3DS challenge mid-flow. The customer saw the 3DS modal but closed it, switched tabs, or the popup was blocked. The PaymentIntent stays in `requires_action`. Retrying without re-running the challenge fails with the same code.
How to fix it
Fixes are ordered by likelihood. Start with the first one that matches your context.
1. Use the Stripe Payment Element — it handles SCA for you
The Payment Element is Stripe's modern, SCA-ready replacement for the Card Element. It automatically detects `requires_action`, presents the 3DS modal, and confirms once authentication completes. You don't write any 3DS-specific code.
// Server: create PaymentIntent (do NOT confirm yet)
// const intent = await stripe.paymentIntents.create({ amount, currency, automatic_payment_methods: { enabled: true } });
// Send intent.client_secret to client.
// Client:
const stripe = Stripe('pk_live_...');
const elements = stripe.elements({ clientSecret });
const paymentElement = elements.create('payment');
paymentElement.mount('#payment-element');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const { error } = await stripe.confirmPayment({
elements,
confirmParams: { return_url: 'https://example.com/return' },
});
if (error) console.error(error.message);
});
2. Handle `requires_action` explicitly with `confirmCardPayment`
If you've kept a legacy Card Element flow, check the PaymentIntent status and re-confirm with Stripe.js so it can render the 3DS modal.
const result = await stripe.confirmCardPayment(clientSecret, {
payment_method: paymentMethodId,
});
if (result.error) {
if (result.error.code === 'card_declined' &&
result.error.decline_code === 'authentication_required') {
// Customer cancelled 3DS — surface a retry button
showRetry('Your bank requires extra verification. Please try again.');
} else {
showError(result.error.message);
}
} else if (result.paymentIntent.status === 'succeeded') {
showSuccess();
}
3. Set `setup_future_usage` for off-session charges
For subscriptions and saved-card flows, the *first* charge must be on-session and authenticated. Use `setup_future_usage: 'off_session'` (or a separate SetupIntent) so the issuer registers the card for future off-session charges. Otherwise every recurring charge will trip `authentication_required`.
const intent = await stripe.paymentIntents.create({
amount: 4999,
currency: 'gbp',
customer: customerId,
payment_method: paymentMethodId,
setup_future_usage: 'off_session',
confirm: true,
});
4. For Stripe Billing, enable automatic SCA recovery emails
In Dashboard → Settings → Billing → Subscriptions and emails, enable "Email customers when their card requires authentication." Stripe sends the customer a hosted-page link to complete 3DS, which marks the invoice paid automatically. This recovers a measurable percentage of failed renewals.
5. Test with the dedicated 3DS test cards
Card `4000002500003155` always requires 3DS authentication. Card `4000002760003184` requires 3DS and *fails* the challenge. Card `4000008260003178` succeeds frictionlessly (no challenge but authenticated). Build automated tests for all three.
Detection and monitoring in production
Track `authentication_required` separately from other `card_declined` reasons. A spike usually means (a) you've expanded into the EEA without updating your checkout, (b) a customer segment newly subject to PSD2 enforcement, or (c) a regression that bypassed your SCA flow. In Stripe Dashboard, the Payments analytics tab breaks down decline reasons over time.
Related errors
- stripecard_declinedThe customer's card issuer refused to authorise the charge. Stripe relays the issuer's decline reason in `decline_code` (e.g., `insufficient_funds`, `lost_card`, `do_not_honor`).
- stripeinsufficient_fundsThe customer's card issuer declined the charge because the account doesn't have enough available balance to cover the amount, including any pending authorisations and holds.
- stripesignature_verification_failedYour webhook handler called `stripe.webhooks.constructEvent()` and the signature in the `Stripe-Signature` header didn't match the HMAC-SHA256 of the raw request body computed with your endpoint secret.
- stripeapi_key_invalidThe API key you sent in the `Authorization: Bearer ...` header doesn't match any active Stripe key — usually because of a test/live mode mismatch, a deleted or rolled key, copy-paste corruption, or a missing environment variable falling through to `undefined`.
- openairate_limit_exceededYour account has exceeded its per-minute request (RPM) or per-minute token (TPM) limit for the model you're calling. Limits are tier-based and per-model.
Frequently asked questions
Is `authentication_required` only for European cards? +
What's the difference between `authentication_required` and `requires_action`? +
Can I bypass 3DS for trusted customers? +
Why does my saved card on a subscription trigger `authentication_required`? +
Does `authentication_required` count as a chargeback risk? +
What if the customer's card doesn't support 3D Secure 2? +
Can I retry a payment after `authentication_required` without showing the customer again? +
Does using ApplePay or GooglePay avoid `authentication_required`? +
When to escalate to Stripe support
Stripe support can't override an issuer's SCA decision. Escalate only if (a) you see SCA being requested on transactions that should qualify for an exemption (recurring fixed-amount, low-value), or (b) the Payment Element isn't rendering the 3DS modal in your environment (suspect a CSP / sandboxed iframe issue). Otherwise, the path is to fix your client-side flow so the customer can complete authentication.