Skip to main content

3DS Integration at Tuna#

Tuna has its own MPI (Merchant Plug-In) powered by Cybersource/Cardinal Cruise (Visa), which means you don't need to integrate with an external MPI provider. The 3DS authentication layer is handled transparently by Tuna's platform.

3DS is activated on a per-merchant basis, according to each customer's risk and conversion strategy. The decision to use 3DS, and for which payment flows, is configured in Tuna Console's no-code flow editor. If you need help enabling or tuning 3DS for your account, contact your Account Manager or the Tuna Support team.

Not sure which integration mode to use?

If you use Tuna Checkout (hosted payment link or embedded), skip to the Tuna Checkout section — just configure your flow.

If you are on VTEX, skip to the VTEX section — it only takes one step. For all other platforms, choose one of the four direct integration modes below.


How 3DS works at Tuna#

3DS sequence diagram: Backend calls NewSession, returns sessionId to Frontend; Frontend calls Token/Generate via JS, returns token and auth fields to Backend; Backend calls Payment/Init

The three fields your backend must pass to Payment/Init inside authenticationInformation:

FieldSourceDescription
codeReturned by 3DS setup / Token/GenerateThe same code sent to Generate in Token
referenceIdReturned by 3DS setup / Token/GenerateCardinal/Cybersource reference ID
transactionIdReturned by 3DS setup / Token/GenerateCardinal/Cybersource transaction ID

Integration modes#

There are four integration approaches available in our official Postman collection. Choose the one that fits your architecture:

ModeWho tokenizes?Who calls Payment/Init?Best for
3DS with pure JSYour frontend formYour backendFull control, custom UI
3DS using tuna.js for tokenizationtuna.js (frontend)Your backendSimplify PCI scope on frontend
3DS using tuna.js for all interactionstuna.js (frontend)tuna.js (frontend)Fastest integration, full delegation
3DS with manual tokenizationYour frontend (JS)Your backendDirect control over card capture and tokenization

Step 1 — Create a session (all modes)#

The first step is always the same regardless of integration mode: create a Token session via your backend.

note

Always create sessions from your backend — never expose your apptoken on the frontend.

curl -X POST https://token.tunagateway.com/api/Token/NewSession \
-H 'Content-Type: application/json' \
-H 'x-tuna-account: YOUR_ACCOUNT' \
-H 'x-tuna-apptoken: YOUR_APP_TOKEN' \
-d '{
"Customer": {
"ID": "customer-123",
"Name": "Maria Silva",
"Email": "maria@example.com"
},
"PaymentAccount": "YOUR_ACCOUNT",
"partneruniqueid": "ORDER-001",
"Is3dsTest": true
}'

Set Is3dsTest: true while testing in the sandbox. Remove it (or set to false) in production.


Mode 1: 3DS with pure JS#

Your frontend handles card capture (using a custom form or any card entry UI). Your backend then calls Payment/Init with the 3DS auth data obtained from the frontend.

How to use the Tuna sample form#

Open the Tuna-hosted sample for this mode:

👉 Pure JS sample form

  1. Enter the sessionId you obtained in Step 1.
  2. Fill in the card details (use a Cybersource test card — see test cards below).
  3. The form runs the 3DS device data collection and authentication challenge.
  4. On success it displays the token, code, referenceId, and transactionId.
  5. Copy those values into your Postman Init / Credit or Init / Debit request.

Backend: Call Payment/Init#

After the frontend completes, your backend calls POST https://engine.tunagateway.com/api/Payment/Init:

{
"TokenSession": "{{sessionid}}",
"partneruniqueid": "ORDER-001",
"customer": {
"id": "customer-123",
"name": "Maria Silva",
"email": "maria@example.com",
"document": "744.479.870-23",
"documentType": "CPF"
},
"paymentItems": {
"items": [
{
"amount": 100,
"detailUniqueId": "ITEM-1",
"productDescription": "Test Product",
"itemQuantity": 1
}
]
},
"paymentdata": {
"paymentmethods": [
{
"partneruniqueid": "ORDER-001",
"paymentmethodtype": "7",
"amount": 100,
"installments": 1,
"cardinfo": {
"token": "{{token_from_frontend}}",
"tokenprovider": "Tuna",
"savecard": false
},
"AuthenticationInformation": {
"Code": "{{sessionid}}",
"ReferenceId": "{{referenceId_from_frontend}}",
"TransactionId": "{{transactionId_from_frontend}}"
}
}
],
"countrycode": "BR",
"amount": 100,
"currency": "BRL"
},
"frontdata": {
"useragent": "Mozilla/5.0 ...",
"IpAddress": "203.0.113.10"
},
"istest": false,
"isfromfrontend": false
}

Mode 2: 3DS using tuna.js for tokenization#

tuna.js handles card field rendering, device data collection, and 3DS authentication on the frontend. It returns the token, code, referenceId, and transactionId to your page. Your backend then calls Payment/Init.

How to use the Tuna sample form#

