# 3DS

### **3D Secure (3DS) Testing Guide**

This guide explains how to test **3DS initialize and authentication flows** in a controlled environment. You can simulate various responses, including **challenge flows**, **frictionless flows**, and **HTTP error scenarios**, to see how your application behaves.

Testing can be performed in two ways:

1. **Mock Card Testing** – using static card IDs that simulate specific responses.
2. **Sandbox Testing** – using test PANs in a sandbox environment.

### **General Setup**

* **Content-Type Header:** `application/vnd.api+json`
* **HTTP Method:** `POST`
* Each request includes a unique `merchant_transaction_id` to track transactions.
* Initialize Responses typically include a `device_fingerprinting_html` (device fingerprinting) that must be submitted to the ACS.
* Challenge responses usually contain a **Challenge Form** which is submitted to display the challenge questionnaire to the user.
* Asynchronous notifications (**webhooks**) are sent once device fingerprinting and the user’s authentication challenge are completed and processed by the ACS.

### 3DS Initialize API - **Mock Card Testing**

Test different 3DS scenarios without using real cards. Each static `cardID` returns a predefined response (success, frictionless, challenge, or error). When using these mock card IDs, you do *not* need 3DS enabled on your account and you do *not* need to generate a JWT auth token. Simply replace `{cardID}` with one of the static mock values.&#x20;

**Base URL:**&#x20;

```
https://gw-01-sandbox.vgsapi.com/3ds-mocks
```

**Initialize API Endpoint:**

```
POST /cards/{cardID}/3ds-initialize
```

**Example Request:**

```bash
curl -X POST "https://gw-01-sandbox.vgsapi.com/3ds-mocks/cards/CRAbcDefGhijKlmNoPqrStuVw/3ds-initialize" \
-H "Content-Type: application/vnd.api+json"
```

<table><thead><tr><th width="204.86419677734375">Test cardID</th><th width="185.6978759765625">Expected HTTP Status</th><th>Expected Response / Notes</th></tr></thead><tbody><tr><td>CRAbcDefGhijKlmNoPqrStuVw</td><td>200 OK</td><td><p>Returns the synchronous response with the iframe/data needed to proceed with authentication. </p><p></p><p><strong>Please note:</strong> rendering the iframe from this mock response will not trigger issuer fingerprinting or send the async notification. To trigger device fingerprinting and receive the async notification, you must submit the iframe on your checkout HTML using sandbox cards.</p></td></tr><tr><td>CRnOiFrAmEqDxRgXU3TFmtcD</td><td>206 OK</td><td>Returns a synchronous response without iframe/data, confirming that initialization is not required and the authentication process can proceed immediately.</td></tr><tr><td>CRaZcXeVtBrNnMpLoKjIhUgYt</td><td>400 Bad Request</td><td>Simulates a 400 error from the initialize endpoint.</td></tr><tr><td>CRdFhJkLmNoPqRsTuVwXyZ012</td><td>401 Unauthorized</td><td>Simulates a 401 error from the initialize endpoint.</td></tr><tr><td>CRpOiUytRewQAzXswEdcRfVtB</td><td>403 Forbidden</td><td>Simulates a 403 error from the initialize endpoint.</td></tr><tr><td>CRuYtRewQAzWsXeDcRfVtGbYh</td><td>404 Not Found</td><td>Simulates a 404 error from the initialize endpoint.</td></tr><tr><td>CRiKoLpMnBvCxZaQsWeRdTfYg</td><td>422 Unprocessable Entity</td><td>Simulates a 422 error from the initialize endpoint.</td></tr><tr><td>CRzXaScVbNmLkJhgFdSaQwErT</td><td>500 Internal Server Error</td><td>Simulates a 500 error from the initialize endpoint.</td></tr><tr><td>CRbNtMvCxZlKjHgFdSaQwErTy</td><td>503 Service Unavailable</td><td>Simulates a 503 error from the initialize endpoint.</td></tr></tbody></table>

