Skip to content

3D Secure (3DS)

Introduction

3D Secure (3DS) is an authentication protocol designed to reduce fraud in online card payments. It adds an extra verification step where the cardholder's bank (issuer) authenticates the customer before the payment is authorized. The Payment Gateway supports 3D Secure 2.x, which provides a better user experience through risk-based authentication — many transactions are approved without customer interaction (frictionless flow), while higher-risk transactions require the cardholder to complete a challenge (e.g., entering an OTP or confirming in a banking app).

3DS authentication is a regulatory requirement under PSD2 Strong Customer Authentication (SCA) in the European Economic Area and is widely adopted by card networks globally.

When is 3DS Triggered?

Payment Method 3DS Behavior
Card (card) 3DS is always initiated
Saved Card (saved_card) 3DS is always initiated
Google Pay (google-pay) 3DS is initiated for PAN_ONLY tokens; skipped for CRYPTOGRAM_3DS tokens
Network Token (network-token) 3DS is skipped when a valid cryptogram and ECI are provided
OCT No 3DS — Original Credit Transactions do not require authentication
Payout No 3DS — Payouts do not require authentication

3DS Flow Overview

When a card transaction requires 3DS authentication, the response from the /api/transactions/authorize endpoint will include additional fields that guide the merchant through the authentication process. The flow involves up to two steps: device fingerprinting and challenge authentication.

sequenceDiagram
    participant Customer
    participant Merchant
    participant Gateway as Payment Gateway
    participant ACS as Issuer ACS

    Merchant ->> Gateway: POST /api/transactions/authorize
    Gateway -->> Merchant: Response with action (fingerprint)

    Note over Merchant: Step 1 — Device Fingerprint
    Merchant ->> Merchant: Decode token from action response
    Merchant ->> ACS: POST threeDSMethodData (hidden iframe)
    ACS -->> Gateway: Notification (via threeDsMethodNotificationUrl)

    Merchant ->> Gateway: POST /api/three-ds/{id}/request-authentication
    Gateway -->> Merchant: Response (approved, or challenge required)

    alt Frictionless Flow
        Note over Merchant: Transaction approved without customer interaction
    else Challenge Required
        Note over Merchant: Step 2 — Challenge
        Merchant ->> Merchant: Decode token, build CReq
        Merchant ->> ACS: POST creq to ACS (iframe)
        Customer ->> ACS: Complete authentication (OTP, biometric, etc.)
        ACS -->> Merchant: CRes via window message
        Merchant ->> Gateway: POST /api/three-ds/{id}/authentication-completed
        Gateway -->> Merchant: Final transaction result
    end

Response Handling

After calling /api/transactions/authorize, the response contains four top-level fields. Which fields are populated determines the next step:

Response Scenario result action redirect form_submit Next Step
Approved (no 3DS needed) Transaction data null null null Done — transaction is approved
Declined Transaction data null null null Done — transaction is declined
Fingerprint required null Action details null null Perform device fingerprinting
Redirect required null null Redirect URL null Redirect customer to URL
Form submit required null null null Form details Auto-submit form to URL

Step 1 — Device Fingerprint

When the response contains an action object with type: "fingerprint", you must collect the customer's device fingerprint by submitting a hidden form to the issuer's Access Control Server (ACS).

Action Response Structure

{
  "result": null,
  "action": {
    "transaction_id": "vvIVFmPuwYosePLsoDsW",
    "session_id": "abc123-session-id",
    "type": "fingerprint",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
    "payment_data": "encrypted_payment_data_string"
  },
  "redirect": null,
  "form_submit": null
}

Action Fields

Field Type Description
transaction_id string The transaction identifier
session_id string The session identifier for this 3DS flow
type string Either "fingerprint" or "challenge"
token string Base64-encoded JSON containing ACS URLs and 3DS server transaction data
payment_data string Encrypted payment data — must be sent back when completing the transaction

Decoded Token Structure

The token field is a Base64-encoded JSON object with the following structure:

{
  "acsUrl": "https://acs.issuer.com/challenge",
  "messageVersion": "2.2.0",
  "threeDsServerTransId": "abc123-server-trans-id",
  "threeDsMethodNotificationUrl": "https://gateway.example.com/three-ds/notification",
  "threeDsMethodUrl": "https://acs.issuer.com/fingerprint",
  "acsTransID": "xyz789-acs-trans-id",
  "challengeWindowSize": "05"
}
Field Description
threeDsMethodUrl URL to submit the fingerprint form to
threeDsServerTransId 3DS server transaction ID — used in subsequent API calls
threeDsMethodNotificationUrl URL where the ACS will notify the gateway of fingerprint completion
acsUrl ACS URL for challenge step (used in Step 2 if needed)
acsTransID ACS transaction ID (used in Step 2 if needed)
messageVersion 3DS protocol version
challengeWindowSize Challenge window size code (used in Step 2 if needed)

