Server-Side 3DS Integration

Overview

📘

Bluefin 3DS Solution

Before reviewing this section, ensure you are familiar with the Bluefin 3DS solution and have met all prerequisites and requirements - in terms of setup, background knowledge, and understanding of our 3DS workflows.

This is a use case is considered to be server-side 3DS integration where the merchant moves their 3DS integration into their own backend/servers.

In communication with the browser, the entire 3DS process can happen on the server side without necessarily exposing the Bluefin 3DS endpoints on the client side. For the client-side 3DS integration, please refer to Bluefin 3D Secure SDK.

This is typically common for large ISVs with their own PCI-compliant environment where they can directly integrate with Bluefin 3DS on the server side.

While the client-side integration with the 3DS SDK is simpler and more automated, this approach offers merchants greater control over the 3DS process. However, it requires more work and attention to detail—something we guide you through in this page.

Environment URLs

PayConex provides three environments for development, integration, and production usage:

Environment NameEndpointDescription
Certification (Testing) Environmenthttps://api-cert.payconex.netUse this environment for development and testing.
Staging (Development) Environmenthttps://api-staging.payconex.netUse this environment for development.
Production (Live) Environment:https://api.payconex.netUse this environment for live requests.

All requests must be made over HTTPS for security. Append the endpoints described below to the appropriate base URL depending on whether you are testing or running in production.

For example, this is the full URL for generating a bearer token in the certification environment:

https://api-cert.payconex.net/api/v4/accounts/{accountId}/auth/token

Server-Side Integration

This section outlines the logic required for the merchant to understand and successfully integrate the 3DS endpoints. It mirrors the flow used by the client-side 3DS SDK, which handles these calls behind the scenes. We strongly recommend following this approach.

Back-end Code Example

We combined all of the requests below into a single NodeJS backend example script for learning purposes. This script can be found at our Github repository.

API Key Requirement

To be able to generate the bearer token below, the following API scopes must be added to your API Key.

  • pcx:auth:token
  • pcx:three_d_secure:*

📘

Note

Bluefin Integrations Teams will set up the API key for you. if you are having trouble, please contact [email protected].

For more information, see API Authentication.

Generating Bearer Token

To use the 3DS endpoints, you must first generate a bearer token using the request below.

POST /api/v4/accounts/{accountId}/auth/token

{
  "scopes": [
    "pcx:three_d_secure:*"
  ],
  "timeout": 900
}

The "pcx:three_d_secure:*" scope grants access to the 3DS endpoints, while the timeout parameter defines the token’s expiration time in seconds.

3DS Endpoint Overview

The 3D Secure | Bluefin 3DS Workflow can be broken down, to detail, into 5 API calls in sequence.

As pointed out above, we also walk you through the workflow logic required to successfully integrate them.

For a comprehensive reference on the request parameters and enumerations, refer to 3DS Endpoints | API Reference.

🚧

Bearer Token and Testing

All of these endpoints require the bearer token generated via /api/v4/accounts/{accountId}/auth/token. Check out Generating Bearer Token above.

Throughout this guide, we are using the certification environment for testing purposes.

Certification environment is used to:

  • Conduct thorough testing in the certification environment.
  • Simulate various transaction scenarios to ensure reliability and security.
  • Perform end-to-end testing, from 3DS MPI Simulation to transaction completion.
SectionAPI EndpointDescription
Initiate Bluefin 3DS with Card Details/api/v4/accounts/{accountId}/3DS/init-card-detailsInitiate the 3DS process with the card data (raw PAN, expiry, and cvv).
ACS MethodThis method relies on two endpoints provided by the card issuer: one for displaying the customer form and another for checking the ACS result status.The ACS Method Fingerprint (Client Device Flow) in the 3D Secure (3DS) process is a mechanism used to collect information about the customer’s device to help the Access Control Server (ACS) perform risk-based authentication without challenging the cardholder. This process is triggered during the Frictionless Flow with the ACS Method URL in a hidden iframe.
Browser Authentication/api/v4/accounts/{accountId}/3DS/{threeDSecureId}/browser-authenticatePerforms the authentication with the browser data along with the purchase, customer, and shipping information. This step determines if the challenge is required. For more details, see below.
Challenge RequestThe challenge request endpoint and form is provided by the card issuer.Browser Authentication returns the challenge form payload considering the challenge is required with "REQUIRES_MANDATE_CHALLENGE" or similar.
3DS Status/api/v4/accounts/{accountId}/3DS/{threeDSecureId}/statusReturns the final 3DS data parameters to be used in a transaction payload for authorization. This GET request should run in the background to periodically check the 3DS result. This can also be referred to as Retry.

