Important
We have released a new version. Documentation for our latest version can be found [here](/access/products/3ds/).

**Last updated**: 22 April 2025 | [**Change log**](/access/products/3ds/changelog/)

# Challenge Display and Verification

If the authentication response `outcome` is `challenged` and the `authentication.version` is `2.x.x` (3DS2) you can use the SDK to provide the improved [3DS2 Challenge display](#3ds2-challenge-display) for mobile devices.

If the `authentication.version` is `1.x.x` (3DS1) you must follow the [3DS1 Challenge display](#3ds1-challenge-display). The integration more closely follows the steps for web.

## 3DS2 Challenge display

If the `authentication.version` is `2.x.x` you will need the following values from the authentication response to use in the SDK.

| Access Name | Value from | Cardinal SDK Name |
|  --- | --- | --- |
| `challenge.reference` | authentication response | `transactionId` |
| `challenge.payload` | authentication response | `payload` |


The Access 3DS API will be periodically tested against the latest version of the Cardinal SDK. Current tested Cardinal SDK version: `v2.2.5`

### SDK challenge display:

- [Android](https://cardinaldocs.atlassian.net/wiki/spaces/CMSDK/pages/1998914544/Handle+the+Centinel+Lookup+Response+and+SDK+Handle+the+Challenge+UI+-+Android+-+V+2.2.5)
- [iOS](https://cardinaldocs.atlassian.net/wiki/spaces/CMSDK/pages/2005696790/Handle+the+Centinel+Lookup+Response+and+SDK+handle+the+Challenge+UI+-+iOS+-+V+2.2.5)


### Customize Challenge Interface

As part of SDK setup you can customize the challenge user interface

- [Android](https://cardinaldocs.atlassian.net/wiki/spaces/CMSDK/pages/1998914483/Challenge+User+InterfaceCustomization+Android-+V+2.2.5)
- [iOS](https://cardinaldocs.atlassian.net/wiki/spaces/CMSDK/pages/2005696657/Challenge+User+Interface+Customization+iOS+V+2.2.5)


## 3DS1 Challenge display

If the `authentication.version` is `1.x.x` use the following steps to display the challenge screen.

Note
3DS version 1 was implemented by issuers long before smartphones existed so offers a degraded experience compared to version 2. We expect version 1 traffic from issuers to drop during 2021.

#### Challenge form (webView)

POST the request to the `challenge.url` with the `challenge.jwt` and optional `MD`.

The `MD` field allows you to pass url parameters (max 1024 characters) in the challenge form that is included/echoed in the response url (`challenge.returnUrl`).

details
summary
iOS
Add a WKWebView to your Storyboard and UIViewController and enable JavaScript


```
@IBOutlet var webView: WKWebView!
override func viewDidLoad() {
    super.viewDidLoad()
    webView.configuration.preferences.javaScriptEnabled = true
}
```

Implement a method similar to the code snippet below in your UIViewController


```
let iframeSrcDoc = """
<html>
  <body onload='document.frmLaunch.submit();'>
    <form name='frmLaunch' method='POST' action='\(challengeUrl)'>
      <input type='hidden' name='JWT' value='\(jwt)'>
      <input type='hidden' name='MD' value='\(md)'>
    </form>
  </body>
</html>
"""
// A viewport meta tag is used to scale the content nicely to the device's screen size
let html = """
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <iframe srcdoc="\(iframeSrcDoc)" width="100%" height="400" frameborder="0">
    </iframe>
  </body>
</html>
"""
webView.loadHTMLString(html, baseURL: URL(string: "about:srcdoc")!)
```

details
summary
Android
Add a WebView to your fragment and enable JavaScript

Kotlin

```
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    val webView = view.findViewById<WebView>(...)
    webView.settings.javaScriptEnabled = true
}
```

Java

```
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
    super.onViewCreated(view, savedInstanceState);
    WebView webView = view.findViewById(...);
    webView.getSettings().setJavaScriptEnabled(true);
}
```

Implement a method similar to the code snippet below in your Fragment

Kotlin

```
val iframeSrcDoc = """
<html>
  <body onload='document.frmLaunch.submit();'>
    <form name='frmLaunch' method='POST' action='$challengeUrl'>
      <input type='hidden' name='JWT' value='$jwt'>
      <input type='hidden' name='MD' value='$md'>
    </form>
  </body>
</html>
"""
// A viewport meta tag is used to scale the content nicely to the device's screen size
val html = """
<html>
  <head>
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <iframe srcdoc="$iframeSrcDoc" width="100%" height="400" frameborder="0">
    </iframe>
  </body>
</html>
"""
webView.loadData(html, "text/html; charset=utf-8", "UTF-8")
```

Java

```
String challengeUrl = "add challengeUrl here";
String iframeSrcDoc = "<html>" +
        "<body onload='document.frmLaunch.submit();'>" +
        "<form name='frmLaunch' method='POST' action='" + challengeUrl + "'>" +
        "<input type='hidden' name='JWT' value='" + jwt + "'>" +
        "<input type='hidden' name='MD' value='" + md + "'>" +
        "</form>" +
        "</body>" +
        "</html>";
// A viewport meta tag is used to scale the content nicely to the device's screen size
String html = "<html>" +
        "<head>" +
        "<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">" +
        "</head>" +
        "<iframe srcdoc=\"" + iframeSrcDoc + "\" width=\"100%\" height=\"400\" frameborder=\"0\">" +
        "</iframe>" +
        "</body>" +
        "</html>";
webView.loadData(html, "text/html; charset=utf-8", "UTF-8");
```

#### Intercept challenge return

Intercept the POST request to your `challenge.returnUrl`

details
summary
iOS
Have your UIViewController adopt the WKNavigationDelegate protocol

Point the navigationDelegate property of your WKWebView to your UIViewController, just before the call to webView.loadHTMLString


```
webView.navigationDelegate = self
```

Implement the following method in your UIViewController to be notified when the challenge has been completed


```
func webView(_ webView: WKWebView, decidePolicyFor navigationAction: WKNavigationAction, decisionHandler: @escaping (WKNavigationResponsePolicy) -> Void) {
     if navigationAction.request.url!.absoluteString == "<value of your challenge return url>" {
        // TODO: add your logic
     }
     decisionHandler(.allow)
}
```

details
summary
Android

Intercept the POST request to your challenge return url

Create a class that extends WebViewClient and override the onLoadResource method to be notified when the challenge has been completed

Kotlin

```
private class CustomWebViewClient: WebViewClient() {
    override fun onLoadResource(view: WebView?, url: String?) {
        if (url.toString() == "<value of your challenge return url>") {
            // TODO: add your logic
        }
        super.onLoadResource(view, url)
    }
}
```

Java

```
private static class CustomWebViewClient extends WebViewClient {
    @Override
    public void onLoadResource(WebView view, String url) {
        if (url.equals("<value of your challenge return url>")) {
            // TODO: add your logic
        }
        super.onLoadResource(view, url);
    }
}
```

Set the webViewClient property of your WebView to an instance of this class, just before the call to webView.loadData()

Kotlin

```
webView.webViewClient = CustomWebViewClient()
```

Java

```
webView.setWebViewClient(new CustomWebViewClient());
```

# Verification

Once the challenge has been completed, a verification request needs to be made to verify the result of the challenge.

Important
You should only request the verification from your backend system, not call it directly from the mobile application using the Access credentials.

POST your verification request to our `3ds:verify` action link received in your authentication response.

## Verification example request

Note
You must use `v2` and later of the API for the Android/iOS SDK

POST `https://try.access.worldpay-bsh.securedataplatform.com/verifications/customers/3ds/verification`

Verification request body:


```json
{
    "transactionReference": "unique-transactionReference",
    "merchant": {
        "entity": "default"
    },
    "challenge": {
        "reference": "123456789"
    }
}
```

## Verification responses

Here are examples of the verification responses you would receive.

Authenticated

```json
{
    "outcome": "authenticated",
    "transactionReference": "unique-transactionReference",
    "authentication": {
        "version": "2.1.0",
        "authenticationValue": "MAAAAAAAAAAAAAAAAAAAAAAAAAA=",
        "eci": "05",
        "transactionId": "c5b808e7-1de1-4069"
    }
}
```

Authentication Failed

```json
{
    "outcome": "authenticationFailed",
    "transactionReference": "unique-transactionReference",
    "authentication": {
      "version": "1.0.2",
      "eci": "00",
      "transactionId": "N+en2I5+ZK/kQqk69wXdI8XIPg8="
    },
    "_links": {
        "3ds:authenticate": {
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/verifications/customers/3ds/authentication"
        },
        "curies": [{
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/rels/verifications/customers/3ds/{rel}",
            "templated": true,
            "name": "3ds"
        }]
    }
}
```

Signature Failed

```json
{
    "outcome": "signatureFailed",
    "transactionReference": "unique-transactionReference",
    "authentication": {
      "version": "1.0.2",
      "eci": "02"
    },
    "_links": {
        "3ds:authenticate": {
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/verifications/customers/3ds/authentication"
        },
        "curies": [{
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/rels/verifications/customers/3ds/{rel}",
            "templated": true,
            "name": "3ds"
        }]
    }
}
```

Unavailable

```json
{
    "outcome": "unavailable",
    "transactionReference": "unique-transactionReference",
    "_links": {
        "3ds:authenticate": {
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/verifications/customers/3ds/authentication"
        },
        "3ds:verify": {
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/verifications/customers/3ds/verification"
        },
        "curies": [{
            "href": "https://try.access.worldpay-bsh.securedataplatform.com/rels/verifications/customers/3ds/{rel}",
            "templated": true,
            "name": "3ds"
        }]
    }
}
```

Bypassed

```json
{
  "outcome": "bypassed",
  "transactionReference": "6032c024-8d33-4e89-98e9-a944f66c3906",
  "authentication": {
    "version": "2.1.0",
    "eci": "00",
    "transactionId": "c5b808e7-1de1-4069-a17b-f70d3b3b1645"
  }
}
```

Warning
You cannot re-use the `authenticationValue` in multiple authorization requests. Re-using the `authenticationValue` in this way will result in issuer declines and may incur fees.

Use the values: `version`, `authenticationValue`, `eci`, `transactionId` from the request when [authorizing a payment](/access/products/card-payments/v6/authorize-a-payment#authorize-a-payment). The values prove that the verification was successful, and that the fraud liability has shifted to the issuer.

| Parameter | Description |
|  --- | --- |
| `authentication.version` | The version of 3DS used to process the transaction. Required for Mastercard's Identity Check transactions in Authorization. |
| `authentication.authenticationValue` | A cryptographic value that provides evidence of the outcome of a 3DS verification.Visa - Cardholder Authentication Verification Value (CAVV)Mastercard - Universal Cardholder Authentication Field (UCAF)Used when [authorizing a payment](/access/products/card-payments/v6/authorize-a-payment). |
| `authentication.eci` | Electronic Commerce Indicator (ECI).Indicates the outcome of the 3DS authentication.02 or 05 - Fully Authenticated Transaction01 or 06 - Attempted Authentication Transaction00 or 07 - Non 3-D Secure TransactionMastercard - 02, 01, 00Visa - 05, 06, 07Amex - 05, 06, 07JCB - 05, 06, 07Diners - 05, 06, 07 You will need to use this when you are [authorizing a payment](/access/products/card-payments/v6/authorize-a-payment). |
| `authentication.transactionId` | A transaction identifier.If provided, you should use it as part of your [payment authorization](/access/products/card-payments/v6/authorize-a-payment).If the `authentication.version` has a major version of:`1` - value returned known as `xid``2` - value returned known as `dsTransactionId` |


**Next steps**

[Take a payment](/access/products/card-payments/v6/authorize-a-payment#3ds)