Card authentication and 3D Secure

    Learn about authentication to reduce fraud and meet regulatory requirements.

    What is 3D Secure?

    For extra fraud protection, 3D Secure requires customers to complete an additional verification step with the card issuer when paying. Typically, you direct the customer to an authentication page on their bank’s website, and they enter a password associated with the card or a code sent to their phone. This process is familiar to customers through the card networks’ brand names, such as Visa Secure and Mastercard Identity Check.

    The Strong Customer Authentication regulation in Europe requires the use of 3D Secure for card payments.

    Step 1: The customer enters their card details.

    Step 2: The customer's bank assesses the transaction and can complete 3D Secure at this step.

    Step 3: If required by their bank, the customer completes an additional authentication step.

    Checkout Screen

    Step 1: The customer enters their card details.

    Loading screen

    Step 2: The SDK presents a loading screen while the customer's bank checks whether authentication is required.

    Challenge flow screen

    Step 3: If required by their bank, the SDK authenticates the customer.

    Checkout screen

    Step 1: The customer enters their payment information.

    Initiate authentication

    Step 2: The SDK presents a loading screen while the customer's bank checks whether authentication is required.

    Challenge flow screen

    Step 3: If required by their bank, the SDK authenticates the customer.

    Stripe supports 3D Secure 2. Your integration runs 3D Secure 2 when supported by the customer’s bank (we expect individual banks to roll out support between 2019 and 2020) and falls back to 3D Secure 1 otherwise.

    Disputed payments and liability shift

    Payments that have been successfully authenticated using 3D Secure are covered by a liability shift. Should a 3D Secure payment be disputed as fraudulent by the cardholder, the liability shifts from you to the card issuer. These types of disputes are handled internally, do not appear in the Dashboard, and do not result in funds being withdrawn from your Stripe account.

    Liability shift can also occur if the card or issuer isn’t enrolled in 3D Secure but the type of card could support 3D Secure (e.g., most Visa and Mastercard consumer cards). During the payment process, the cardholder isn’t prompted to complete 3D Secure authentication, since the card is not enrolled. Although the cardholder did not complete 3D Secure authentication, liability still shifts to the issuer.

    There are certain circumstances where payments that are successfully authenticated using 3D Secure do not experience a liability shift. This is rare and can happen, for example, if you have an excessive level of fraud on your account and are enrolled in a fraud monitoring program.

    Although payments that have been successfully authenticated using 3D Secure cannot be disputed as fraudulent with an upfront financial chargeback, issuers may initiate a retrieval request. This type of dispute is non-financial, and is basically a request for information.

    It’s important to note that responding to retrieval requests is important for any charge, but is vital when a 3D Secure-authenticated charge is involved. Although the cardholder’s bank is not allowed to file an upfront financial chargeback for fraud, they are allowed to initiate a financial chargeback if the merchant does not respond to the retrieval request, known as a no-reply chargeback. To prevent no-reply chargebacks on 3D Secure charges, be sure to submit sufficient information about the charge. You should include information about what was ordered, how it was delivered and to whom it was delivered (whether merchandise or services, etc.).

    Controlling when to present the 3D Secure flow

    Stripe triggers 3D Secure automatically if required by a regulatory mandate such as Strong Customer Authentication. You can also use Radar rules or the API to control when customers are prompted to complete 3D Secure authentication, making a determination for each user based on the desired parameters.

    To track whether 3D Secure was attempted on a card payment, read the 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.succeeded indicates whether authentication succeeded.

    Use Radar rules in the Dashboard

    Stripe provides three default rules to dynamically request 3D Secure. You can configure these 3D Secure Radar rules in your Stripe Dashboard. The following screenshot depicts these Radar rules, which request additional authentication from customers when the issuer of their card requires 3D Secure:

    The first rule is enabled by default, but you can choose to disable it.

    If you have Radar for Fraud Teams, you can add custom 3D Secure rules using the syntax described in our Rules reference. Radar requests 3D Secure authentication for payments that match these rules. In the example below, the enabled rule requests 3D Secure authentication for payment attempts where the amount of the payment exceeds $500 USD and the risk level is not considered normal.

    Manually request 3D Secure with the API

    The default method to trigger 3D Secure is using Radar to dynamically request 3D Secure based on risk level and other requirements. Triggering 3D Secure manually is for advanced users integrating Stripe with their own fraud engine.

    To trigger 3D Secure manually, set payment_method_options[card][request_three_d_secure] to any when creating or confirming a PaymentIntent or SetupIntent. This process is the same for one-time payments or future off-session payments. When this parameter is provided, Stripe attempts to perform 3D Secure and overrides any dynamic 3D Secure Radar rules on the PaymentIntent or SetupIntent.

    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 3D Secure before you create the PaymentIntent or SetupIntent. If your fraud engine inspects both card and transaction details, provide the parameter during confirmation—once 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:

    When you set request_three_d_secure to any, Stripe requires your customer to perform authentication to complete the payment successfully if 3D Secure authentication is available for a card. If 3D Secure is not available for the given card, the payment proceeds normally.

    Stripe’s SCA rules run automatically, regardless of whether you manually request 3D Secure. Any 3D Secure prompts from you are additional and not required for SCA.

    Displaying the 3D Secure Flow

    There are three ways to display the 3D Secure flow on web:

    • Show the authentication UI in a pop-up modal (default behavior when calling confirmCardPayment and handleCardAction)
    • Redirect to the bank’s website
    • Use an iframe

    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 or on the client. You can also set return_url when creating the PaymentIntent.

    After confirmation, if a PaymentIntent has a requires_action status, inspect the PaymentIntent’s next_action. If it’s redirect_to_url, that means 3D Secure is required.

    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.

    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 customer’s purchase.

    Display in an iframe

    You cannot customize the authentication UI on the web to match your website’s design—the bank that issued the card controls the fonts and colors of the experience.

    However, you can choose how and where the 3D Secure UI is shown. Most merchants show it in a modal dialog above their payment page. If you have your own modal component, you can place the 3D Secure frame inside of it. You can also show the authentication content inline with your payment form.

    1 Confirm the PaymentIntent Server-side

    When your customer is ready to complete their purchase, you confirm the PaymentIntent to begin the process of collecting their payment.

    If you want to control how 3D Secure is displayed, provide a return_url, which is where the 3D Secure <iframe> will be redirected once authentication is complete. If your site uses a content security policy, check that iframes from https://js.stripe.com, https://hooks.stripe.com, and the origin of the URL you passed to return_url are allowed.

    If you are confirming from the frontend, use the confirmCardPayment method in Stripe.js. For example, if your are gathering card information using Stripe Elements:

    stripe.confirmCardPayment(
      '{{PAYMENT_INTENT_CLIENT_SECRET}}',
      {
        payment_method: {card: cardElement},
        return_url: 'https://example.com/return_url'
      },
      // Disable the default next action handling.
      {handleActions: false}
    ).then(function(result) {
      // Handle result.error or result.paymentIntent
      // More details in Step 2.
    });
    

    If you confirm from your server, be sure to provide a return_url. Depending on your integration, you may want to pass other information to confirm as well.

    curl https://api.stripe.com/v1/payment_intents/{{PAYMENT_INTENT_ID}}/confirm \
    -u sk_test_4eC39HqLyjWDarjtT1zdp7dc: \
    -d return_url="https://example.com/return_url"
    intent = Stripe::PaymentIntent.confirm(
      '{{PAYMENT_INTENT_ID}}',
      {
        return_url: 'https://example.com/return_url'
      }
    )
    
    intent = stripe.PaymentIntent.confirm(
      '{{PAYMENT_INTENT_ID}}',
      return_url='https://example.com/return_url'
    )
    
    $intent = \Stripe\PaymentIntent::retrieve('{{PAYMENT_INTENT_ID}}');
    $intent->confirm([
        'return_url' => 'https://example.com/return_url',
    ]);
    
    PaymentIntent intent = PaymentIntent.retrieve("{{PAYMENT_INTENT_ID}}");
    Map<String, Object> params = new HashMap<String, Object>();
    params.put("return_url", "https://example.com/return_url");
    intent.confirm(params);
    
    (async () => {
      let intent = await stripe.paymentIntents.confirm(
        '{{PAYMENT_INTENT_ID}}',
        {
          return_url: 'https://example.com/return_url'
        }
      );
    })()
    
    params := &stripe.PaymentIntentConfirmParams{
      ReturnUrl: stripe.String("https://example.com/return_url"),
    }
    intent, err := paymentintent.Confirm("{{PAYMENT_INTENT_ID}}", params)
    
    var service = new PaymentIntentService();
    var options = new PaymentIntentConfirmOptions
    {
      ReturnUrl = "https://example.com/return_url",
    };
    var intent = service.Confirm("{{PAYMENT_INTENT_ID}}", options);
    

    2 Check the PaymentIntent status Server-side

    Next, inspect the status property of the confirmed PaymentIntent to determine whether the payment completed successfully. The following list describes possible status values and their significance:

    Status Description
    requires_payment_method The request failed with a 402 HTTP status code, meaning that the payment was unsuccessful. Check the last_payment_error property and attempt to try again, collecting new payment information from the customer if necessary.
    requires_action An additional step like 3D Secure is required to complete the payment. Ask the customer to return to your application to complete payment.
    succeeded The payment completed, creating a Charge with the supplied payment method. No further steps are required.

    Note that on versions of the API before 2019-02-11, requires_payment_method appears as requires_source and requires_action appears as requires_source_action.

    3 Render the 3D Secure iframe Client-side

    When the value of the status property is requires_action, some additional step is required before payment can be processed. For a card payment requiring 3D Secure, the PaymentIntent’s status will be requires_action and its next_action property will be redirect_to_url. The redirect_to_url payload contains a URL that you should open in an iframe to display 3D Secure:

    var iframe = document.createElement('iframe');
    iframe.src = paymentIntent.next_action.redirect_to_url.url;
    iframe.width = 600;
    iframe.height = 400;
    yourContainer.appendChild(iframe);

    For 3D Secure 2, card issuers are required to support showing the 3D Secure content at sizes of 250x400, 390x400, 500x600, 600x400, and full screen (dimensions are width by height). The 3D Secure UI may be better if you open the iframe at exactly one of those sizes.

    4 Handle the redirect Client-side

    After the customer completes 3D Secure, the iframe redirects to the return_url you provided when confirming the PaymentIntent. That page should postMessage to your top-level page to inform it that 3D Secure authentication is complete. Your top-level page should then determine whether the payment succeeded or requires further action from your customer.

    For example, you might have your return_url page execute:

    window.top.postMessage('3DS-authentication-complete');

    Your top payment page should be listening for this postMessage to know when authentication has finished. You should then retrieve the updated PaymentIntent and check on the status of the payment. If the authentication failed, the PaymentIntent’s status is requires_payment_method. If the payment completed successfully, the status is succeeded. If you use separate authorize and capture, the status is requires_capture instead.

    function on3DSComplete() {
      // Hide the 3DS UI
      yourContainer.remove();
    
      // Check the PaymentIntent
      stripe.retrievePaymentIntent('{{PAYMENT_INTENT_CLIENT_SECRET}}')
        .then(function(result) {
          if (result.error) {
            // PaymentIntent client secret was invalid
          } else {
            if (result.paymentIntent.status === 'succeeded') {
              // Show your customer that the payment has succeeded
            } else if (result.paymentIntent.status === 'requires_payment_method') {
              // Authentication failed, prompt the customer to enter another payment method
            }
          }
        });
    }
    
    window.addEventListener('message', function(ev) {
      if (ev.data === '3DS-authentication-complete') {
        on3DSComplete();
      }
    }, false);

    The STPPaymentHandler class presents UIViewControllers over your app for authentication. STPPaymentHandler.threeDSCustomizationSettings contains the customizable items for 3D Secure authentication.

    The authenticationTimeout property controls how long the 3D Secure authentication process will run before it times out. This duration includes both network round trips and awaiting customer input, and must be at least 5 minutes. If authentication times out, STPPaymentHandler will report an error with code STPPaymentHandlerTimedOutErrorCode in the completion block.

    The uiCustomization property allows you to provide a STPThreeDSUICustomization instance to control the look and feel of the authentication UI. For each customizable element of the UI, like the navigation bar or the Submit button, there is a corresponding class with properties to configure colors, fonts, text, borders, etc. See the STPThreeDSUICustomization documentation for a detailed explanation of each parameter.

    The following example code demonstrates some of the configuration for a dark UI color theme.

    let uiCustomization = STPPaymentHandler.shared().threeDSCustomizationSettings.uiCustomization
    uiCustomization.textFieldCustomization.keyboardAppearance = .dark
    uiCustomization.navigationBarCustomization.barStyle = .black
    uiCustomization.navigationBarCustomization.textColor = .white
    uiCustomization.buttonCustomization(for: .cancel).textColor = .white
    STPThreeDSUICustomization *uiCustomization = [STPPaymentHandler sharedHandler].threeDSCustomizationSettings.uiCustomization;
    uiCustomization.textFieldCustomization.keyboardAppearance = UIKeyboardAppearanceDark;
    uiCustomization.navigationBarCustomization.barStyle = UIBarStyleBlack;
    uiCustomization.navigationBarCustomization.textColor = UIColor.whiteColor;
    [uiCustomization buttonCustomizationForButtonType:STPThreeDSCustomizationButtonTypeCancel].textColor = UIColor.whiteColor;

    There are four different types of challenge screens that may be presented to your customer. Test your UI customization with these different screens with your customization.

    You can also configure which view controller presents the authentication UI and other behavior via the STPAuthenticationContext passed to STPPaymentHandler.

    Set up a return URL

    The iOS SDK can present a webview in your app to authenticate your customer instead of a native view (for example, if your customer is using an outdated app version). When authentication is finished, the webview can automatically dismiss itself instead of having your customer close it. To enable this behavior, configure a custom URL scheme or universal link and set up your app delegate to forward the URL to the SDK.

    // This method handles opening custom URL schemes (e.g., "your-app://")
    func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
        let stripeHandled = Stripe.handleURLCallback(with: url)
        if (stripeHandled) {
            return true
        } else {
            // This was not a Stripe url – handle the URL normally as you would
        }
        return false
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
        if userActivity.activityType == NSUserActivityTypeBrowsingWeb {
            if let url = userActivity.webpageURL {
                let stripeHandled = Stripe.handleURLCallback(with: url)
                if (stripeHandled) {
                    return true
                } else {
                    // This was not a Stripe url – handle the URL normally as you would
                }
            }
        }
        return false
    }
    
    // This method handles opening custom URL schemes (e.g., "your-app://")
    - (BOOL)application:(UIApplication *)app openURL:(NSURL *)url options:(NSDictionary<UIApplicationOpenURLOptionsKey,id> *)options {
        BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:url];
        if (stripeHandled) {
            return YES;
        } else {
            // This was not a Stripe url – handle the URL normally as you would
        }
        return NO;
    }
    
    // This method handles opening universal link URLs (e.g., "https://example.com/stripe_ios_callback")
    - (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void (^)(NSArray<id<UIUserActivityRestoring>> * _Nullable))restorationHandler {
        if (userActivity.activityType == NSUserActivityTypeBrowsingWeb) {
            if (userActivity.webpageURL) {
                BOOL stripeHandled = [Stripe handleStripeURLCallbackWithURL:userActivity.webpageURL];
                if (stripeHandled) {
                    return YES;
                } else {
                    // This was not a Stripe url – handle the URL normally as you would
                }
                return NO;
            }
        }
        return NO;
    }
    

    Next, pass the URL as the return_url when you confirm the PaymentIntent (if you’re collecting a payment) or SetupIntent (if you’re saving card details). After webview-based authentication is finished, Stripe redirects the user back to your app or web page with the return_url you provided.

    PaymentAuthConfig.Stripe3ds2Config contains the customizable items for 3D Secure authentication interactions.

    The timeout property controls how long the 3D Secure authentication process runs before it times out. This duration includes both network round trips and awaiting customer input. Note that this value must be at least 5 minutes in order to remain compliant with Strong Customer Authentication regulation. A value less than 5 minutes results in an error.

    The uiCustomization property allows you to provide a StripeUiCustomization instance to control the look of views presented by the Android SDK during 3D Secure authentication. Stripe currently supports customization parameters for colors, fonts, text, borders on app bars, labels, text fields, and buttons. See the SDK documentation for a full explanation of each parameter.

    final PaymentAuthConfig.Stripe3ds2UiCustomization uiCustomization =
            new PaymentAuthConfig.Stripe3ds2UiCustomization.Builder()
                    .setLabelCustomization(
                            new PaymentAuthConfig.Stripe3ds2LabelCustomization.Builder()
                                    .setTextFontSize(12)
                                    .build())
                    .build();
    PaymentAuthConfig.init(new PaymentAuthConfig.Builder()
            .set3ds2Config(new PaymentAuthConfig.Stripe3ds2Config.Builder()
                    .setTimeout(5)
                    .setUiCustomization(uiCustomization)
                    .build())
            .build());
    val uiCustomization = PaymentAuthConfig.Stripe3ds2UiCustomization.Builder()
        .setLabelCustomization(
            PaymentAuthConfig.Stripe3ds2LabelCustomization.Builder()
                .setTextFontSize(12)
                .build()
        )
        .build()
    PaymentAuthConfig.init(
        PaymentAuthConfig.Builder()
            .set3ds2Config(
                PaymentAuthConfig.Stripe3ds2Config.Builder()
                    .setTimeout(5)
                    .setUiCustomization(uiCustomization)
                    .build()
            )
            .build()
    )

    Testing the 3D Secure flow

    Use a Stripe test card with any CVC, postal code, and future expiration date to trigger 3DS authentication challenge flows while in testmode.

    When you build an integration with your test API keys, the authentication process displays a mock authentication page. In that page, you can either authorize or cancel the payment. Authorizing the payment simulates successful authentication and redirects you to the specified return URL. Clicking on the Failure button simulates an unsuccessful attempt at authentication.

    Number 3D Secure usage Description
    4000000000003220 Required 3D Secure 2 authentication must be completed for the payment to be successful. By default, your Radar rules will request 3D Secure authentication for this card.
    4000000000003063 Required 3D Secure authentication must be completed for the payment to be successful. By default, your Radar rules will request 3D Secure authentication for this card.
    4000008400001629 Required 3D Secure authentication is required, but payments will be declined with a card_declined failure code after authentication. By default, your Radar rules will request 3D Secure authentication for this card.
    4000000000003055 Supported 3D Secure authentication may still be performed, but is not required. By default, your Radar rules will not request 3D Secure authentication for this card.
    4242424242424242 Supported 3D Secure is supported for this card, but this card is not enrolled in 3D Secure. This means that if 3D Secure is requested by your Radar rules, the customer will not go through additional authentication. By default, your Radar rules will not request 3D Secure authentication for this card.
    378282246310005 Not supported 3D Secure is not supported on this card and cannot be invoked. The PaymentIntent will proceed without performing authentication.
    Payment Method 3D Secure usage Description
    pm_card_threeDSecure2Required Required 3D Secure 2 authentication must be completed for the payment to be successful. By default, your Radar rules will request 3D Secure authentication for this card.
    pm_card_threeDSecureRequired Required 3D Secure authentication must be completed for the payment to be successful. By default, your Radar rules will request 3D Secure authentication for this card.
    pm_card_threeDSecureRequiredChargeDeclined Required 3D Secure authentication is required, but payments will be declined with a card_declined failure code after authentication. By default, your Radar rules will request 3D Secure authentication for this card.
    pm_card_threeDSecureOptional Supported 3D Secure authentication may still be performed, but is not required. By default, your Radar rules will not request 3D Secure authentication for this card.
    pm_card_visa Supported 3D Secure is supported for this card, but this card is not enrolled in 3D Secure. This means that if 3D Secure is requested by your Radar rules, the customer will not go through additional authentication. By default, your Radar rules will not request 3D Secure authentication for this card.
    pm_card_amex_threeDSecureNotSupported Not supported 3D Secure is not supported on this card and cannot be invoked. The PaymentIntent will proceed without performing authentication.

    All other Visa and Mastercard test cards do not require authentication from the customer’s card issuer.

    You can write custom Radar rules in test mode to trigger authentication on test cards. Learn more about testing your Radar rules.

    When testing your custom iOS integration, pick a test card to trigger a specific challenge flow.

    Number Challenge flow Description
    4000000000003220 Out of Band 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with Out of Band UI.
    4000000000003238 One Time Passcode 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with One Time Passcode UI.
    4000000000003246 Single Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with single-select UI.
    4000000000003253 Multi Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with multi-select UI.
    4000000000003063 Webview 3D Secure authentication must be completed for the payment to be successful. Triggers the challenge flow using a webview. You can check that your return_url dismisses the modal.

    All other Visa and Mastercard test cards do not require authentication from the customer’s card issuer.

    You can write custom Radar rules in test mode to trigger authentication on test cards. Learn more about testing your Radar rules.

    When testing your custom Android integration, pick a test card to trigger a specific challenge flow.

    Number Challenge flow Description
    4000000000003220 Out of Band 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with Out of Band UI.
    4000000000003238 One Time Passcode 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with One Time Passcode UI.
    4000000000003246 Single Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with single-select UI.
    4000000000003253 Multi Select 3D Secure 2 authentication must be completed on all transactions. Triggers the challenge flow with multi-select UI.
    4000000000003063 Webview 3D Secure authentication must be completed for the payment to be successful. Triggers the challenge flow using a webview. You can check that your return_url dismisses the modal.

    All other Visa and Mastercard test cards do not require authentication from the customer’s card issuer.

    You can write custom Radar rules in test mode to trigger authentication on test cards. Learn more about testing your Radar rules.

    Was this page helpful?

    Thank you for helping improve Stripe's documentation. If you need help or have any questions, please consider contacting support.

    On this page