For testing purposes, we are using one of the Test 3DS Cards to trigger the 3DS workflow. In the table, the prompt (Y | N) depends whether the challenge will be included or not.

📘

Challenge Request

The challenge is simulated via Bluefin 3DS MPI in the certification environment for testing purposes.

If there is no challenge required/requested, the merchant server doesn't need to return a form to the browser for authentication. For more details, dive into Challenge Request.

🚧

Note

For simplicity's sake, all of the data below is hard-coded. This data is supposed to come from both the merchant form and browser as the customer interacts with the front-end application.

For example,

// See: https://developer.mozilla.org/en-US/docs/Web/API/Navigator
function getBrowserData() {
 return {
   acceptHeader: navigator.userAgent.includes("Chrome") ? "*/*" : navigator.accept || "*/*",
   javaEnabled: navigator.javaEnabled(),
   colorDepth: screen.colorDepth,
   language: navigator.language || navigator.userLanguage,
   screenWidth: screen.width,
   screenHeight: screen.height,
   timezone: new Date().getTimezoneOffset(),
   userAgent: navigator.userAgent
 };
}

Initiate Bluefin 3DS with Card Details

As the name suggests, this request initiates 3DS with the provided card details so that we are ready for authentication with threeDSecureId and other parameters from the response below.

Request

POST /api/v4/accounts/{accountId}/3DS/init-card-details

{
  "pan": "4124939999999990",
  "expiry": "1225",
  "cvv": "555"
}

Response

{
  "threeDSecureId": "tds_17218614a5ea49e6bb2efe59457ab3f2",
  "status": "READY_FOR_AUTH_REQUEST",
  "serverTransactionID": "61102ae1-a985-41e3-b82f-2aefb7c19556",
  "threeDSecureData": "eyJ0aHJlZURTTWV0aG9kTm90aWZpY2F0aW9uVVJMIjoiaHR0cHM6Ly90ZXN0LnRlY3MuYXQvbXBpLTItdmlzYS9yZXF1ZXN0b3IvbWV0aG9kL25vdGlmeSIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiNjExMDJhZTEtYTk4NS00MWUzLWI4MmYtMmFlZmI3YzE5NTU2In0",
  "paymentDetailsReference": "pdd_9e64e02e7f88439e86e02e2a16bae78f"
}

🚧

paymentDetailsReference

From this response, we are reusing the paymentDetailsReference for the browser authentication.

ACS Method

Initiate Card Details and ACS URLs

Depending on the card, the init-card-details response includes the ACS URL and ACS result URL. These two URLs are determined by the card issuer.

As mentioned above,

The ACS Method Fingerprint (Client Device Flow) in the 3D Secure (3DS) process is a mechanism used to collect information about the customer’s device to help the Access Control Server (ACS) perform risk-based authentication without challenging the cardholder. This process is triggered during the Frictionless Flow with the ACS Method URL in a hidden iframe.

In the Bluefin certification MPI environment, we get the response similar to the following:

{
  "threeDSecureId": "tds_facb050d8567583cfaa36f3351cf43b7",
  "status": "CARDHOLDER_ACCOUNT_ENROLLED",
  "serverTransactionID": "809d2199-895e-46b5-8b7e-01ce9ac8703d",
  "threeDSecureData": "eyJ0aHJlZURTTWV0aG9kTm90aWZpY2F0aW9uVVJMIjoiaHR0cHM6XC9cL3Rlc3QudGVjcy5hdFwvbXBpLTItdmlzYVwvcmVxdWVzdG9yXC9tZXRob2RcL25vdGlmeSIsInRocmVlRFNTZXJ2ZXJUcmFuc0lEIjoiYjU1Mzk0YWItZWVlNC00OGE1LTkxN2EtMDg5ODAxYjNmNDdhIn0=",
  "acsUrl": "https://test.tecs.at/mpi-2-simulators/acs/visa/method/5",
  "acsResultUrl": "https://test.tecs.at/mpi-2-visa/requestor/method/notify/result/ajax/b55394ab-eee4-48a5-917a-089801b3f47a",
  "paymentDetailsReference": "pdd_5249941f13564471b3be9f96a6d532c1"
}

🚧

Note

If the status of the card init response object matches to CARDHOLDER_ACCOUNT_ENROLLED with acsURL, threeDSecureData, and acsResultURL , the merchant is required to make an ACS call and set up the entire ACS method.

For example, this is the code that implements that step and the logic.