Implementation

1. Decode the token:

const decodedToken = JSON.parse(atob(action.token));

2. Build the threeDSMethodData:

Construct a JSON object containing the server transaction ID and notification URL, then Base64 URL-encode it:

const fingerprintData = {
  threeDSServerTransID: decodedToken.threeDsServerTransId,
  threeDSMethodNotificationURL: decodedToken.threeDsMethodNotificationUrl
};
const threeDSMethodData = btoa(JSON.stringify(fingerprintData));

3. Create and auto-submit the hidden form:

<iframe id="fingerprint-iframe" name="fingerprint-iframe"
        style="display:none;" width="0" height="0"></iframe>

<form id="fingerprint-form" method="POST"
      action="<decodedToken.threeDsMethodUrl>"
      target="fingerprint-iframe">
  <input type="hidden" name="threeDSMethodData"
         value="<threeDSMethodData>" />
</form>

<script>
  document.getElementById('fingerprint-form').submit();
</script>

4. Wait for the fingerprint to complete (timeout: ~5 seconds), then proceed to request authentication.

Requesting Authentication

After the fingerprint step, request authentication by calling the 3DS authentication endpoint:

POST /api/three-ds/{threeDsServerTransId}/request-authentication
Authorization: Bearer <access_token>
Content-Type: application/json
{
  "fingerprint_result": "<base64-encoded completion indicator>",
  "payment_data": "<payment_data from the action response>"
}

The response will be in the same format as the initial authorize response:

  • If the transaction is approved (frictionless flow): result will contain the transaction data
  • If a challenge is required: the response will contain a new action with type: "challenge", a redirect, or a form_submit

Step 2 — Challenge

If the issuer requires additional customer authentication after the fingerprint step, the response will indicate a challenge. This can be returned as an action with type: "challenge", a redirect, or a form_submit.

Action with Challenge Type

When the response contains an action with type: "challenge":

{
  "result": null,
  "action": {
    "transaction_id": "vvIVFmPuwYosePLsoDsW",
    "session_id": "abc123-session-id",
    "type": "challenge",
    "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6...",
    "payment_data": "encrypted_payment_data_string"
  },
  "redirect": null,
  "form_submit": null
}

1. Decode the token (same structure as the fingerprint token):

const decodedToken = JSON.parse(atob(action.token));

2. Build the Challenge Request (CReq):

const creq = {
  threeDSServerTransID: decodedToken.threeDsServerTransId,
  acsTransID: decodedToken.acsTransID,
  messageVersion: decodedToken.messageVersion,
  messageType: "CReq",
  challengeWindowSize: decodedToken.challengeWindowSize
};
const encodedCReq = btoa(JSON.stringify(creq));

3. Submit the CReq to the ACS via an iframe:

<iframe id="challenge-iframe" name="challenge-iframe"
        width="100%" height="400"></iframe>

<form id="challenge-form" method="POST"
      action="<decodedToken.acsUrl>"
      target="challenge-iframe">
  <input type="hidden" name="creq" value="<encodedCReq>" />
</form>

<script>
  document.getElementById('challenge-form').submit();
</script>

The customer will see the issuer's authentication UI inside the iframe (e.g., OTP input, biometric prompt).

4. Listen for the Challenge Response (CRes):

The ACS sends the challenge result back via a window.message event:

window.addEventListener('message', function(event) {
  const data = JSON.parse(event.data);
  if (data.type === 'CHALLENGE_RESPONSE') {
    const transStatus = data.trans_status;
    // Proceed to submit the result to the gateway
  }
});

Transaction Status Values (trans_status)

Value Meaning
Y Authentication successful
N Not authenticated (denied)
U Authentication could not be performed
A Authentication attempted
C Challenge required (additional interaction needed)
R Authentication rejected

Completing the Challenge

After receiving the CRes, submit the authentication result to the gateway:

POST /api/three-ds/{threeDsServerTransId}/authentication-completed
Authorization: Bearer <access_token>
Content-Type: application/json
{
  "payment_data": "<payment_data from the action response>",
  "session_id": "<session_id from the action response>",
  "trans_status": "<trans_status from CRes>",
  "parent_transaction_id": "<transaction_id from the action response>"
}

The response will contain the final transaction result.


Redirect Flow

When the response contains a redirect object instead of an action:

{
  "result": null,
  "action": null,
  "redirect": {
    "transaction_id": "vvIVFmPuwYosePLsoDsW",
    "session_id": "abc123-session-id",
    "url": "https://acs.issuer.com/challenge?id=..."
  },
  "form_submit": null
}

Redirect the customer's browser to the provided url. After authentication, the customer will be redirected back to your return_url.

Form Submit Flow

When the response contains a form_submit object:

