> ## Documentation Index
> Fetch the complete documentation index at: https://docs.thedecard.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 3DS forwarding

> Adds the sequence diagrams and the webhook fields.

## 📄 Guide

Whether you are an exchange, a wallet or a platform, you can let the SMS, email or in-app notification channels you already run carry the online payment verification (3DS) of your cardholders, so that the last mile of delivering a passcode goes back to the channel you know best. As a licensed issuer, DCS handles the 3DS protocol exchange with the card network, generates and validates the passcode, and forwards whatever the verification needs to the partner securely.

3DS (3D Secure) is the security protocol for online payments: it adds one extra identity check on the cardholder during a transaction to reduce fraud losses. Under Partner-Managed, DCS offers **three forms of 3DS verification**, and the partner is free to choose how much of the cardholder experience it wants to own.

## The three modes at a glance

| Mode                                                   | Who sends the passcode                 | Who validates it                               | What the partner has to do                                                                                 | `flowsType` triggered |
| ------------------------------------------------------ | -------------------------------------- | ---------------------------------------------- | ---------------------------------------------------------------------------------------------------------- | --------------------- |
| Mode 1: DCS sends the passcode                         | DCS                                    | DCS                                            | **Nothing at all**                                                                                         | No webhook is sent    |
| Mode 2: the partner sends the passcode (delegated OTP) | Partner                                | DCS                                            | Expose a webhook to receive the OTP, decrypt it and deliver it to the user                                 | `OTP_DELEGATE`        |
| Mode 3: the partner verifies the user (OOB)            | Partner, inside its own app or channel | Partner, which then returns the outcome to DCS | Expose a webhook to receive the notification, take the user through authentication, and return the outcome | `OOB`                 |

> Which mode applies is set by your card program configuration. **Configuring the 3DS mode: DCS.** **Building the integration: the partner.** To change modes, contact the DCS team.

## The overall 3DS forwarding flow

The diagram below shows the full round trip of a 3DS verification request between the parties: DCS receives the request from the card network and forwards it to the partner, the partner completes verification in whichever mode applies and returns the outcome back along the same path, and DCS then sends a webhook with the challenge status update. Who sends the passcode and who decides the outcome differ by mode, as detailed below.

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-3ds-flow-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=0dc802d45ade1b6f768a2b427876e09c" alt="End-to-end 3DS forwarding sequence" width="638" height="536" data-path="imgs/en/diagrams/pa-3ds-flow-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-3ds-flow-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=bfd38e83c5aebcd79b84f5166ba5e537" alt="End-to-end 3DS forwarding sequence" width="638" height="536" data-path="imgs/en/diagrams/pa-3ds-flow-dark.svg" />
</Frame>

***

## Mode 1: DCS sends the passcode

DCS sends the SMS or email passcode to the cardholder and validates it. In this mode **the partner has nothing extra to build**: DCS handles generating, sending and validating the passcode, and sends no 3DS webhook.

Suitable for partners that have not built their own messaging channels yet and want the fastest possible launch.

***

## Mode 2: the partner sends the passcode (OTP\_DELEGATE)

The partner delivers the passcode to the cardholder over its own channel and DCS validates it. In this mode the partner must expose a webhook to receive the OTP that DCS pushes, decrypt it and forward it to the user.

<Frame>
  <img className="block dark:hidden" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-3ds-otp-delegate-light.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=c77d6d83d5788d7ba301720e117641cd" alt="Partner-sent passcode (OTP_DELEGATE) sequence" width="638" height="614" data-path="imgs/en/diagrams/pa-3ds-otp-delegate-light.svg" />

  <img className="hidden dark:block" src="https://mintcdn.com/dcs-0bf7a937/oUoeUKtwWWK2o-Li/imgs/en/diagrams/pa-3ds-otp-delegate-dark.svg?fit=max&auto=format&n=oUoeUKtwWWK2o-Li&q=85&s=742f69640ad7d8f7882e431142dffced" alt="Partner-sent passcode (OTP_DELEGATE) sequence" width="638" height="614" data-path="imgs/en/diagrams/pa-3ds-otp-delegate-dark.svg" />
</Frame>

### Webhook fields (3DS challenge notification)