👉 tuna.js tokenization sample form

  1. Provide the sessionId from Step 1.
  2. tuna.js renders the card fields and handles 3DS transparently.
  3. On completion it returns the authentication data you need for Payment/Init.
  4. Pass those values to your backend, which calls Payment/Init using the same request structure as Mode 1 above.

Adding tuna.js to your page#

<script src="https://js.tuna.uy/tuna-essentials.js"></script>

Initialize with the sessionId from your backend and configure 3DS:

const tunaCheckout = new Tuna.Checkout({
sessionId: "{{sessionid}}",
account: "YOUR_ACCOUNT",
environment: "sandbox", // or "production"
callback: function(paymentData) {
// paymentData contains token, code, referenceId, transactionId
// Send to your backend to call Payment/Init
yourBackend.initPayment(paymentData);
}
});

See the full tuna.js documentation for all configuration options.


Mode 3: 3DS using tuna.js for all interactions#

tuna.js handles the complete flow: card entry, 3DS authentication, and the Payment/Init call — all from the browser. Your backend only needs to create the session.

note

Because tuna.js calls Payment/Init from the frontend in this mode, the session must be created with the full order data (amount, items, etc.) — not just customer data.

How to use the Tuna sample form#

👉 tuna.js full-flow sample form

  1. Your backend creates a NewSession including the PaymentAccount and partneruniqueid.
  2. Return the sessionId to the frontend.
  3. tuna.js renders the checkout, performs 3DS, and submits the payment.
  4. Poll the Payment Status API or listen to webhooks to confirm the outcome.

For the Postman collection, this mode only requires the NewSession call — tuna.js takes over from there.


Mode 4: 3DS with manual tokenization#

In this mode your frontend calls Token/Generate directly using JavaScript, and your backend calls Payment/Init. This gives you full control over the card capture form while keeping sensitive card data off your backend.

PCI DSS requirement

Token/Generate — and any API that handles raw card data — must be called from the browser (frontend JS), not from your backend server, unless your company holds a PCI DSS Level 1 certification. Routing card numbers through your backend without Level 1 certification puts you out of PCI scope.

This is why NewSession is always called from the backend (it only handles non-sensitive identifiers), while Token/Generate is called from the frontend.

Step 1: Create session (already covered above)#

Step 2: Tokenize the card with 3DS code (frontend JS)#

Your frontend calls POST https://token.tunagateway.com/api/Token/Generate via JavaScript, passing the card data and the 3DS Code from the Cardinal/Cybersource payer auth setup:

{
"SessionId": "{{sessionid}}",
"Card": {
"CardNumber": "4000000000002701",
"CardHolderName": "Maria Silva",
"ExpirationMonth": 1,
"ExpirationYear": 2029,
"CVV": "456",
"SingleUse": false
},
"AuthenticationInformation": {
"Code": "{{Code}}"
}
}

Step 3: Call Payment/Init#

Use the same Payment/Init request structure as Mode 1, with the token from Token/Generate and the authenticationInformation fields from the 3DS frontend flow.


Providing browser/device data#

For all modes, passing frontData in Payment/Init is strongly recommended when using 3DS. It enables more accurate risk assessment and better frictionless rates:

"frontdata": {
"useragent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",
"IpAddress": "203.0.113.10",
"acceptheader": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"acceptlanguage": "pt-BR,pt;q=0.9,en-US;q=0.8",
"colorDepth": "24",
"screenHeight": "864",
"screenWidth": "1536",
"timeDifference": "-180",
"javaEnabled": "N",
"javaScriptEnabled": "Y"
}
FieldDescription
useragentBrowser User-Agent header
IpAddressCardholder IP address
acceptheaderHTTP Accept header
acceptlanguageBrowser language (IETF BCP47)
colorDepthScreen color depth in bits
screenHeight / screenWidthScreen dimensions in pixels
timeDifferenceUTC offset in minutes (e.g. -180 for BRT)
javaEnabled"Y" or "N"
javaScriptEnabled"Y" or "N"

VTEX#

For VTEX merchants, 3DS is enabled out of the box. All you need to do is install Tuna's VTEX IO app:

tunabr.tuna-gateway

Install it from the VTEX App Store or via the VTEX IO CLI:

vtex install tunabr.tuna-gateway

After installation, follow the standard Tuna VTEX plugin setup to configure your Gateway Affiliation and credentials. There is no additional 3DS configuration required — Tuna handles authentication automatically based on your flow settings in Tuna Console.

Headless & mobile apps on VTEX

Tuna has partnerships with the leading headless and mobile implementors in the VTEX ecosystem. 3DS is fully supported in storefronts built by Compra Rápida, Trinio, and Kobe. If your storefront runs on one of these platforms, contact your implementor or your Tuna Account Manager to enable 3DS.


Tuna Checkout#

If you use Tuna Checkout — whether as a hosted payment link shared externally or embedded in your page via iframe — 3DS is supported out of the box with no additional integration work.