function shouldMakeACSCall(initData: ThreeDSecureBrowserInit) {
    if (initData.status == "CARDHOLDER_ACCOUNT_ENROLLED"
        && initData.acsUrl
        && initData.threeDSecureData
        && initData.acsResultUrl) {
            return true;
        }
    return false;
}

ACS Form

Using the ACS URL and the threeDSecureData, the merchant builds the following form for the customer and injects it into your destination HTML iframe.

For example,

<!DOCTYPE html>
<html lang='en'>
<head>
    <title>Redirect user's browser to ACS</title>
</head>
<body>
    <form id="submitForm" action="${initData.acsUrl ?? ''}" method="POST">
       <input name="threeDSMethodData" value="${initData.threeDSecureData ?? ''}"/>
        <noscript>
            JavaScript is not supported or is disabled. Please hit the Submit but
            ton.
           <input type="submit" value="Submit"/>
        </noscript>
    </form>
    <script>
        window.onload = function () {
            document.getElementById("submitForm").submit();
        }
    </script>
</body>
</html>

ACS Result URL

While the merchant waits for the customer to complete and submit the ACS form, a setInterval function should run in the background to periodically check the ACS result using a GET request.

For example,

let c = 0
const id = setInterval(() => {
    let res = await fetch(initData.acsResultUrl, {
        method: "GET"
    })
    if(res.status == 200) {
        // Proceed with Browser Authentication
        clearInterval(id)
    }
    // Stop after 10 seconds
    if(c == 10) {
        // Render the iframe with ACS Method expired.
        clearInterval(id)
    }
    c++
}, 1000) // User-defined interval value. Check every 1 second in this case

Proceed with Browser Authentication

Once the ACS result status is 200, the merchant is ready to proceed with the Browser Authentication of the 3DS process.

Logic Diagram

If illustrated, the ACS method part sums up into the following:

ACS Method Diagram

ACS Method Diagram

The 3DS workflow for Browser Authentication is followed up with the diagram below for repeated 3DS status requests and QSAPI transaction authorization, completing the 3DS process.

Browser Authentication

The browser authentication request is composed of the data collected from the browser via Web APIs along with the threeDSecureChallengeIndicator, customer, and shipping information.

Request

POST /api/v4/accounts/{accountId}/3DS/{threeDSecureId}/browser-authenticate

{
  "card": {
    "paymentDetailsReference": "{paymentDetailsReference_FROM_ABOVE}"
  },
  "browser": {
    "acceptHeader": "*/*",
    "javaEnabled": false,
    "colorDepth": 24,
    "language": "en-GB",
    "screenWidth": 1920,
    "screenHeight": 1080,
    "timezone": -120,
    "userAgent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/133.0.0.0 Safari/537.36"
  },
  "purchase": {
    "currency": "USD",
    "date": "20250521235204",
    "amount": "1.00",
    "transactionType": "GOODS_SERVICE_PURCHASE",
    "deliveryTimeFrame": "SAME_DAY_SHIPPING"
  },
  "threeDSecureChallengeIndicator": "PREFER_NO_CHALLENGE",
  "customer": {
    "name": "Jane Smith",
    "billingAddress": {
      "address1": "123 N Main St",
      "address2": "123",
      "city": "Tulsa",
      "state": "OK",
      "zip": "12345",
      "country": "USA"
    }
  },
  "shipping": {
    "indicator": "BILLING_ADDRESS",
    "address": {
      "address1": "123 Plain St",
      "address2": "West Side",
      "city": "Atlanta",
      "state": "GA",
      "zip": "90210",
      "country": "USA",
      "company": "Acme Inc."
    }
  }
}

For all the request parameters and enumerations, refer to 3DS Endpoints | API Reference.

Response

Depending on the threeDSecureChallengeIndicator specified in the browser authentication request, we get the following in response for "PREFER_NO_CHALLENGE".

{
  "threeDSecureId": "tds_17218614a5ea49e6bb2efe59457ab3f2",
  "status": "PROCESS_DONE",
  "serverTransactionID": "61102ae1-a985-41e3-b82f-2aefb7c19556"
}

Challenge Request

If the challenge is requested via "REQUIRES_MANDATE_CHALLENGE" or similar, we get the following response:

📘

Form Action URL

The form action URL used to redirect to and complete the challenge varies based on the card issuer of the customer.