3DS webhooks carry the type `AUTHORISATION_3DS_CHALLENGE`, and `flowsType` distinguishes the mode. In mode 2, `flowsType=OTP_DELEGATE`.

| Field                 | Type   | Description                                                                                    |
| --------------------- | ------ | ---------------------------------------------------------------------------------------------- |
| `challengeId`         | string | 3DS challenge ID                                                                               |
| `status`              | string | Challenge status: `INIT` / `NOTICED` / `RECEIVED` / `APPROVED` / `REJECTED`                    |
| `cardId`              | string | Card ID                                                                                        |
| `expiryTime`          | string | When the challenge expires                                                                     |
| `currency`            | string | Currency of the transaction                                                                    |
| `amount`              | number | Amount of the transaction                                                                      |
| `merchantId`          | string | Merchant ID                                                                                    |
| `merchantName`        | string | Merchant name                                                                                  |
| `merchantCountry`     | string | Merchant country                                                                               |
| `mcc`                 | string | Merchant category code                                                                         |
| `flowsType`           | string | Authentication flow type: `OOB` / `OTP_DELEGATE`                                               |
| `challengeMethodType` | string | Challenge method type: `DELEGATE_SCA_V1` / `SMS_OTP` / `EMAIL_OTP`                             |
| `otpPasscode`         | string | OTP passcode (**AES-GCM encrypted**), populated only when `flowsType=OTP_DELEGATE`             |
| `phoneNumber`         | string | Cardholder mobile number (**AES-GCM encrypted**), populated only when `flowsType=OTP_DELEGATE` |
| `email`               | string | Cardholder email (**AES-GCM encrypted**), populated only when `flowsType=OTP_DELEGATE`         |
| `iv`                  | string | IV for the AES-GCM encryption (Base64-encoded), populated only when `flowsType=OTP_DELEGATE`   |

> For webhook delivery, the outer envelope and signature verification in general, see [Webhook events and schema](../webhooks/events-and-schema).

### Decrypting the OTP (partner)

`otpPasscode`, `phoneNumber` and `email` are encrypted in AES-GCM mode with the partner's `secretKey`, and the webhook carries the `iv` alongside them. On receipt, the partner decrypts with `secretKey` and `iv`.

* **Algorithm**: AES-128-GCM (NoPadding)
* **Key**: the partner's `secretKey`, Base64-encoded
* **IV**: the `iv` field of the webhook, Base64-encoded, 12 bytes
* **Auth tag**: 128 bits, appended to the ciphertext
* **Ciphertext format**: Base64-encoded, decoding to `ciphertext + authTag`

```java theme={null}
import javax.crypto.Cipher;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.nio.charset.StandardCharsets;
import java.util.Base64;

public class AesGcmDecryptor {

    public static String decrypt(String base64Ciphertext, String base64Key, String base64IV) {
        byte[] key = Base64.getDecoder().decode(base64Key);
        byte[] iv = Base64.getDecoder().decode(base64IV);
        byte[] ciphertext = Base64.getDecoder().decode(base64Ciphertext);

        SecretKeySpec secretKey = new SecretKeySpec(key, "AES");
        GCMParameterSpec spec = new GCMParameterSpec(128, iv);

        Cipher cipher = Cipher.getInstance("AES/GCM/NoPadding");
        cipher.init(Cipher.DECRYPT_MODE, secretKey, spec);

        byte[] plaintext = cipher.doFinal(ciphertext);
        return new String(plaintext, StandardCharsets.UTF_8);
    }
}

// Usage example
String otpPasscode = decrypt(webhook.getOtpPasscode(), secretKey, webhook.getIv());
String phoneNumber = decrypt(webhook.getPhoneNumber(), secretKey, webhook.getIv());
String email       = decrypt(webhook.getEmail(), secretKey, webhook.getIv());
```

> In mode 2 **DCS still does the validating**: the partner is only responsible for getting the decrypted passcode to the user and does not return a verification result. The final outcome arrives in a webhook carrying `status=APPROVED` or `status=REJECTED`.

***

## Mode 3: the partner verifies the user (OOB)

