> For the complete documentation index, see [llms.txt](https://docs.payments.thalescloud.io/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.payments.thalescloud.io/3d-secure/implement-3ds/implement-oob-flow-thales-authentication/handle-the-oob-challenge.md).

# Handle the OOB challenge

## Overview

The 3DS service triggers the OOB challenge flow when a transaction is challenged and the end user has an enrolled authenticator in the issuer application.

## User experience

<figure><img src="/files/kY3aaNXV7CnJTPJ3F9Rh" alt=""><figcaption></figcaption></figure>

## Flow

<figure><img src="/files/ABsRbDS3lhChbEMDB0LY" alt=""><figcaption><p>OOB challenge flow overview - step 1.</p></figcaption></figure>

<figure><img src="/files/fW4Vf1UYTDqAPFQavdpF" alt=""><figcaption><p>OOB challenge flow overview - step 2.</p></figcaption></figure>

## Sequence diagram – Backend flow

The diagrams below show the end‑to‑end sequence for an OOB challenge flow.

### Prerequisites

* Card products are configured in the D1 backend and in the payment network directory server.
* The end user and card are registered in the D1 backend .
* An authenticator was enrolled in the device.
* The D1 SDK is initialized.
* The issuer application has push notifications configured.

***

### 1 – AReq/ARes and first CReq/CRes

<figure><img src="/files/ZQNvgL6l4rQ9CoualwrT" alt=""><figcaption><p>AReq/ARes and initial CReq/CRes.</p></figcaption></figure>

### 2 – CReq/CRes and OOB challenge from the issuer

<figure><img src="/files/OZorAAGpUvqnsAsOEoZQ" alt=""><figcaption><p>CReq/CRes and OOB challenge from the issuer.</p></figcaption></figure>

For SDK implementation details, see [SDK flow](/3d-secure/implement-3ds/implement-oob-flow-thales-authentication/handle-the-oob-challenge.md#sequence-diagram-sdk-flow).

### 3 – Final CReq/CRes and notification

<figure><img src="/files/Knm2FmIREtVz2VYbx9R8" alt=""><figcaption><p>Final CReq/CRes and notification.</p></figcaption></figure>

***

## Sequence diagram – SDK flow

The sequence below shows how the issuer application uses the D1 SDK to complete the OOB challenge.

### 1 - Issuer application fetches FIDO challenge via D1 SDK

<figure><img src="/files/55tZK94eKjfvO8OB7z2h" alt=""><figcaption><p>Issuer Application fetches the FIDO challenge via the D1 SDK.</p></figcaption></figure>

### 2 - D1 SDK authenticates the end user

<figure><img src="/files/KHlEgAgVjKW9tAgnRPC1" alt=""><figcaption><p>D1 SDK authenticates the end user.</p></figcaption></figure>

For push notifications, refer to [push notification configuration](/3d-secure/integrate-d1-sdk/get-started/4.-push-notification-configuration.md).

### Transaction confirmation data

The following table outlines the transaction data that the D1 SDK provides to the issuer application for display purposes.

| Parameter name         | Description                                                                                                                                                                                                                                  |
| ---------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| acsTransId             | A universally unique transaction identifier that is assigned by ACS to identify a single transaction.                                                                                                                                        |
| purchaseDate           | Date and time of the purchase, expressed in UTC time format.                                                                                                                                                                                 |
| purchaseExponent       | Minor units of currency as specified in the ISO 4217 currency exponent.                                                                                                                                                                      |
| threeDSRequestorAppURL | 3DS Requestor application declaring its URL within the CReq message so the authentication application can call the 3DS Requestor app after OOB authentication completes. Use this to navigate the End User back to the merchant application. |
| purchaseCurrency       | The currency in which the purchase amount is expressed in ISO 4217 format.                                                                                                                                                                   |
| merchantName           | The merchant name assigned by the Acquirer or Payment Network.                                                                                                                                                                               |
| purchaseAmount         | The amount in minor units of currency without any decimals.                                                                                                                                                                                  |

## D1 SDK integration

{% hint style="info" %}

* Calling `login` is not required as a prerequisite for this use case.
* If the issuer application does not receive a verification request via push notification, it can fetch the request by calling the D1 SDK `fetchAuthnRequest` API.
* The SDK uses strong biometric authentication. If multiple biometric methods are available (for example, Face ID and fingerprint), the operating system manages the user interface.
  {% endhint %}

{% tabs %}
{% tab title="Android Java" %}

```java
static String lastProcessedTransactionId = "";
public void challengeOOBFlow(
        @NonNull D1Task d1Task,
        @NonNull FragmentActivity activity,
        @NonNull CountDownLatch transactionWaiter) throws D1Exception {
    // Implement the interface conforming to the AuthnCallback
    D1Authn d1Authn = d1Task.getD1Authn(activity, new AuthnCallback(){
        @Override
        public void onTransactionDataConfirmation(@NonNull Map<String, String> map, @NonNull AuthnUserConfirmationCallback authnUserConfirmationCallback) {
            //Pass the transaction handler that checks the last processed transaction id
            try {
                transactionHandler(map, transactionWaiter, authnUserConfirmationCallback);
            } catch (InterruptedException e) {
                //Process the exception
            }
        }

        @NonNull
        @Override
        public String onBiometricPromptMessage() {
            // Provide a message prompt for the authentication prompt. Application can customize the value.
            return "Message to be displayed to the user during biometric prompt";
        }
    });

    d1Authn.fetchAuthnRequest(new D1Task.Callback<Void>() {
        @Override
        public void onSuccess(Void unused) {
            // Proceed with subsequent flows after end user verification.
            transactionWaiter.countDown();
        }

        @Override
        public void onError(@NonNull D1Exception e) {
            // Refer to D1 SDK Integration – Error Management section.
            transactionWaiter.countDown();
        }
    });
}

public synchronized void transactionHandler(
        @NonNull Map<String, String> map,
        @NonNull CountDownLatch transactionWaiter,
        @NonNull AuthnUserConfirmationCallback authnUserConfirmationCallback) throws InterruptedException {
    // The 'map' contains the transaction data that is to be authenticated.
    //Example Map :
    // {
    //    "acsTransId": "2f37539c-d82e-4929-88db-2f9e72c7fa62",
    //    "merchantName": "CoffeeHouse2 (Dæmø)",
    //    "purchaseAmount": "100",
    //    "purchaseCurrency": "840",
    //    "purchaseExponent": "2",
    //    "purchaseDate": "20231017055638"
    // }

    //Get the current transaction Id and compare to last process transaction id. If same no need to handle the transaction and proceed to cancel the transaction
    if(map.get("acsTransId") != lastProcessedTransactionId) {
        lastProcessedTransactionId = map.get("acsTransId");
        JSONObject json = new JSONObject();
        for (Map.Entry<String, String> entry : map.entrySet()) {
            try {
                json.put(entry.getKey(), entry.getValue());
            } catch (JSONException ignored) {}
        }

        // Application to display the content, prompt end uer either to 'proceed' or to 'deny' the authentication request.

        // To proceed: when uer selects proceed
        // once proceed, UI will be prompted for authentication based on the selected AuthnType enrolled.
        authnUserConfirmationCallback.proceed();

        // To cancel: when user selects cancel
        authnUserConfirmationCallback.cancel();
    } else {
        if(transactionWaiter.await(60, TimeUnit.SECONDS)) {
            //Mo need to handle the transaction and proceed to cancel the transaction
            authnUserConfirmationCallback.cancel();
        }
    }
}
```

{% endtab %}

{% tab title="Android Kotlin" %}

```kotlin
companion object {
    var lastProcessedTransactionId = ""
}
fun challengeOOBFlow(d1Task: D1Task, activity: FragmentActivity, transactionWaiter: CountDownLatch) {
    // Implement the interface conforming to the AuthnCallback
    val d1Authn: D1Authn = d1Task.getD1Authn(activity, object : AuthnCallback {
        override fun onTransactionDataConfirmation(
            map: Map<String, String>,
            authnUserConfirmationCallback: AuthnUserConfirmationCallback
        ) {
            // Pass the transaction handler that checks the last processed transaction id
            try {
                transactionHandler(map, transactionWaiter, authnUserConfirmationCallback)
            } catch (e: InterruptedException) {
                // Process the exception
            }
        }

        override fun onBiometricPromptMessage(): String {
            // Provide a message prompt for the authentication prompt. Application can customize the value.
            return "Message to be displayed to the user during biometric prompt"
        }
    })

    d1Authn.fetchAuthnRequest(object : D1Task.Callback<Void?> {
        override fun onSuccess(unused: Void?) {
            // Proceed with subsequent flows after end user verification.
            transactionWaiter.countDown()
        }

        override fun onError(e: D1Exception) {
            // Refer to D1 SDK Integration – Error Management section.
            transactionWaiter.countDown()
        }
    })
}

@Synchronized
@Throws(InterruptedException::class)
fun transactionHandler(
    map: Map<String, String>,
    transactionWaiter: CountDownLatch,
    authnUserConfirmationCallback: AuthnUserConfirmationCallback
) {
    // The 'map' contains the transaction data that is to be authenticated.
    // Example Map:
    // {
    //    "acsTransId": "2f37539c-d82e-4929-88db-2f9e72c7fa62",
    //    "merchantName": "CoffeeHouse2 (Dæmø)",
    //    "purchaseAmount": "100",
    //    "purchaseCurrency": "840",
    //    "purchaseExponent": "2",
    //    "purchaseDate": "20231017055638"
    // }

    // Get the current transaction Id and compare to last processed transaction id. If same no need to handle the transaction and proceed to cancel the transaction
    val currentTransactionId = map["acsTransId"]
    if (currentTransactionId != lastProcessedTransactionId) {
        // Last processed transaction ID is used as a variable to maintain its value since it can't be modified as is in Kotlin
        lastProcessedTransactionId = currentTransactionId ?: ""

        val json = JSONObject()
        for ((key, value) in map) {
            try {
                json.put(key, value)
            } catch (ignored: JSONException) {
                // Ignore exceptions during JSON insertion
            }
        }

        // Application to display the content, prompt end user either to 'proceed' or to 'deny' the authentication request.

        // To proceed: when user selects proceed
        authnUserConfirmationCallback.proceed()

        // To cancel: when user selects cancel (this part should be handled in the UI based on user interaction)
        authnUserConfirmationCallback.cancel()
    } else {
        if (transactionWaiter.await(60, TimeUnit.SECONDS)) {
            // No need to handle the transaction and proceed to cancel the transaction
            authnUserConfirmationCallback.cancel()
        }
    }
}
```

{% endtab %}

{% tab title="iOS" %}

```swift
public class AuthnConformer {
    weak var viewController: UIViewController?
    private var lastTransaction:[String:String] = [:]
}

extension AuthnConformer: AuthnDelegate {
    public func authn(_ authn: D1Authn, shouldConfirmTransactionData transactionData: [String : String], proceedHandler: (@escaping() -> Void), cancelHandler: (@escaping() -> Void)) {
        // The 'map' contains the transaction data that is to be authenticated. 

        //Example Map : 
        // {
        //    "acsTransId": "2f37539c-d82e-4929-88db-2f9e72c7fa62",
        //    "merchantName": "CoffeeHouse2 (Dæmø)",
        //    "purchaseAmount": "100",
        //    "purchaseCurrency": "840",
        //    "purchaseExponent": "2",
        //    "purchaseDate": "20231017055638"
        // }


        if (lastTransaction != transaction) {
            var message = ""
            if let json = try? JSONSerialization.data(withJSONObject: transactionData, options: .prettyPrinted),
               let jsonString = String(data: json, encoding: .utf8) {
                message = jsonString
            }

            // Application to display the content, prompt end uer either to 'proceed' or to 'deny' the authentication request.
          
            // To proceed: when uer selects proceed
            // once proceed, UI will be prompted for authentication based on the selected AuthnType enrolled.

            let alertController = UIAlertController(title: "Transaction Details", message: message, preferredStyle: .alert)
            alertController.addAction(UIAlertAction(title: "Proceed", style: .default, handler: { action in
                proceedHandler()
            }))
        alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: {action in
                cancelHandler()
            }))
            viewController?.present(alertController, animated: true)
            lastTransaction = transaction
        }
        else {
            cancelHandler()
        }
    }

    public func authnTouchIDOperationPrompt(_ authn: D1Authn) -> String {
        //Does not matter. Unenrollment is done without authentication 
    }
}
let authnConformer = AuthnConformer()
authnConformer.viewController = self?.window?.rootViewController
let d1Authn = d1Task.d1Authn(authnConformer)

d1Authn.fetchAuthnRequest({ error in
    if let error = error {
        // Refer to D1 SDK Integration – Error Management section.
    } else {
        // Proceed with subsequent flows after end user verification.
    }
})
```

{% endtab %}
{% endtabs %}

The example below shows how the Issuer Application uses `threeDSRequestorAppURL` from the transaction confirmation data to return the end user to the merchant application.

{% tabs fullWidth="false" %}
{% tab title="Android Java" %}

```java
public interface ITransactionConfirmation {
    void transactionConfirmationPayload(@NonNull String threeDSRequestorAppURL);
}

public D1Authn configureAuthn(@NonNull D1Task d1Task, @NonNull FragmentActivity activity, @NonNull ITransactionConfirmation transactionConfirmation) throws D1Exception {
    D1Authn d1Authn = d1Task.getD1Authn(activity, new AuthnCallback() {
        @Override
        public void onTransactionDataConfirmation(@NonNull Map<String, String> map, @NonNull AuthnUserConfirmationCallback authnUserConfirmationCallback) {
            String appUrl = map.get("threeDSRequestorAppURL");
            transactionConfirmation.transactionConfirmationPayload((appUrl == null)? "": appUrl);

            //Show UI to the user to proceed

            //If proceed, call:
            authnUserConfirmationCallback.proceed();

        }

        @NonNull
        @Override
        public String onBiometricPromptMessage() {
            return "biometricPromtMessage";
        }
    });
    return d1Authn;
}

public void fetchAllRequest(@NonNull D1Task d1Task, @NonNull FragmentActivity activity) throws D1Exception {
    final String[] threeDSRequestorAppURL = new String[1];
    D1Authn d1Authn = configureAuthn(d1Task, activity, new ITransactionConfirmation() {
        @Override
        public void transactionConfirmationPayload(@NonNull String url3DS) {
            threeDSRequestorAppURL[0] = url3DS;
        }
    });

    d1Authn.fetchAuthnRequest(new D1Task.Callback<Void>() {
        @Override
        public void onSuccess(Void unused) {
            launchMerchantApp(threeDSRequestorAppURL[0], activity);
        }

        @Override
        public void onError(@NonNull D1Exception e) {
            // Refer to D1 SDK Integration – Error Management section.
            launchMerchantApp(threeDSRequestorAppURL[0], activity);
        }
    });
}

public void launchMerchantApp(@NonNull String threeDSRequestorAppURL, @NonNull FragmentActivity activity) {
    if (!threeDSRequestorAppURL.isEmpty()) {
        Uri uri = Uri.parse(threeDSRequestorAppURL);

        // Create an Intent to open the URI in a browser or an app that can handle the URI
        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
        activity.startActivity(intent);
    } else {
        Toast.makeText(activity, "No transaction Requests available", Toast.LENGTH_SHORT).show();
    }
}
```

{% endtab %}

{% tab title="Android Kotlin" %}

```kotlin
interface ITransactionConfirmation {
    fun transactionConfirmationPayload(threeDSRequestorAppURL: String)
}

@Throws(D1Exception::class)
fun configureAuthn(d1Task: D1Task, activity: FragmentActivity, transactionConfirmation: ITransactionConfirmation): D1Authn {
    return d1Task.getD1Authn(activity, object : AuthnCallback {
        override fun onTransactionDataConfirmation(map: Map<String, String>, authnUserConfirmationCallback: AuthnUserConfirmationCallback) {
            transactionConfirmation.transactionConfirmationPayload(map["threeDSRequestorAppURL"] ?: "")

            // Show UI to the user to proceed
            // If proceed, call:
            authnUserConfirmationCallback.proceed()
        }

        override fun onBiometricPromptMessage(): String {
            return "biometricPromptMessage"
        }
    })
}

@Throws(D1Exception::class)
fun fetchAllRequest(d1Task: D1Task, activity: FragmentActivity) {
    var threeDSRequestorAppURL : String = ""
    val d1Authn = configureAuthn(d1Task, activity, object : ITransactionConfirmation {
        override fun transactionConfirmationPayload(url3DS: String) {
            threeDSRequestorAppURL = url3DS
        }
    })

    d1Authn.fetchAuthnRequest(object : D1Task.Callback<Void?> {
        override fun onSuccess(unused: Void?) {
            launchMerchantApp(threeDSRequestorAppURL, activity)
        }

        override fun onError(e: D1Exception) {
            // Refer to D1 SDK Integration – Error Management section.
        }
    })
}

fun launchMerchantApp(threeDSRequestorAppURL: String, activity: FragmentActivity) {
    if (threeDSRequestorAppURL.isNotEmpty()) {
        val uri = Uri.parse(threeDSRequestorAppURL)

        // Create an Intent to open the URI in a browser or an app that can handle the URI
        val intent = Intent(Intent.ACTION_VIEW, uri)
        activity.startActivity(intent)
    } else {
        Toast.makeText(activity, "No transaction Requests available", Toast.LENGTH_SHORT).show()
    }
}
```

{% endtab %}

{% tab title="iOS" %}

```swift
// 'threeDSRequestorAppURL' is received from AuthnDelegate's authnDelegate.authn callback method.
// It will be received as transactionData: [String : String].
public class MAuthnDelegate: AuthnDelegate {
    var threeDSRequestorAppURL: String?
    public func authn(_ authn: D1Authn, shouldConfirmTransactionData transactionData: [String : String], proceedHandler: (@escaping() -> Void), cancelHandler: (@escaping() -> Void)) {
        // Extract threeDSRequestorAppURL from transactionData
        if let url3DS = transactionData["threeDSRequestorAppURL"] {
            threeDSRequestorAppURL = url3DS
        }
        //Show UI to the user to proceed

        //If proceed, call:
        proceedHandler()

    }
}

private func launchMerchantApp(_ threeDSRequestorAppURL: String) {
    if let appURL = URL(string: threeDSRequestorAppURL) {
        UIApplication.shared.open(appURL, options: [:], completionHandler: nil)
    }
}

//Register AuthnDelegate to d1Task at initialization
d1Task.d1Authn(mAuthnDelegate)

//Call fetchAuthnRequest API to receive the authn delegate callback.
d1Authn.fetchAuthnRequest { error in
  if error == nil {
      //launch merchant app with fetched url.
      self.launchMerchantApp(mAuthnDelegate.threeDSRequestorAppURL)
  }
}
```

{% endtab %}
{% endtabs %}

## Issuer backend integration

The issuer backend is notified at the end of the processing flow through the [Notify 3DS Card Operation](/3d-secure/integrate-d1-api/d1-api-reference/outbound-api-from-d1/3ds-api.md#post-notifications-d1-v1-issuers-issuerid-cards-cardid-3ds-notifications) API.


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://docs.payments.thalescloud.io/3d-secure/implement-3ds/implement-oob-flow-thales-authentication/handle-the-oob-challenge.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