{
  "result": null,
  "action": null,
  "redirect": null,
  "form_submit": {
    "transaction_id": "vvIVFmPuwYosePLsoDsW",
    "session_id": "abc123-session-id",
    "url": "https://acs.issuer.com/challenge",
    "data": {
      "PaReq": "eJxVUt1ugjAUvl...",
      "MD": "vvIVFmPuwYosePLsoDsW",
      "TermUrl": "https://gateway.example.com/callback"
    }
  }
}

Create a form with the provided fields and auto-submit it:

<form id="challenge-form" method="POST"
      action="https://acs.issuer.com/challenge">
  <input type="hidden" name="PaReq" value="eJxVUt1ugjAUvl..." />
  <input type="hidden" name="MD" value="vvIVFmPuwYosePLsoDsW" />
  <input type="hidden" name="TermUrl" value="https://gateway.example.com/callback" />
</form>

<script>
  document.getElementById('challenge-form').submit();
</script>

After authentication, the customer will be redirected back to your return_url.


After Authentication

Once the customer completes the 3DS challenge (via redirect or form submit flows), they will be redirected back to your return_url. Verify the transaction status:

GET /api/transactions/{transaction_id}/status
Authorization: Bearer <access_token>

The transaction will be in one of the following statuses:

Status Description
APPROVED Authentication successful, transaction authorized
DECLINED Authentication failed or transaction was declined by the issuer
CANCELED Customer canceled the authentication
SESSION_EXPIRED The 3DS session timed out before the customer completed authentication

Use Webhooks

Instead of polling the transaction status, configure a webhook to receive real-time notifications when the transaction reaches a final status.


Browser Info

The browser_info object is required for all card transactions and is used by the issuer to assess risk during 3DS authentication. You must collect this information from the customer's browser.

Required Fields

Field Type Description Example
user_agent string The browser's User-Agent header "Mozilla/5.0..."
accept_header string The browser's Accept header "text/html,application/xhtml+xml..."
java_enabled boolean Whether Java is enabled false
color_depth integer Screen color depth in bits 24
screen_height integer Screen height in pixels 1080
screen_width integer Screen width in pixels 1920
time_zone_offset integer Timezone offset from UTC in minutes -120
language string Browser language "en-US"

JavaScript Collection Example

const browserInfo = {
  user_agent: navigator.userAgent,
  accept_header: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
  java_enabled: navigator.javaEnabled(),
  color_depth: screen.colorDepth,
  screen_height: screen.height,
  screen_width: screen.width,
  time_zone_offset: new Date().getTimezoneOffset(),
  language: navigator.language
};

Accept Header

The accept_header value cannot be read directly from JavaScript. Use a standard value as shown above, or capture it server-side from the customer's HTTP request headers.


Timeouts

Step Timeout Description
Device fingerprint 5 seconds If the ACS does not respond within this time, proceed to request authentication regardless
Challenge 10 minutes Maximum time allowed for the customer to complete the challenge

Complete Integration Example

Below is a simplified example of the full 3DS flow for a card payment:

sequenceDiagram
    participant Browser as Customer Browser
    participant Server as Merchant Server
    participant Gateway as Payment Gateway
    participant ACS as Issuer ACS

    Browser ->> Server: Checkout (card details + browser info)
    Server ->> Gateway: POST /api/transactions/authorize
    Gateway -->> Server: action (type: fingerprint, token, payment_data)
    Server -->> Browser: Decoded token + payment_data

    Note over Browser: Step 1 — Device Fingerprint
    Browser ->> Browser: Build threeDSMethodData from token
    Browser ->> ACS: POST threeDSMethodData (hidden iframe to threeDsMethodUrl)
    Note over Browser: Wait up to 5 seconds

    Browser ->> Server: Fingerprint complete
    Server ->> Gateway: POST /api/three-ds/{id}/request-authentication
    Gateway -->> Server: Frictionless approval or challenge action

    alt Approved (Frictionless)
        Server -->> Browser: Payment successful
    else Challenge Required (action)
        Server -->> Browser: Challenge token + payment_data
        Note over Browser: Step 2 — Challenge
        Browser ->> Browser: Build CReq from token
        Browser ->> ACS: POST creq (iframe to acsUrl)
        Browser ->> ACS: Customer completes authentication
        ACS -->> Browser: CRes via window.message
        Browser ->> Server: trans_status from CRes
        Server ->> Gateway: POST /api/three-ds/{id}/authentication-completed
        Gateway -->> Server: Final transaction result
        Server -->> Browser: Payment result
    else Redirect Required
        Server -->> Browser: Redirect URL
        Browser ->> ACS: Browser redirect
        ACS -->> Browser: Redirect to return_url
        Browser ->> Server: Return from authentication
        Server ->> Gateway: GET /api/transactions/{id}/status
        Gateway -->> Server: Final status
        Server -->> Browser: Payment result
    end