3D Secure Testing: How 3DS2 Works and Test Cards That Trigger Each Flow
3D Secure (3DS) is the authentication layer that shifts fraud liability from merchants to issuers. It is also the part of checkout testing that breaks most often, because it involves redirects, iframes, mobile SDKs and asynchronous outcomes. This guide covers what to test, which test cards trigger which flow, and the bugs that consistently slip through.
From 3DS1 to 3DS2 in one paragraph
3DS1 was the original browser-redirect flow: the customer left your page, authenticated on the issuer's page, and was redirected back with a result. Visa, Mastercard and American Express retired 3DS1 (3-D Secure 1.0.2) in October 2022, and the last regional extensions ended in 2023, so test plans today only need EMV 3DS (3DS2). 3DS2 sends the issuer far more device and transaction data, so it can approve frictionlessly (no user interaction) or issue a challenge. It has two channels: a browser flow, which collects device data through a hidden 3DS Method iframe and shows the challenge in an iframe (or a provider-hosted redirect), and an app flow that uses the 3DS SDK in native mobile apps.
The three outcomes you must handle
Every 3DS transaction ends in one of three ways, and all three need explicit UI states:
- Frictionless success β authentication succeeds without user interaction. The most common 3DS2 result.
- Challenge β the customer is asked to authenticate (OTP, app approval). Handle both success and failure/cancel.
- Not enrolled / unsupported β the card or issuer does not take part. Either no authentication runs at all, or the network answers on the issuer's behalf with an attempt. Depending on your gateway configuration you proceed or fail the transaction.
Under the hood every result is a transStatus value: Y (authenticated), A (attempted; usually still shifts liability to the issuer), N (not authenticated), R (rejected; do not authorize), U (unavailable, a technical failure with no liability shift). C and D mean a challenge or decoupled authentication is still in progress.
The failure paths are where checkouts break: a cancelled challenge is not the same as a decline, and users who close the modal must be able to try again without rebuilding the whole cart.
Stripe test cards for 3DS flows
Stripe documents dedicated test numbers for authentication behaviour:
| Card number | What it triggers |
|---|---|
| 4000 0025 0000 3155 | Requires authentication for on-session payments, and off-session until the card is saved with a SetupIntent |
| 4000 0027 6000 3184 | Requires authentication on every payment, however the card was set up |
| 4000 0000 0000 3055 | Supports 3DS but does not require it |
| 4000 0000 0000 3220 | 3DS required; it must be completed for the payment to succeed |
| 4000 0084 0000 1629 | 3DS required, then the payment is declined (card_declined) after authentication |
These are designed for sandbox use only. For other providers, Adyen documents 4917 6100 0000 0000 (Visa enrolled in 3DS2; expiry 03/2030, CVC 737) for challenge flows, and 4111 1111 1111 1111 among its general Visa test cards. Always re-check the current provider docs β test card behaviour changes without notice.
A fuller list lives in our test card numbers reference.
Sandbox setup checklist
- Enable 3DS in the gateway dashboard, not just in the API call. Many gateways have a per-account toggle that silently disables authentication in test mode.
- Use the 3DS2 SDK in native apps, and test the browser flow (iframe and any redirect your provider uses) separately.
- Whitelist return URLs for every environment, including local dev tunnels.
- Test on real mobile devices, not only desktop narrow viewports. In-app browsers (Instagram, TikTok, banking apps) behave differently from Safari and Chrome.
- Keep the challenge window responsive. A challenge rendered inside a fixed-height iframe is a classic conversion killer.
Bugs that slip through
- Losing the cart on redirect. Persist the order before starting authentication, not after.
- Not handling the user pressing back. Browsers restore the page from bfcache; your JS must re-query the payment state instead of assuming a fresh load.
- Double submission. Users click "Pay" twice while the challenge is open. Use idempotency keys on the authorization request.
- Assuming success after challenge. The authentication result is a separate signal; the authorization can still decline afterwards.
- Async webhooks ignored. Some 3DS2 flows complete asynchronously. If you only listen to the redirect, you will miss results.
- Wrong amount or currency. The authentication covers the amount and currency you sent; if the cart changes, authenticate again (on Stripe, changing a PaymentIntent's amount means confirming it again).
How to test without real cards
You do not need a real card to exercise 3DS. Provider test cards plus generated data cover the full matrix:
- Use provider cards for authenticated success, failure and frictionless flows.
- Use our card generator to produce large volumes of structurally valid numbers for load, fuzzing and UI tests.
- Use the advanced generator when you need every combination of a specific pattern.
- Validate any test number with the Luhn validator.
Summary
3DS testing is mostly about covering the unhappy paths: cancelled challenges, failed authentication, asynchronous results and mobile browsers. Provider test cards trigger each flow deliberately, and a generated dataset fills the gaps. Treat authentication as a separate state machine from authorization and you will avoid the majority of production incidents.