All you need is the flow configuration in Tuna Console (no-code flow editor) to enable 3DS for the relevant payment methods. Tuna Checkout will handle the full authentication challenge flow automatically, including device data collection and the redirect/embedded challenge UI.

If you need help setting up your flow to include 3DS, contact your Account Manager or the Tuna Support team.


Cybersource test cards#

Tuna's MPI runs on Cybersource/Cardinal Cruise. Use the test cards below in the sandbox environment.

How to use test cards#

  • Replace every X in the card number with 0 (zero).
  • Use expiration month: January (01) and expiration year: current year + 3 (e.g., for 2026 use 01/2029).
  • Any CVV (e.g., 123) works in the sandbox.

Visa (Card Type = 001) — most common for Brazilian merchants#

Scenario3DS 2.2 Test CardEnroll resultValidation result
2.1 Frictionless — authentication successful4XXXXX XX XXXX 27X14000000000002701RC=100, VERes=Y, PARes=Y, ECI=05 (vbv)No challenge required
2.2 Frictionless — authentication unsuccessful4XXXXX XX XXXX 45744000000000004574RC=476, VERes=Y, PARes=N, ECI=07
2.3 Stand-in — attempted4XXXXX XX XXXX 27194000000000002719RC=100, VERes=Y, PARes=A, ECI=06 (vbv_attempted)
2.4 Frictionless — authentication unavailable4XXXXX XX XXXX 23134000000000002313RC=100, VERes=Y, PARes=U
2.5 Frictionless — authentication rejected4XXXXX XX XXXX 25374000000000002537RC=476, VERes=Y, PARes=R
2.6 Authentication not available (system error)4XXXXX XX XXXX 299X4000000000002990RC=100, VERes=U
2.7 Check enrollment error4XXXXX XX XXXX 24464000000000002446RC=100, VERes=U
2.8 Timeout4XXXXX XX XXXX 23544000000000002354RC=100, VERes=U
2.9 Step-up — challenge successful4XXXXX XX XXXX 25X34000000000002503RC=475, VERes=Y, PARes=CRC=100, PARes=Y, ECI=05 (vbv)
2.10 Step-up — challenge unsuccessful4XXXXX XX XXXX 42934000000000004293RC=475, VERes=Y, PARes=CRC=476, PARes=N

Mastercard (Card Type = 002)#

Scenario3DS 2.2 Test CardEnroll result
2.1 Frictionless successful52XXXX XX XXX 22355200000000002235RC=100, VERes=Y, PARes=Y, ECI=02 (spa)
2.2 Frictionless unsuccessful52XXXX XX XXXX 22765200000000002276RC=476, VERes=Y, PARes=N
2.3 Stand-in attempted52XXXX XX XXXX 24825200000000002482RC=100, VERes=Y, PARes=A, ECI=01 (spa)
2.9 Step-up successful52XXXX XX XXXX 21515200000000002151RC=475, PARes=C; validation RC=100, ECI=02

Elo (Card Type = 054)#

Scenario3DS 2.2 Test CardEnroll resultValidation result
2.1 Frictionless — authentication successful65X529 XX XXXX 2XXX6505290000002000RC=100, VERes=Y, PARes=Y, ECI=05 (cs)No challenge required
2.2 Frictionless — authentication unsuccessful65X529 XX XXXX 2X186505290000002018RC=476, VERes=Y, PARes=N, ECI=07
2.3 Stand-in — attempted65X529 XX XXXX 20266505290000002026RC=100, VERes=Y, PARes=A, ECI=06 (cs_attempted)
2.4 Frictionless — authentication unavailable65X529 XX XXXX 2X346505290000002034RC=100, VERes=Y, PARes=U
2.5 Frictionless — authentication rejected65X529 XX XXXX 2X836505290000002083RC=476, VERes=Y, PARes=R
2.9 Step-up — challenge successful65X529 XX XXXX 219X6505290000002190RC=475, VERes=Y, PARes=CRC=100, PARes=Y, ECI=05 (cs)

ECI value reference#

The ECI (E-Commerce Indicator) value in the enrollment/validation response indicates the authentication outcome:

NetworkSuccessful auth ECIString valueAttempted ECIFailed ECI
Visa05vbv06 (vbv_attempted)07
Mastercard02spa0100
Amex05aesk06 (aesk_attempted)07
Elo05cs06 (cs_attempted)07
Discover05dipb06 (dipb_attempted)07

Interpreting test results#

PARes statusMeaningAction
YAuthentication successfulProceed with authorization — full liability shift
AAttempted (stand-in)Proceed with authorization — partial liability shift
UUnavailableProceed with authorization — no liability shift
NAuthentication failedDo not proceed; ask for another payment method
RAuthentication rejectedDo not proceed; ask for another payment method
CChallenge required (step-up)Wait for challenge completion before proceeding
Postman collection

Download the official collection at github.com/tuna-software/postman. The 3DS folder contains all four integration mode examples with pre-configured environments and test variables.


Related references#