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 (THREE_DS_2_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

        alt Final Result
            Gateway -->> Merchant: Transaction result (approved or declined)
        else Additional Authentication Required
            Gateway -->> Merchant: Response with new action (THREE_DS_2_FINGERPRINT or THREE_DS_2_CHALLENGE)
            Note over Merchant: Repeat 3DS flow from the appropriate step
        end
    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: "THREE_DS_2_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": "THREE_DS_2_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 The action type — see Action Type Values below
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

Action Type Values

The type field uses the following values:

Value Meaning Next Step
THREE_DS_2_FINGERPRINT Device fingerprint collection required Perform device fingerprinting
THREE_DS_2_CHALLENGE 3DS challenge authentication required Perform challenge authentication

Match on action type correctly

Use the exact values above when checking the type field. For example, use action.type === "THREE_DS_2_FINGERPRINT" and action.type === "THREE_DS_2_CHALLENGE" in your implementation. Do not check for shortened values like "fingerprint" or "challenge".

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 uses the same structure as the initial /api/transactions/authorize response. Check the top-level fields to determine the next step:

  • If result is present — the transaction is finalized (frictionless flow: approved or declined)
  • If action is present with type: "THREE_DS_2_CHALLENGE" — proceed to challenge authentication
  • If redirect is present — redirect the customer
  • If form_submit is present — auto-submit the form

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: "THREE_DS_2_CHALLENGE", a redirect, or a form_submit.

Action with Challenge Type

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

{
  "result": null,
  "action": {
    "transaction_id": "vvIVFmPuwYosePLsoDsW",
    "session_id": "abc123-session-id",
    "type": "THREE_DS_2_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 follows the same structure as the initial /api/transactions/authorize response — check the result, action, redirect, and form_submit fields to determine the next step.

Additional authentication may be required

The /authentication-completed endpoint does not always return a final transaction result. If the issuer determines that further authentication is needed, the response will contain a new action, redirect, or form_submit — just like the initial authorization response.

When this happens, handle the response exactly as you would after the initial /api/transactions/authorize call:

  • If action is present with type: "THREE_DS_2_FINGERPRINT" — perform device fingerprinting again
  • If action is present with type: "THREE_DS_2_CHALLENGE" — perform challenge authentication again
  • If redirect is present — redirect the customer
  • If form_submit is present — auto-submit the form
  • If result is present — the transaction is finalized (approved or declined)

This additional authentication round can occur at most once per authentication attempt. If the issuer requests further authentication after the second round, the transaction will be automatically declined. However, if the transaction is declined and the gateway auto-retries with a different provider, a new 3DS flow may be initiated — see Limits for details.


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.


Handling Repeated Authentication

In certain scenarios, the Payment Gateway may return an action, redirect, or form_submit response after you have already completed a 3DS authentication step. This means the issuer or the gateway requires additional authentication before the transaction can be finalized.

When Does This Happen?

After completing a challenge (/authentication-completed): The issuer may request an additional authentication round. For example, after submitting the challenge result with trans_status: "C", the gateway may return a new action with type: "THREE_DS_2_FINGERPRINT" or type: "THREE_DS_2_CHALLENGE".

After a transaction is declined and automatically retried: When a transaction is declined, the Payment Gateway may automatically retry the transaction with a different payment provider. This retry goes through a full authorization flow, which includes 3DS authentication. As a result, you may receive a new action, redirect, or form_submit response that requires the customer to complete 3DS authentication again — this time with the new provider.

How to Handle It

Your integration should always check the response structure after every API call in the 3DS flow, not just after the initial /api/transactions/authorize:

1. Check if `result` is present → Transaction is finalized (approved/declined)
2. Check if `action` is present → Handle fingerprint or challenge
3. Check if `redirect` is present → Redirect the customer
4. Check if `form_submit` is present → Auto-submit the form

Build a response handler, not a step counter

Rather than coding a linear sequence of steps, implement a single response handler that inspects each response and routes to the appropriate action. This naturally handles repeated authentication without special-case logic.

Limits

The gateway enforces limits at two levels:

  • Per-authentication attempt: A maximum of one additional authentication round is allowed. If the issuer requests further authentication after this, the attempt will be automatically declined.
  • Fallback routing: When a transaction is declined, the gateway may automatically retry with a different payment provider — up to N times (configurable, default is 5). Each retry goes through a full authorization and 3DS flow, so the customer may be asked to authenticate multiple times across retries.

After Authentication

Once the customer completes the 3DS challenge (via redirect or form submit flows), they will be redirected back to your return_url. Note that if the gateway performed an auto-retry with a different provider, the customer may need to complete 3DS again before reaching a final status. 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: THREE_DS_2_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

        alt Transaction Finalized
            Gateway -->> Server: Transaction result
            Server -->> Browser: Payment result
        else Additional Authentication Required
            Gateway -->> Server: New action (THREE_DS_2_FINGERPRINT or THREE_DS_2_CHALLENGE)
            Note over Server: Handle response same as initial authorize
            Note over Browser: Repeat fingerprint or challenge flow
        end
    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