{
    "threeDSecureId": "tds_88e57fd2305f4b3ca5f396e4a894ef37",
    "status": "CHALLENGE_REQUIRED",
    "serverTransactionID": "722e5512-b263-400a-8fea-38ced020d174",
    "challenge": {
        "form": {
            "action": "https:\/\/test.tecs.at\/mpi-2-simulators\/acs\/amex\/challengeRequest",
            "id": "submitForm",
            "inputs": [
                {
                    "name": "creq",
                    "value": "eyJhY3NUcmFuc0lEIjoi...",
                    "type": "hidden"
                },
                {
                    "name": "threeDSSessionData",
                    "value": "eyJhbiI6IjM0MDAwMD...",
                    "type": "hidden"
                }
            ],
            "method": "post",
            "name": "submitForm"
        },
        "responseCode": "0",
        "responseMessage": "redirect",
        "transactionID": "tds_88e57fd2305f4b3ca5f396e4a894ef37"
    }
}

Using the form action url and with the cred and threeDSSessionData values as form data payload, we are able to request the HTML form from the issuer.

function buildRequest(data) {
    let body = {}
    data.challenge.form.inputs.forEach(input => {
        body[input.name] = input.value
    })
    return body
}

async function request_challenge(data) {
  let res = await fetch(data.challenge.form.action, {
    method: data.challenge.form.method,
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
    body: new URLSearchParams(buildRequest(data)),
  })

  return await res.text()
}

In the certification MPI environment, the form automatically redirects to the challenge-complete endpoint to update the 3DS status. This redirect includes the cres value needed to complete the challenge.

🚧

Note

This form is assembled and returned by the card issuer by default, so the merchant does not need to manually call the Bluefin 3DS challenge-complete endpoint.

In production, the communication bridge between the issuing bank and the browser is established when the customer interacts with the form. After successful authentication, the cres value is returned, and the issuing bank returns the form similar to the following.

<!DOCTYPE html>
<html lang='en'>
    <head>
        <title>Redirect back to the 3DS Server</title>
    </head>
    <body>
        <div>
            Simulation of the user authentication done. Please hit the submit button for payment finalization. Redirect back to the 3DS Server.
            <form id='submitForm' name='submitForm' action='https://api-cert.payconex.net/api/v4/accounts/{accountId}/3DS/tds_88e57fd2305f4b3ca5f396e4a894ef37/challenge-complete' method='post'>
                <input name='creq' type='hidden' value='eyJhY3NUcmFuc0lEIjoiZGIx...'/>
                <input name='cres' type='hidden' value='eyJhY3NUcmFuc0lEIjoiZGIxMmJkZGM...'/>
                <input name='threeDSSessionData' type='hidden' value='eyJhbiI6IjM0MDAwMDExMTE...'/>
                <input type='submit' value='Submit'/>
            </form>
        </div>
    </body>
</html>

Request Parameters

The issuing bank puts together the form using the challenge-complete endpoint using the following request parameters.

ParameterType
creqstringChallenge Request. This is part of the 3DS protocol, representing a request message sent from the PayConex Frontend to the ACS (Access Control Server) to initiate a challenge.
cresstringChallenge Response. This is the response message from the ACS (Access Control Server, a component of the issuer domain) to the PayConex Frontend, indicating the result of the challenge.
threeDSSessionDatastringThis generally refers to the session data that maintains the state and context of the user's interaction with the system throughout the 3DS authentication process.

3DS Status

After the challenge complete call is made to update the 3DS status, we get the successful 3DS response with the corresponding threeDSecureId, indicating the 3DS data is ready to use in a transaction for authorization.

GET /api/v4/accounts/{accountId}/3DS/{threeDSecureId}/status

Response

{
  "threeDSecureId": "tds_2a8161d22a004ad99433ebe004bc3c78",
  "status": "PROCESS_DONE",
  "serverTransactionID": "6d62b365-0696-463c-ab8b-e2605145c63f",
  "threeDSecure": {
    "eci": "05",
    "version": "2.2.0",
    "authenticationValue": "ji+A0S72pd/RFOrr1fwCKXKqDno=",
    "dsTransId": "2abe7b65-45cd-4a58-bcd3-8b10b8f8f50e",
    "status": "Y"
  },
  "info": {
    "cardBrand": "VISA",
    "transactionOutcomeMessage": "3DS authentication and account verification were successful.",
    "threeDSecureServerTransactionId": "6d62b365-0696-463c-ab8b-e2605145c63f",
    "challenged": false,
    "enrolled": false
  }
}

If the challenge remains to be completed, the following response is returned:

{
  "threeDSecureId": "tds_92bb14615e3a4663aeea110ad4669042",
  "status": "CHALLENGE_REQUIRED",
  "serverTransactionID": "5280c42c-ed93-4dff-bc93-14889548faa8",
  "threeDSecure": {
    "eci": "07",
    "version": "2.1.0",
    "amexDsTransId": "02010000",
    "status": "U"
  },
  "info": {
    "cardBrand": "AMERICAN EXPRESS",
    "transactionOutcomeMessage": "3DS authentication and account verification could not be performed.",
    "threeDSecureServerTransactionId": "eyJhbiI6IjM0MDAwMDExMTExMTExNyIsInhk",
    "challenged": false,
    "enrolled": false
  },
  "error": {
    "threeDSecureServerTransactionId": "eyJhbiI6IjM0MDAwMDExMTExMTExNyIsInhk",
    "internalMessage": "3DS authentication and account verification could not be performed."
  }
}

Integration Summary Diagram

With the challenge remaining to be completed by the customer, this GET request should run in the background to periodically check the 3DS result. This can also be referred to as Retry.

Once it evaluates to PROCESS_DONE, that means the challenge is completed and the 3DS status updated.

For example,

const status_id = setInterval(() => {
    const status_URL = `https://api-cert.payconex.net/api/v4/accounts/${accountId}/3DS/${threeDSecureId}/status`
    let res = await fetch(status_URL, {
        method: "GET",
        headers: {
            Authorization: `Bearer ${auth_bearer_token_from_above}`,
        }
    })
    let threeDSdata = await res.json()
    if(threeDSdata.status == "PROCESS_DONE") {
        // Authorize and process transaction with 3DS data
        clearInterval(status_id)
    }
}, 1000) // User-defined interval value. Check every 1 second in this case

It is recommended that the merchant comes up with a solution that specifies how many times the status is run before expiring. The expiry time may also depend on the challenge form from the card issuer.

In other words, the following code gives the customer 30 seconds to complete the challenge.

let c = 0
const status_id = setInterval(() => {
    const status_URL = `https://api-cert.payconex.net/api/v4/accounts/${accountId}/3DS/${threeDSecureId}/status`
    let res = await fetch(status_URL, {
        method: "GET",
        headers: {
            Authorization: `Bearer ${auth_bearer_token_from_above}`,
        }
    })
    let threeDSdata = await res.json()
    if(threeDSdata.status == "PROCESS_DONE") {
        // Authorize and process transaction with 3DS data
        clearInterval(status_id)
    }
    if(c == 30) {
        // Render the iframe with challenge expired.
        clearInterval(status_id)
    }
    c++
}, 1000) // User-defined interval value. Check every 1 second in this case

With that in mind, the Browser Authentication, 3DS status, and transaction authorization workflow can be depicted in the following diagram:

<<glossary:3DS>> Integration Summary Diagram

3DS Integration Summary Diagram

Authorizing QSAPI Transaction with 3DS Data

Finally, the server processes a QSAPI transaction using the returned threeDSecure parameters from the 3DS Status response. These response parameters correspond to the following fields required in the QSAPI transaction.

3DS Response ParameterQSAPI Parameter
threeDSecure.dsTransIdddds_dstransid
threeDSecure.authenticationValueddds_authenticationvalue
threeDSecure.eciddds_eci
threeDSecure.statusddds_status
threeDSecure.versionddds_version

For the comprehensive descriptions of these, refer to 3D Secure | 3DS Data Parameters.

curl --location --request POST 'https://secure.payconex.net/api/qsapi/3.8' \
--header 'Accept: application/json' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--data-urlencode 'account_id=180000005402' \
--data-urlencode 'api_accesskey=5d7849b64dc5639d6bbac8dd44b385be' \
--data-urlencode 'response_format=JSON' \
--data-urlencode 'transaction_type=SALE' \
--data-urlencode 'tender_type=CARD' \
--data-urlencode 'card_verification=555' \
--data-urlencode 'card_expiration=1225' \
--data-urlencode 'transaction_amount=1.00' \
--data-urlencode 'card_number=4124939999999990' \
# 3DS-Authenticated Parameters
--data-urlencode 'ddds_dstransid=2abe7b65-45cd-4a58-bcd3-8b10b8f8f50e' \
--data-urlencode 'ddds_authenticationvalue=ji+A0S72pd/RFOrr1fwCKXKqDno=' \
--data-urlencode 'ddds_eci=05' \
--data-urlencode 'ddds_status=Y' \
--data-urlencode 'ddds_version=2.2.0'

🚧

ddds_dstransid and Amex cards

For Amex cards, usually a separate amexDsTransId is used. Its value has to be passed on in the ddds_dstransid field.