### 3DS Initialize API - **Sandbox Testing**

Test 3DS flows using **test PANs** in a sandbox environment. This is closer to production and safe for testing. When using Sandbox, 3DS must be enabled on your account. You’ll also need a service account with the required 3DS scopes and must use it to generate a JWT auth token.

**Base URL:**

```
https://sandbox.vgsapi.com
```

**Initialize API Endpoint:**

```
POST /cards/{card_id}/3ds-initialize
```

**Sample Request Body:**

```json
{
  "data": {
    "attributes": {
      "token_type": "pan",
      "transaction_info": {
        "merchant_transaction_id": "12346586",
        "xid": "UgG20AB6E6dceR1gDg8I8VtxoHk="
      }
    }
  }
}
```

<table><thead><tr><th width="204.86419677734375">Test Card/Pan</th><th width="118.0833740234375">Expiration Date</th><th>Expected Response / Notes</th><th>HTTP Status Code</th></tr></thead><tbody><tr><td>4016360000000493</td><td>Any Future Date</td><td><p>Returns the synchronous response with the iframe/form needed to proceed with authentication. </p><p></p><p>Look for <strong>device_fingerprinting_html</strong> field in the Initialize sync response.</p></td><td>200</td></tr><tr><td>5188340000000969</td><td>Any Future Date</td><td><p>Returns the synchronous response with the iframe/form needed to proceed with authentication. </p><p></p><p>Look for <strong>device_fingerprinting_html</strong> field in the Initialize sync response.</p></td><td>200</td></tr><tr><td>4016365555555493</td><td>Any Future Date</td><td><p>Returns a synchronous response without iframe/data, confirming that initialization is not required and the authentication process can proceed immediately. </p><p></p><p><strong>device_fingerprinting_html</strong> field will not be present in the Initialize sync response.</p></td><td>206 (Partial Content)</td></tr></tbody></table>

#### **Invoking 3DS Device Fingerprinting**

1. **Prepare Request**
   * Ensure `merchant_transaction_id` is **unique** for each request.
   * The synchronous response will include a **3DS Method Form** in `device_fingerprinting_html` with a hidden field `threeDSMethodData`.
2. **Run Device Fingerprinting**
   * This simulates the device fingerprinting between the browser and the ACS/Issuer.
   * Posting `threeDSMethodData` to the ACS allows it to collect device/browser info and triggers an **asynchronous 3DS Method Completion** notification to your configured webhook. Extract `<input type="hidden" name="threeDSMethodData" value="..."/>` from `device_fingerprinting_html`.
   * Create a local HTML file and embed the value in this snippet:

```html
<!doctype html>
<html lang="en">
<body onload="document.forms[0].submit()">
  <form action="https://3ds-acs.test.modirum.com/mdpayacs/3ds-method" method="post" target="hiddenFrame">
    <input type="hidden" name="threeDSMethodData" value="<threeDSMethodData>" />
  </form>
  <iframe name="hiddenFrame" style="display:none"></iframe>
  <p>The form has been submitted to ACS. View the callback <a href="https://play.svix.com/view/e_y4H4J9EA18Hm1Y1Ws5XeVaU45Lq" target="_blank">here</a>.</p>
</body>
</html>
```