With OOB (out-of-band authentication), the partner takes the cardholder through authentication inside its own app or another channel, using biometrics, in-app confirmation or similar. **The partner decides** whether the check passed and returns the outcome to DCS. In this mode the webhook carries `flowsType=OOB` and `challengeMethodType=DELEGATE_SCA_V1`, and **does not include** `otpPasscode`, `phoneNumber`, `email` or `iv`.

### Returning the authentication result (partner)

Once the partner has decided, call `POST /open-api/3ds-challenge/v1/authenticate` to return the outcome to DCS.

**Minimal request:**

```json theme={null}
{
  "challengeId": "<the challengeId pushed in the webhook>",
  "approveStatus": "APPROVE"
}
```

**Request fields:**

| Field           | Type   | Required | Description                                                                            |
| --------------- | ------ | :------: | -------------------------------------------------------------------------------------- |
| `challengeId`   | string |    Yes   | The 3DS challenge ID raised before the transaction, taken from the OOB webhook; max 50 |
| `approveStatus` | string |    Yes   | Challenge action: `APPROVE` / `REJECT`; max 20                                         |

**Response:** every `/open-api/` endpoint shares the same response envelope, `{ code, message, messageDetail, data }`. On success, `data` echoes the current state of the challenge:

```json theme={null}
{
  "code": "SYS_SUCCESS",
  "message": "success",
  "messageDetail": null,
  "data": {
    "challengeId": "CHL_1a2b...",
    "status": "APPROVED",
    "cardId": "CARD_8f3a...",
    "expiryTime": "2026-06-17T08:05:00Z",
    "currency": "USD",
    "amount": 100.00,
    "merchantId": "M_001",
    "merchantName": "Acme Store",
    "merchantCountry": "SG",
    "mcc": "5732"
  }
}
```

| Response field    | Description                                                                 |
| ----------------- | --------------------------------------------------------------------------- |
| `challengeId`     | 3DS challenge ID                                                            |
| `status`          | Challenge status: `INIT` / `NOTICED` / `RECEIVED` / `APPROVED` / `REJECTED` |
| `cardId`          | Card ID                                                                     |
| `expiryTime`      | When the challenge expires                                                  |
| `currency`        | Currency of the transaction                                                 |
| `amount`          | Amount of the transaction                                                   |
| `merchantId`      | Merchant ID                                                                 |
| `merchantName`    | Merchant name                                                               |
| `merchantCountry` | Merchant country                                                            |
| `mcc`             | Merchant category code                                                      |

> About the envelope: judge success or failure by `code` alone (for example an echoed `SYS_SUCCESS`; for the error code conventions see [Authorization declines and error codes](./decline-codes)). `message` is a short developer-facing string, and `messageDetail` is an optional display object (with `message` / `title` / `type` / `icon` / `action` / `linkTitle` / `linkUrl`) meant for front-end guidance only, never as the basis for deciding the outcome.

<Warning>
  Return your result before `expiryTime`. That deadline comes from the challenge validity the 3DS provider issues; if nothing arrives in time, DCS sets the challenge to `REJECTED` and never auto-approves.
</Warning>

***

## Integration notes

* **Content-Type**: set `Content-Type: application/json` explicitly on every `/open-api/` request.
* **Authentication**: calling `/open-api/3ds-challenge/v1/authenticate` and verifying the signature on inbound webhooks both follow the [Authentication guide](../../integration-resources/authentication).
* **Webhook endpoint**: modes 2 and 3 both require the partner to configure, in advance, a webhook address able to receive `AUTHORISATION_3DS_CHALLENGE`. The partner supplies that address to DCS.
* **Mode 3 deadline**: the result must be returned before the `expiryTime` issued by the provider; an overdue challenge is set to `REJECTED`.
* **When delegated OTP fails**: in mode 2, DCS waits synchronously for the partner webhook for at most 7 seconds. On timeout, a failure response, a decryption failure or a delivery failure, there is no resend and no fallback to DCS sending the passcode: the current challenge goes straight to `REJECTED`. The partner must decrypt, deliver and return `phoneNumberTail` or `email` synchronously within those 7 seconds.

## Next steps

Once 3DS verification passes, the transaction moves into authorization. Go to [Authorization](./authorization) to see how the decision is forwarded to the partner, then read [Authorization and settlement](./auth-and-settlement) to understand how funds are held and finally settled.
