# Authenticate with 3D Secure Integrate 3D Secure (3DS) into your checkout flow. > Major card brands no longer support 3D Secure 1. If your implementation uses 3D Secure 1, update it to use the [Payment Intents](https://docs.stripe.com/api/payment_intents.md) and [Setup Intents](https://docs.stripe.com/api/setup_intents.md) APIs. Using those APIs: > > - Supports [3D Secure 2 (3DS2)](https://stripe.com/guides/3d-secure-2). - Takes advantage of [Dynamic 3D Secure](https://docs.stripe.com/payments/3d-secure/authentication-flow.md#three-ds-radar). - Complies with European [Strong Customer Authentication](https://docs.stripe.com/strong-customer-authentication.md) regulations. You can integrate 3D Secure (3DS) authentication into your checkout flow on multiple platforms, including Web, iOS, Android, and React Native. This integration runs [3D Secure 2 (3DS2)](https://stripe.com/guides/3d-secure-2) when supported by the customer’s bank and falls back to 3D Secure 1 otherwise. You can also perform 3DS authentication on Stripe while acquiring the transaction with another payment service provider (PSP) by using the [Standalone 3DS](https://docs.stripe.com/payments/3d-secure/standalone-three-d-secure.md) product. #### Web ![Checkout page](https://b.stripecdn.com/docs-statics-srv/assets/3ds-flow-1-checkout-page.039294e0dee3a6dede8ea8a32185aae5.png) The customer enters their card details. ![Loading symbol](https://b.stripecdn.com/docs-statics-srv/assets/3ds-flow-2-frictionless-flow.417618d0570c469cfb6bbc43630c7896.png) The customer’s bank assesses the transaction and can complete 3D Secure at this step. ![Authentication modal](https://b.stripecdn.com/docs-statics-srv/assets/3ds-flow-3-challenge-flow.9052a220f336bbdb75a51799622c6477.png) If required by their bank, the customer completes an additional authentication step. #### iOS ![Checkout Screen](https://b.stripecdn.com/docs-statics-srv/assets/3ds2-checkout.1cd901263328cbb76020b66c173da8b7.png) The customer enters their card details. ![Loading screen](https://b.stripecdn.com/docs-statics-srv/assets/3ds2-loading.f93743ad15b9120027f93f49ed45b26d.png) The SDK presents a loading screen while the customer’s bank checks whether authentication is required. ![Challenge flow screen](https://b.stripecdn.com/docs-statics-srv/assets/3ds2-otp.ce1e46e0a853d7d6e3238750a07bca86.png) If required by their bank, the SDK authenticates the customer. #### Android ![Checkout screen](https://b.stripecdn.com/docs-statics-srv/assets/auth-flow-step01-confirm.399f5a4abbd7f303861689d186b79557.png) The customer enters their payment information. ![Initiate authentication](https://b.stripecdn.com/docs-statics-srv/assets/auth-flow-step02-processing.3877946d74743878ec86cec56dd69085.png) The SDK presents a loading screen while the customer’s bank checks whether authentication is required. ![Challenge flow screen](https://b.stripecdn.com/docs-statics-srv/assets/auth-flow-step03-otp.f42397e1ce4ec5975e05f1bada72d195.png) If required by their bank, the SDK authenticates the customer. ## Control the 3DS flow Stripe triggers 3DS automatically if mandated by regulations such as [Strong Customer Authentication](https://docs.stripe.com/strong-customer-authentication.md) in Europe, industry guidelines such as the Credit Card Security Guidelines in Japan, if requested by an issuer with a [soft decline](https://docs.stripe.com/declines/codes.md) code, or if certain Stripe optimizations apply. You can also [use Radar](https://docs.stripe.com/payments/3d-secure/authentication-flow.md#three-ds-radar) or [the API](https://docs.stripe.com/payments/3d-secure/authentication-flow.md#manual-three-ds) to decide when to prompt users for 3DS authentication. This allows you to customize the authentication process for each user based on your chosen parameters. However, not all transactions support 3DS, for example wallets or *off-session payments* (A payment is described as off-session if it occurs without the direct involvement of the customer, using previously-collected payment information). When a payment triggers 3DS, the card issuer might require the customer to authenticate to complete the payment, as long as 3DS authentication is supported for that card. While Stripe initiates the authentication request, the requirement comes from the issuer. Depending on the front end you’re using, this might require you to [display the 3DS flow](https://docs.stripe.com/payments/3d-secure/authentication-flow.md#when-to-use-3d-secure). In a typical Payment Intent API flow that triggers 3DS: 1. The user enters their payment information, which confirms a PaymentIntent, SetupIntent, or attaches a PaymentMethod to a Customer. 1. Stripe assesses if the transaction supports and requires 3DS based on regulatory mandates, Radar rules, manual API requests, issuer soft declines, and other criteria. 1. If 3DS is: - **Not required**: For example, because of an *exemption* (Some transactions that are deemed low risk, based on the volume of fraud rates associated with the payment provider or bank, may be exempt from Europe's Strong Customer Authentication requirements), Stripe attempts the charge. The PaymentIntent transitions to a status of `processing`. If requested by the issuer with a [soft decline](https://docs.stripe.com/declines/codes.md), we automatically reattempt and continue as if required. - **Not supported**: The PaymentIntent transitions to a status of `requires_payment_method`. Depending on the reason 3DS was triggered it might be permissible to continue to the authorization step for the charge. In that case, the PaymentIntent transitions to a status of `processing`. - **Required**: Stripe starts the 3DS authentication flow by contacting the card issuer’s 3DS Access Control Server (ACS) and starting the 3DS flow. 1. When 3DS flow information is received from the issuer, Stripe submits the request for the issuer to authenticate the cardholder. The PaymentIntent transitions to a status of `requires_action`: - See below for how to [display the required 3DS action](https://docs.stripe.com/payments/3d-secure/authentication-flow.md#when-to-use-3d-secure). Issuers might request different 3DS flow action types, which might not always result in visibly displaying a 3DS challenge (for example, a frictionless flow). - If the issuer doesn’t support 3DS at all or has an outage, Stripe might attempt to complete the payment without authentication if permissible. - Data for 3DS authentication requests is typically provided by the customer at the time of the transaction. To reduce friction and the possibility of failed authentication, we might complete these requests with data we infer from other sources such as data collected from your customer during the payment flow, records related to your customer’s past transactions with you, or relevant information available from the customer’s card or issuers. - If Stripe already has access to all the required 3DS data elements, our optimized 3DS server might attempt to complete the authentication request for you while confirming the PaymentIntent. This can result in the PaymentIntent directly transitioning to a status of `processing` if the 3DS flow succeeds, or to a status of `requires_action` if additional steps or data elements are required to complete the 3DS flow. 1. Depending on the 3DS authentication result: - **Authenticated**: Stripe attempts the charge and the PaymentIntent transitions to a status of `processing`. - **Failure**: The PaymentIntent transitions to a status of `requires_payment_method`, indicating that you need to try a different payment method, or you can retry 3DS by reconfirming. - **Other scenarios**: Depending on the reason the payment triggered 3DS, it might be permissible to continue authorization for the charge in [edge cases](https://docs.stripe.com/api/charges/object.md#charge_object-payment_method_details-card-three_d_secure-result). For example, a result of `attempt_acknowledged` leads to a charge and the PaymentIntent transitions to a status of `processing`. - An exception is when creating [Indian e-mandates for recurring payments](https://docs.stripe.com/india-recurring-payments.md). Anything but an `authenticated` result is treated as failure. 1. The PaymentIntent transitions to one of the following statuses, depending on the outcome of the payment: `succeeded`, `requires_capture`, or `requires_payment_method`. To track whether 3DS was supported and attempted on a card payment, read the [three_d_secure](https://docs.stripe.com/api/charges/object.md#charge_object-payment_method_details-card-three_d_secure) property on the card information in the Charge’s `payment_method_details`. Stripe populates the `three_d_secure` property when the customer attempts to authenticate the card—`three_d_secure.result` indicates the authentication outcome. ### Use Radar rules in the Dashboard Stripe provides [fraud controls](https://docs.stripe.com/radar/rules.md#request-3d-secure) to dynamically request 3DS when creating or confirming a [PaymentIntent](https://docs.stripe.com/api/payment_intents.md) or [SetupIntent](https://docs.stripe.com/api/setup_intents.md). You can configure these rules in your [Dashboard](https://dashboard.stripe.com/settings/radar/rules). If you have [Radar for Fraud Teams](https://stripe.com/radar/fraud-teams), you can add [custom 3DS rules](https://docs.stripe.com/radar/rules.md#request-3d-secure). ### Manually request 3DS with the API The default method to trigger 3DS is [using Radar to dynamically request 3D Secure](https://docs.stripe.com/radar/risk-settings.md#adaptive-3ds) based on risk level and other requirements. Triggering 3DS manually is for advanced users integrating Stripe with their own fraud engine. To trigger 3DS manually, set `payment_method_options[card][request_three_d_secure]` depending on what you want to optimize for when either creating or confirming a [PaymentIntent](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method_options-card-request_three_d_secure) or [SetupIntent](https://docs.stripe.com/api/setup_intents/create.md#create_setup_intent-payment_method_options-card-request_three_d_secure), or creating a [Checkout Session](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-payment_method_options-card-request_three_d_secure). This process is the same for one-time payments or when setting up a payment method for future payments. When you provide this parameter, Stripe attempts to perform 3DS and overrides any [dynamic 3D Secure Radar rules](https://docs.stripe.com/radar/rules.md) on the PaymentIntent, SetupIntent, or Checkout Session. #### Payment Intents API ```curl curl https://api.stripe.com/v1/payment_intents \ -u "<>:" \ -d amount=1000 \ -d currency=usd \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```cli stripe payment_intents create \ --amount=1000 \ --currency=usd \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```ruby # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = Stripe::StripeClient.new("<>") payment_intent = client.v1.payment_intents.create({ amount: 1000, currency: 'usd', payment_method_options: {card: {request_three_d_secure: 'any'}}, }) ``` ```python # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = StripeClient("<>") # For SDK versions 12.4.0 or lower, remove '.v1' from the following line. payment_intent = client.v1.payment_intents.create({ "amount": 1000, "currency": "usd", "payment_method_options": {"card": {"request_three_d_secure": "any"}}, }) ``` ```php // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys $stripe = new \Stripe\StripeClient('<>'); $paymentIntent = $stripe->paymentIntents->create([ 'amount' => 1000, 'currency' => 'usd', 'payment_method_options' => ['card' => ['request_three_d_secure' => 'any']], ]); ``` ```java // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys StripeClient client = new StripeClient("<>"); PaymentIntentCreateParams params = PaymentIntentCreateParams.builder() .setAmount(1000L) .setCurrency("usd") .setPaymentMethodOptions( PaymentIntentCreateParams.PaymentMethodOptions.builder() .setCard( PaymentIntentCreateParams.PaymentMethodOptions.Card.builder() .setRequestThreeDSecure( PaymentIntentCreateParams.PaymentMethodOptions.Card.RequestThreeDSecure.ANY ) .build() ) .build() ) .build(); // For SDK versions 29.4.0 or lower, remove '.v1()' from the following line. PaymentIntent paymentIntent = client.v1().paymentIntents().create(params); ``` ```node // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('<>'); const paymentIntent = await stripe.paymentIntents.create({ amount: 1000, currency: 'usd', payment_method_options: { card: { request_three_d_secure: 'any', }, }, }); ``` ```go // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys sc := stripe.NewClient("<>") params := &stripe.PaymentIntentCreateParams{ Amount: stripe.Int64(1000), Currency: stripe.String(stripe.CurrencyUSD), PaymentMethodOptions: &stripe.PaymentIntentCreatePaymentMethodOptionsParams{ Card: &stripe.PaymentIntentCreatePaymentMethodOptionsCardParams{ RequestThreeDSecure: stripe.String(stripe.PaymentIntentPaymentMethodOptionsCardRequestThreeDSecureAny), }, }, } result, err := sc.V1PaymentIntents.Create(context.TODO(), params) ``` ```dotnet // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys var options = new PaymentIntentCreateOptions { Amount = 1000, Currency = "usd", PaymentMethodOptions = new PaymentIntentPaymentMethodOptionsOptions { Card = new PaymentIntentPaymentMethodOptionsCardOptions { RequestThreeDSecure = "any", }, }, }; var client = new StripeClient("<>"); var service = client.V1.PaymentIntents; PaymentIntent paymentIntent = service.Create(options); ``` #### Setup Intents API ```curl curl https://api.stripe.com/v1/setup_intents \ -u "<>:" \ -d customer="{{CUSTOMER_ID}}" \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```cli stripe setup_intents create \ --customer="{{CUSTOMER_ID}}" \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```ruby # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = Stripe::StripeClient.new("<>") setup_intent = client.v1.setup_intents.create({ customer: '{{CUSTOMER_ID}}', payment_method_options: {card: {request_three_d_secure: 'any'}}, }) ``` ```python # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = StripeClient("<>") # For SDK versions 12.4.0 or lower, remove '.v1' from the following line. setup_intent = client.v1.setup_intents.create({ "customer": "{{CUSTOMER_ID}}", "payment_method_options": {"card": {"request_three_d_secure": "any"}}, }) ``` ```php // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys $stripe = new \Stripe\StripeClient('<>'); $setupIntent = $stripe->setupIntents->create([ 'customer' => '{{CUSTOMER_ID}}', 'payment_method_options' => ['card' => ['request_three_d_secure' => 'any']], ]); ``` ```java // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys StripeClient client = new StripeClient("<>"); SetupIntentCreateParams params = SetupIntentCreateParams.builder() .setCustomer("{{CUSTOMER_ID}}") .setPaymentMethodOptions( SetupIntentCreateParams.PaymentMethodOptions.builder() .setCard( SetupIntentCreateParams.PaymentMethodOptions.Card.builder() .setRequestThreeDSecure( SetupIntentCreateParams.PaymentMethodOptions.Card.RequestThreeDSecure.ANY ) .build() ) .build() ) .build(); // For SDK versions 29.4.0 or lower, remove '.v1()' from the following line. SetupIntent setupIntent = client.v1().setupIntents().create(params); ``` ```node // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('<>'); const setupIntent = await stripe.setupIntents.create({ customer: '{{CUSTOMER_ID}}', payment_method_options: { card: { request_three_d_secure: 'any', }, }, }); ``` ```go // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys sc := stripe.NewClient("<>") params := &stripe.SetupIntentCreateParams{ Customer: stripe.String("{{CUSTOMER_ID}}"), PaymentMethodOptions: &stripe.SetupIntentCreatePaymentMethodOptionsParams{ Card: &stripe.SetupIntentCreatePaymentMethodOptionsCardParams{ RequestThreeDSecure: stripe.String(stripe.SetupIntentPaymentMethodOptionsCardRequestThreeDSecureAny), }, }, } result, err := sc.V1SetupIntents.Create(context.TODO(), params) ``` ```dotnet // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys var options = new SetupIntentCreateOptions { Customer = "{{CUSTOMER_ID}}", PaymentMethodOptions = new SetupIntentPaymentMethodOptionsOptions { Card = new SetupIntentPaymentMethodOptionsCardOptions { RequestThreeDSecure = "any", }, }, }; var client = new StripeClient("<>"); var service = client.V1.SetupIntents; SetupIntent setupIntent = service.Create(options); ``` #### Checkout Session API ```curl curl https://api.stripe.com/v1/checkout/sessions \ -u "<>:" \ -d mode=payment \ --data-urlencode success_url="https://example.com/success" \ -d "line_items[0][price]"="{{PRICE_ID}}" \ -d "line_items[0][quantity]"=1 \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```cli stripe checkout sessions create \ --mode=payment \ --success-url="https://example.com/success" \ -d "line_items[0][price]"="{{PRICE_ID}}" \ -d "line_items[0][quantity]"=1 \ -d "payment_method_options[card][request_three_d_secure]"=any ``` ```ruby # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = Stripe::StripeClient.new("<>") session = client.v1.checkout.sessions.create({ mode: 'payment', success_url: 'https://example.com/success', line_items: [ { price: '{{PRICE_ID}}', quantity: 1, }, ], payment_method_options: {card: {request_three_d_secure: 'any'}}, }) ``` ```python # Set your secret key. Remember to switch to your live secret key in production. # See your keys here: https://dashboard.stripe.com/apikeys client = StripeClient("<>") # For SDK versions 12.4.0 or lower, remove '.v1' from the following line. session = client.v1.checkout.sessions.create({ "mode": "payment", "success_url": "https://example.com/success", "line_items": [{"price": "{{PRICE_ID}}", "quantity": 1}], "payment_method_options": {"card": {"request_three_d_secure": "any"}}, }) ``` ```php // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys $stripe = new \Stripe\StripeClient('<>'); $session = $stripe->checkout->sessions->create([ 'mode' => 'payment', 'success_url' => 'https://example.com/success', 'line_items' => [ [ 'price' => '{{PRICE_ID}}', 'quantity' => 1, ], ], 'payment_method_options' => ['card' => ['request_three_d_secure' => 'any']], ]); ``` ```java // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys StripeClient client = new StripeClient("<>"); SessionCreateParams params = SessionCreateParams.builder() .setMode(SessionCreateParams.Mode.PAYMENT) .setSuccessUrl("https://example.com/success") .addLineItem( SessionCreateParams.LineItem.builder() .setPrice("{{PRICE_ID}}") .setQuantity(1L) .build() ) .setPaymentMethodOptions( SessionCreateParams.PaymentMethodOptions.builder() .setCard( SessionCreateParams.PaymentMethodOptions.Card.builder() .setRequestThreeDSecure( SessionCreateParams.PaymentMethodOptions.Card.RequestThreeDSecure.ANY ) .build() ) .build() ) .build(); // For SDK versions 29.4.0 or lower, remove '.v1()' from the following line. Session session = client.v1().checkout().sessions().create(params); ``` ```node // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys const stripe = require('stripe')('<>'); const session = await stripe.checkout.sessions.create({ mode: 'payment', success_url: 'https://example.com/success', line_items: [ { price: '{{PRICE_ID}}', quantity: 1, }, ], payment_method_options: { card: { request_three_d_secure: 'any', }, }, }); ``` ```go // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys sc := stripe.NewClient("<>") params := &stripe.CheckoutSessionCreateParams{ Mode: stripe.String(stripe.CheckoutSessionModePayment), SuccessURL: stripe.String("https://example.com/success"), LineItems: []*stripe.CheckoutSessionCreateLineItemParams{ &stripe.CheckoutSessionCreateLineItemParams{ Price: stripe.String("{{PRICE_ID}}"), Quantity: stripe.Int64(1), }, }, PaymentMethodOptions: &stripe.CheckoutSessionCreatePaymentMethodOptionsParams{ Card: &stripe.CheckoutSessionCreatePaymentMethodOptionsCardParams{ RequestThreeDSecure: stripe.String(stripe.CheckoutSessionPaymentMethodOptionsCardRequestThreeDSecureAny), }, }, } result, err := sc.V1CheckoutSessions.Create(context.TODO(), params) ``` ```dotnet // Set your secret key. Remember to switch to your live secret key in production. // See your keys here: https://dashboard.stripe.com/apikeys var options = new Stripe.Checkout.SessionCreateOptions { Mode = "payment", SuccessUrl = "https://example.com/success", LineItems = new List { new Stripe.Checkout.SessionLineItemOptions { Price = "{{PRICE_ID}}", Quantity = 1, }, }, PaymentMethodOptions = new Stripe.Checkout.SessionPaymentMethodOptionsOptions { Card = new Stripe.Checkout.SessionPaymentMethodOptionsCardOptions { RequestThreeDSecure = "any", }, }, }; var client = new StripeClient("<>"); var service = client.V1.Checkout.Sessions; Stripe.Checkout.Session session = service.Create(options); ``` When to provide this parameter depends on when your fraud engine detects risk. For example, if your fraud engine only inspects card details, you know whether to request 3DS before you create the PaymentIntent or SetupIntent. If your fraud engine inspects both card and transaction details, provide the parameter during confirmation—when you have more information. Then pass the resulting PaymentIntent or SetupIntent to your client to complete the process. Explore the `request_three_d_secure` parameter’s usage for each case in the API reference: - [Create a PaymentIntent](https://docs.stripe.com/api/payment_intents/create.md#create_payment_intent-payment_method_options-card-request_three_d_secure) - [Confirm a PaymentIntent](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-payment_method_options-card-request_three_d_secure) - [Create a SetupIntent](https://docs.stripe.com/api/setup_intents/create.md#create_setup_intent-payment_method_options-card-request_three_d_secure) - [Confirm a SetupIntent](https://docs.stripe.com/api/setup_intents/confirm.md#confirm_setup_intent-payment_method_options-card-request_three_d_secure) - [Create a Checkout Session](https://docs.stripe.com/api/checkout/sessions/create.md#create_checkout_session-payment_method_options-card-request_three_d_secure) Set `request_three_d_secure` to `any` to manually request 3DS with a preference for a `frictionless` flow, increasing the likelihood of the authentication being completed without any additional input from the customer. Set `request_three_d_secure` to `challenge` to request 3DS with a preference for a `challenge` flow, where the customer must respond to a prompt for active authentication. Stripe can’t guarantee your preference because the issuer determines the ultimate authentication flow. You can find out what the ultimate authentication flow was by inspecting the `authentication_flow` on the `three_d_secure` property of the [Charge](https://docs.stripe.com/api/charges/object.md#charge_object-payment_method_details-card-three_d_secure-authentication_flow) or [SetupAttempt](https://docs.stripe.com/api/setup_attempts/object.md#setup_attempt_object-payment_method_details-card-three_d_secure-authentication_flow). To learn more about 3DS flows, read our [guide](https://stripe.com/guides/3d-secure-2#frictionless-authentication). > Stripe only prompts your customer to perform authentication if 3DS authentication is available for a card. If it’s not available for the given card or if an error occurred during the authentication process, the payment proceeds normally. Stripe’s mandatory authentication rules run automatically, regardless of whether or not you manually request 3DS. Any 3DS requests from you are additional to those required for SCA. ## Display the 3DS flow #### Web Stripe automatically displays the authentication UI in a pop-up modal when calling `confirmCardPayment` and `handleCardAction`. You can also redirect to the bank’s website or use an iframe. Stripe.js collects [basic device information](https://support.stripe.com/questions/3d-secure-2-device-information) during 3DS2 authentication and sends it to the issuing bank for their risk analysis. ### Redirect to the bank website To redirect your customer to the 3DS authentication page, pass a `return_url` to the PaymentIntent when confirming [on the server](https://docs.stripe.com/api/payment_intents/confirm.md#confirm_payment_intent-return_url) or on the [client](https://docs.stripe.com/js/payment_intents/confirm_card_payment). After confirmation, if a PaymentIntent has a *requires\_action* (This status appears as "requires_source_action" in API versions before 2019-02-11) status, inspect the PaymentIntent’s `next_action`. If it contains `redirect_to_url`, that means 3DS is required. ```js next_action: { type: 'redirect_to_url', redirect_to_url: { url: 'https://hooks.stripe.com/...', return_url: 'https://mysite.com' } } ``` In the browser, redirect the customer to the `url` in the redirect_to_url hash to complete authentication. ```javascript var action = intent.next_action; if (action && action.type === 'redirect_to_url') { window.location = action.redirect_to_url.url; } ``` When the customer finishes the authentication process, the redirect sends them back to the `return_url` you specified when you created or confirmed the PaymentIntent. The redirect also adds `payment_intent` and `payment_intent_client_secret` URL query parameters that your application can use to identify the PaymentIntent associated with the purchase. ### Display in an iframe You can’t customize the authentication UI on the web to match your website’s design—the bank that issued the card controls the fonts and colors. However, you can choose *how* and *where* to show the 3DS UI. Most businesses show it in a modal dialog above their payment page. If you have your own modal component, you can place the 3DS frame inside of it. You can also show the authentication content inline with your payment form. #### Confirm the PaymentIntent When your customer is ready to complete their purchase, you *confirm* (Confirming a PaymentIntent indicates that the customer intends to pay with the current or provided payment method. Upon confirmation, the PaymentIntent attempts to initiate a payment) the PaymentIntent to begin the process of collecting their payment. If you want to control how to display 3DS, provide a `return_url`, which is where the 3DS `