3. **Execute and Observe**
   * Open the HTML file in a browser; it auto-submits to ACS.
   * Observe the **asynchronous 3DS Method Completion notification** at your configured notification URL.&#x20;
   * Refer to the VGS Dashboard to **set up notification URL** and **configure 3DS events**.
     * [Configure 3DS events](https://docs.verygoodsecurity.com/cmp/notifications#cmp-3ds-events)
     * [Set up your notification URL](https://app.gitbook.com/s/zbOVGj5YTirkppRKlOP9/developer-resources/webhook-notifications)

![](https://236204706-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2Fg1pA9re6lCmtaiQAnvjh%2Fuploads%2FA8A2KBuiC6kelPHQnt2X%2Funknown.png?alt=media\&token=dd9ac2e2-e451-4ed4-9c6f-2fe09a7f7c46)

### **3DS Authenticate API - Mock Card Testing**

The 3DS **Authenticate** API (`/cards/{cardID}/3ds-authenticate`) simulates the final authentication response.

* For **frictionless** flows, this will be the synchronous response.
* For **challenge** flows, this response will be sent in the asynchronous webhook notification.

You can test various authentication outcomes and error scenarios by providing the corresponding `cardID` as part of the URL path.

**Example Request URL:&#x20;**<mark style="color:orange;">**POST**</mark>**&#x20;** [<mark style="color:blue;">`https://gw-01-sandbox.vgsapi.com/3ds-mocks`</mark>](https://gw-01-sandbox.vgsapi.com/3ds-mocks)<mark style="color:blue;">`/cards/CRAbcDefGhijKlmNoPqrStuVw/3ds-authenticate`</mark>

**Example CURL:**

{% code overflow="wrap" %}

```
curl -X POST "https://gw-01-sandbox.vgsapi.com/3ds-mocks/cards/CRcHaLlEnGeReQdXU3TFmtcD/3ds-authenticate"
-H "Content-Type: application/vnd.api+json"
```

{% endcode %}

**Test `cardID`s and Expected Responses**

<table><thead><tr><th width="147.92327880859375">Test cardID</th><th width="125.3294677734375">Expected HTTP Status</th><th width="181.8585205078125">Expected status (in JSON response)</th><th>Notes</th></tr></thead><tbody><tr><td>CRAbcDefGhijKlmNoPqrStuVw</td><td>200 OK</td><td>AUTHENTICATED</td><td>Simulates a successful authentication (final auth response).</td></tr><tr><td>CRcHaLlEnGeReQdXU3TFmtcD</td><td>200 OK</td><td>CHALLENGE_REQUIRED</td><td><p>Simulates a response that includes the prebuilt HTML form (<code>challenge_form</code>) to directly render the challenge or build your own form using <code>challenge_url</code>, <code>challenge_request</code>, and <code>challenge_session_data</code>. </p><p></p><p><strong>Please note:</strong> rendering the <code>challenge_form</code> from this mock response will not trigger the challenge questionnaire. To trigger the challenge form and receive the async notification, you must submit the html on your checkout page using sandbox cards.</p></td></tr><tr><td>CRXyZ0123456789abcDEfGhij</td><td>200 OK</td><td>INFORMATIONAL_ONLY</td><td>Simulates an informational-only status for a frictionless auth result.</td></tr><tr><td>CRMnOpQrStUvWxYzABCdefGhi</td><td>200 OK</td><td>REJECTED</td><td>Simulates a rejected status for the auth result.</td></tr><tr><td>CRQrStUvWxYz0123456789AbC</td><td>200 OK</td><td>UNABLE_TO_AUTHENTICATE</td><td>Simulates an unable to authenticate status for the auth result.</td></tr><tr><td>CRDecqZp3xRgXU3TFmtcDdzQs</td><td>200 OK</td><td>DENIED</td><td>Simulates a denied status for the auth result.</td></tr><tr><td>CRaZcXeVtBrNnMpLoKjIhUgYt</td><td>400 Bad Request</td><td>N/A (Error Response)</td><td>Simulates a 400 error from the authenticate endpoint.</td></tr><tr><td>CRdFhJkLmNoPqRsTuVwXyZ012</td><td>401 Unauthorized</td><td>N/A (Error Response)</td><td>Simulates a 401 error from the authenticate endpoint.</td></tr><tr><td>CRpOiUytRewQAzXswEdcRfVtB</td><td>403 Forbidden</td><td>N/A (Error Response)</td><td>Simulates a 403 error from the authenticate endpoint.</td></tr><tr><td>CRuYtRewQAzWsXeDcRfVtGbYh</td><td>404 Not Found</td><td>N/A (Error Response)</td><td>Simulates a 404 error from the authenticate endpoint.</td></tr><tr><td>CRiKoLpMnBvCxZaQsWeRdTfYg</td><td>422 Unprocessable Entity</td><td>N/A (Error Response)</td><td>Simulates a 422 error from the authenticate endpoint.</td></tr><tr><td>CRzXaScVbNmLkJhgFdSaQwErT</td><td>500 Internal Server Error</td><td>N/A (Error Response)</td><td>Simulates a 500 error from the authenticate endpoint.</td></tr><tr><td>CRbNtMvCxZlKjHgFdSaQwErTy</td><td>503 Service Unavailable</td><td>N/A (Error Response)</td><td>Simulates a 503 error from the authenticate endpoint.</td></tr></tbody></table>

### **3DS Authentication API – Sandbox Testing**

Use the 3DS Authentication API to simulate different challenge flow responses in Sandbox.\
As with Initialize, Sandbox behavior closely mirrors production and is safe for end-to-end testing.

To test Auth in Sandbox, ensure:

* 3DS is **enabled** for the network you are testing
* Your account has a **notification URL** configured
* You are using a **service account** with the required 3DS scopes to generate a JWT

**Base URL**

```
https://sandbox.vgsapi.com
```

**Auth API Endpoint**

```
POST /cards/{card_id}/3ds-authenticate
```

**Headers**

```
Accept: text 
Accept-Language: text (e.g. en-US)
Content-Type: application/vnd.api+json
```

**Sample Auth Request**

(Must reuse the `merchant_transaction_id` and `xid` from your initialize call)

```json
{
  "data": {
    "attributes": {
      "auth_type": "challenge",
      "token_type": "pan",
      "redirect_url": "https://www.merchant-website.com/purchase-complete",
      "transaction_info": {
        "xid": "E6Kdhoz49St6A2uhf//tZFeXq8Q=",
        "merchant_transaction_id": "f1ec2b0b-bf68-4f2e-9ad5-a60fd04ebdf8"
      },
      "purchase_info": {
        "currency_code": "USD",
        "amount": 1002.25
      },
      "browser_info": {
        "java_enabled": "false",
        "javascript_enabled": "true",
        "language": "en-US",
        "color_depth": 24,
        "screen_height": 1080,
        "screen_width": 1920,
        "tz": -240
      },
      "merchant_info": {
        "acquirer_bin": "444444",
        "acquirer_country_code": "USA",
        "acquirer_requestor_name": "CoffeeHouse",
        "acquirer_requestor_id": "1000",
        "acquirer_merchant_id": "1000",
        "name": "CoffeeHouse",
        "category_code": "4829",
        "country_code": "CAN",
        "website_url": "https://www.coffeehouse.com"
      }
    }
  }
}
```

#### **⚠️ Important Sandbox Requirements**

When testing in **Sandbox**, the following apply:

* **All fields under `merchant_info` are required**.
* The following fields must use these exact values:

```json
"acquirer_bin": "444444",
"acquirer_requestor_id": "1000",
"acquirer_merchant_id": "1000"
```

#### **ℹ️ Production Requirements**

In **Production**:

* `acquirer_requestor_id` and `acquirer_requestor_name` are **optional** for **Visa** and **Mastercard**.
* All other fields should follow the network-specific requirements you receive during acquirer setup.

> *For more information on how to use 3DS with VGS, see the guide* [*here*](https://docs.verygoodsecurity.com/cmp/products-and-services/3ds#using-3ds-with-vgs)*.*

#### **Understanding the Auth Response**

After calling Auth, you will get one of two outcomes:

<table><thead><tr><th width="40">#</th><th width="130.3359375">Type</th><th width="291.70703125">Description</th><th>Example / Notes</th></tr></thead><tbody><tr><td>1</td><td>Synchronous Auth Result</td><td>For certain test PANs, the final 3DS result is returned directly in the auth response. No browser interaction is required.</td><td><ol><li>APPROVED</li><li>DENIED</li><li>ATTEMPTS_PERFORMED</li><li>REJECTED</li><li>UNABLE_TO_AUTHENTICATE</li></ol></td></tr><tr><td>2</td><td>Challenge Flow (Async)</td><td>If the response includes <strong>data.attributes.challenge_info.challenge_form</strong>, a browser-based challenge must be performed.</td><td>N/A</td></tr></tbody></table>

#### **How to Execute the `challenge_form` Returned in the Auth Response**

If the Auth API returns a challenge, you’ll receive:

```
data.attributes.challenge_info.challenge_form
```

This is **ready-to-run HTML** that must be rendered **exactly as provided**.

To execute it, inject the `challenge_form` HTML directly into your checkout page or an iframe you control. Examples include:

* Setting it as the innerHTML of a container
* Writing it into an iframe
* Rendering it inside a modal that can execute HTML

The HTML already includes:

* The ACS form
* All required hidden fields
* Auto-submit behavior
* Redirect/iframe handling
* Any UI needed for the challenge (password entry or simulation buttons)

**Do not** save it to a file, modify or sanitize it, wrap it in another form, or rebuild the structure.\
This breaks the challenge because the ACS expects the **exact form layout**, the auto-submit must run inside a real browser DOM, and the HTML may rely on specific parent/iframe context.

Render it as-is to ensure the ACS challenge runs correctly. Once the challenge is executed and completed, the authentication flow proceeds, and the final 3DS result is returned to the **configured async webhook**.

#### **Auth Testing Cards (Sandbox)**

Below are all supported test cards and their expected behaviors.

**Mastercard**

<table><thead><tr><th width="208.03515625">Test Card PAN</th><th width="125.875">Result Type</th><th width="257.3125">Expected Status</th><th>Notes</th></tr></thead><tbody><tr><td>5239290700000151</td><td><strong>Async</strong></td><td>APPROVED</td><td>Password challenge → use <code>secret!33</code></td></tr><tr><td>5239290700000102</td><td><strong>Sync</strong></td><td>APPROVED</td><td>—</td></tr><tr><td>5188340000000629</td><td><strong>Sync</strong></td><td>DENIED</td><td>—</td></tr><tr><td>5188340000000937</td><td><strong>Sync</strong></td><td>ATTEMPTS_PERFORMED</td><td>—</td></tr><tr><td>5188340000000952</td><td><strong>Sync</strong></td><td>REJECTED</td><td>—</td></tr><tr><td>5188340000000222</td><td><strong>Sync</strong></td><td>APPROVED</td><td>—</td></tr><tr><td>5188340000000445</td><td><strong>Sync</strong></td><td>UNABLE_TO_AUTHENTICATE</td><td>With <code>acs_info</code></td></tr></tbody></table>

**Visa**

<table><thead><tr><th width="223.0546875">Test Card PAN</th><th>Result Type</th><th>Expected Status</th><th>Notes</th></tr></thead><tbody><tr><td>4016360000000493</td><td><strong>Async</strong></td><td>APPROVED / ATTEMPTED / DENIED / REJECTED / UNAVAILABLE</td><td>Buttons simulate every result</td></tr><tr><td>4147463011110134</td><td><strong>Sync</strong></td><td>APPROVED</td><td>—</td></tr><tr><td>4147463011110142</td><td><strong>Sync</strong></td><td>DENIED</td><td>—</td></tr><tr><td>4147463011110159</td><td><strong>Sync</strong></td><td>ATTEMPTS_PERFORMED</td><td>—</td></tr><tr><td>4147463011110175</td><td><strong>Sync</strong></td><td>REJECTED</td><td>—</td></tr><tr><td>4147463011110167</td><td><strong>Sync</strong></td><td>UNABLE_TO_AUTHENTICATE</td><td>With <code>acs_info</code></td></tr></tbody></table>
