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 Name | Endpoint | Description |
---|---|---|
Certification (Testing) Environment | https://api-cert.payconex.net | Use this environment for development and testing. |
Staging (Development) Environment | https://api-staging.payconex.net | Use this environment for development. |
Production (Live) Environment: | https://api.payconex.net | Use 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.
Section | API Endpoint | Description |
---|---|---|
Initiate Bluefin 3DS with Card Details | /api/v4/accounts/{accountId}/3DS/init-card-details | Initiate the 3DS process with the card data (raw PAN, expiry, and cvv). |
ACS Method | This 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-authenticate | Performs 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 Request | The 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}/status | Returns 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
withacsURL
,threeDSecureData
, andacsResultURL
, 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
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.
Parameter | Type | |
---|---|---|
creq | string | Challenge 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. |
cres | string | Challenge 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. |
threeDSSessionData | string | This 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:

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 Parameter | QSAPI Parameter |
---|---|
threeDSecure.dsTransId | ddds_dstransid |
threeDSecure.authenticationValue | ddds_authenticationvalue |
threeDSecure.eci | ddds_eci |
threeDSecure.status | ddds_status |
threeDSecure.version | ddds_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 theddds_dstransid
field.
Updated about 21 hours ago