Skip to content
API DocsDocs

3D Secure Authentication

Complete guide to 3DS2 authentication — frictionless and challenge flows, device data, liability shift, and testing

10 min readUpdated Mar 26, 2026

3D Secure (3DS) is a security protocol that adds an authentication step to card-not-present transactions. The cardholder's issuing bank verifies their identity before approving the payment — reducing fraud and shifting chargeback liability from the merchant to the issuer.

Exirom supports 3DS2 (the current EMV standard), which enables frictionless authentication for low-risk transactions and challenge flows for higher-risk ones.


#How It Works

3DS operates across three domains defined by the EMV 3DS protocol:

DomainPartyRole
AcquirerThe acquiring bank / PSP processing the transactionInitiates the authentication request
IssuerCardholder's bank (Access Control Server)Evaluates risk, decides frictionless or challenge
InteroperabilityCard networks (Visa Secure, Mastercard Identity Check, etc.)Routes messages between acquirer and issuer via the Directory Server

#Where Exirom Fits

Exirom is a payment orchestrator — it sits between your application and the underlying acquirers/PSPs. Depending on the routing configuration and the capabilities of the downstream processor:

  • Exirom initiates 3DS when the acquirer/PSP supports receiving pre-authenticated transactions (with CAVV/ECI data).
  • The acquirer/PSP initiates 3DS when their API only accepts card credentials and handles authentication internally.

In both cases, the flow from your perspective is the same — you send the payment request to Exirom, and the response tells you whether a challenge is needed. Exirom abstracts the 3DS complexity regardless of who initiates it.


#Flow Diagram

3D Secure Authentication Flow


#The Two Flows

#Frictionless Flow (Best Case)

The issuer silently authenticates the cardholder using risk-based analysis — no customer interaction needed:

  1. You submit payment request with device + billing data
  2. The 3DS authentication happens behind the scenes (issuer evaluates device fingerprint, transaction history, billing match)
  3. Issuer approves → transaction proceeds immediately
  4. You receive webhook with transactionStatus: "SUCCEED"

No redirect. No challenge page. No customer friction. The more data you provide, the higher your frictionless approval rate.

Edge cases in frictionless flows:

In practice, "frictionless" doesn't always mean zero redirect. Some issuers return a challengeUrl even for frictionless approvals. Two variations we've seen in production:

  • Silent challenge page: The response includes CUSTOMER_VERIFICATION + challengeUrl, but when the customer opens the page, authentication completes automatically — no OTP or input required. The page redirects back immediately. You should still redirect the customer in this case.
  • Callback without redirect: The response includes CUSTOMER_VERIFICATION + challengeUrl, but the webhook arrives with SUCCEED before the customer is redirected. The issuer approved silently despite returning a challenge URL. Your webhook handler should accept this — if you receive a SUCCEED callback, honor it regardless of whether the customer completed the redirect.

Both cases reinforce why the webhook is the only source of truth, not the redirect outcome.

#Challenge Flow

When the issuer needs active verification (OTP, biometric, banking app):

  1. You submit payment request
  2. Response returns transactionStatus: "CUSTOMER_VERIFICATION" with challengeUrl and challengeUrlIframe
  3. You redirect customer to challengeUrl (or embed challengeUrlIframe)
  4. Customer authenticates with their bank
  5. Customer's browser redirects to your successRedirectUrl or failureRedirectUrl
  6. You receive webhook with final transactionStatus: "SUCCEED" or "FAILED"

The webhook callback is the ONLY reliable source of the transaction result. Never rely on the redirect URL to determine payment outcome — network issues, browser crashes, or ad blockers can prevent the redirect from completing. Always wait for the webhook.


#Step-by-Step Implementation

#1. Submit the Payment

Include device data, billing details, and redirect URLs in your payment request:

{
  "requestId": "req_3ds_001",
  "mid": "merchant_123",
  "amount": "49.99",
  "currency": "USD",
  "card": {
    "number": "4111111111111111",
    "expMonth": "12",
    "expYear": "2028",
    "cvv": "123",
    "cardHolderName": "John Doe",
    "requestThreeDSecure": true
  },
  "lang": "en",
  "billingDetails": {
    "externalUserId": "user-001",
    "firstName": "John",
    "lastName": "Doe",
    "email": "john@example.com",
    "phone": "+12125551234",
    "address1": "123 Main St",
    "city": "New York",
    "state": "NY",
    "country": "US",
    "postalCode": "10001",
    "dateOfBirth": "1990-01-15"
  },
  "order": {
    "orderId": "ord-001",
    "title": "Product Purchase"
  },
  "device": {
    "ip": "203.0.113.42",
    "userAgent": "Mozilla/5.0 ...",
    "accept": "text/html,application/json",
    "acceptLanguage": "en-US,en;q=0.9",
    "javaEnabled": false,
    "javaScriptEnabled": true,
    "deviceLanguage": "en",
    "colorDepth": "24",
    "screenHeight": "1080",
    "screenWidth": "1920",
    "deviceTimezone": "America/New_York"
  },
  "successRedirectUrl": "https://yoursite.com/payment/success",
  "failureRedirectUrl": "https://yoursite.com/payment/failure",
  "callbackUrl": "https://yourserver.com/webhooks/payment"
}

#2. Check the Response

// Frictionless — no redirect needed
{
  "transactionId": "txn_abc123",
  "transactionStatus": "SUCCEED",
  "challengeUrl": null
}
 
// Challenge required — redirect the customer
{
  "transactionId": "txn_abc123",
  "transactionStatus": "CUSTOMER_VERIFICATION",
  "challengeUrl": "https://acs.issuerbank.com/3ds/challenge?id=xyz",
  "challengeUrlIframe": "https://acs.issuerbank.com/3ds/challenge?id=xyz&iframe=true"
}

#3. Handle the Challenge (if required)

Option A: Full-page redirect (recommended for mobile)

if (response.transactionStatus === 'CUSTOMER_VERIFICATION') {
  window.location.href = response.challengeUrl;
}

Option B: Iframe embed (better desktop UX)

<iframe
  src="https://acs.issuerbank.com/3ds/challenge?id=xyz&iframe=true"
  width="500"
  height="600"
  style="border: none;"
></iframe>

Recommended iframe sizes: 250x400, 390x400, 500x600, 600x400, or fullscreen.

Do not add the sandbox attribute to the iframe — the issuer's content may fail to load.

#4. Customer Returns

After authentication, the customer's browser redirects to your successRedirectUrl or failureRedirectUrl. Show a "processing" message while you wait for the webhook.

Do NOT use the redirect URL to determine payment outcome. The redirect is for UX only. The customer may close the browser, lose connectivity, or be blocked by an ad blocker before the redirect completes. Your backend must rely solely on the webhook callback.

If using iframe, the iframe navigates to your redirect URL. Use postMessage to notify the parent window:

// Inside your redirect page (loaded in iframe)
// Replace 'https://yoursite.com' with your actual domain — never use '*' in production
window.parent.postMessage({
  type: '3ds-complete',
  transactionId: new URLSearchParams(location.search).get('transactionId')
}, 'https://yoursite.com');

#5. Process the Webhook (Source of Truth)

The webhook callback is the only authoritative source of the transaction result:

{
  "transactionId": "txn_abc123",
  "transactionStatus": "SUCCEED",
  "declineCode": null
}
StatusMeaningAction
SUCCEEDPayment approved after successful authenticationFulfill the order
FAILEDAuthentication failed or payment declinedShow error, offer retry
PENDINGRare — transaction still processing after 3DSWait for webhook with final status
PROCESSINGRare — transaction still processing after 3DSWait for webhook with final status

Critical: Your payment processing logic must be driven by webhooks, not redirects. Implement your webhook handler before going live.

See Webhook Best Practices for handling guidance.


#Liability Shift

When 3DS authentication succeeds, liability for fraudulent chargebacks shifts from the merchant to the card issuer:

  • With 3DS: If a fraud-related chargeback occurs, the issuer bears the cost — not you.
  • Without 3DS: You are liable for fraud-related chargebacks.

Liability shift applies when:

  • 3DS authentication was attempted and succeeded
  • The transaction was processed with valid authentication data (CAVV/ECI)

Note: Liability shift covers fraud disputes only. It does not cover service/product disputes (e.g., "item not received"). You must still respond to all dispute inquiries.


#Maximizing Frictionless Approval

The more data you provide, the higher the chance the issuer approves without a challenge. Here's what matters most:

#High-Impact Fields (always send these)

FieldWhy It Matters
billingDetails.emailIssuer can verify against cardholder profile
billingDetails.phoneUsed for risk scoring
billingDetails.firstName + lastNameMatched against card records
billingDetails.address1 + postalCodeAVS (Address Verification) check
device.ipGeolocation + risk scoring
device.userAgentDevice fingerprinting
All device.* fieldsBrowser fingerprint for ACS risk model

#Additional Fields That Help

FieldImpact
billingDetails.dateOfBirthAge verification for certain issuers
billingDetails.countryGeographic risk assessment
kycVerifiedTells the issuer you've verified the customer
previousPaymentCountReturning customers score lower risk

Frictionless rate tip

Merchants who send complete billing + device data typically see 70–85% frictionless rates. Minimal data often results in 100% challenge flows.

#Collecting Device Data

Use this JavaScript snippet to collect all required device fields from the customer's browser:

function collectDeviceData() {
  return {
    ip: null, // Set server-side from the request IP
    userAgent: navigator.userAgent,
    accept: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
    acceptLanguage: navigator.language || navigator.userLanguage || 'en-US',
    javaEnabled: navigator.javaEnabled ? navigator.javaEnabled() : false,
    javaScriptEnabled: true,
    deviceLanguage: navigator.language?.split('-')[0] || 'en',
    colorDepth: String(screen.colorDepth),
    screenHeight: String(screen.height),
    screenWidth: String(screen.width),
    deviceTimezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
  };
}

Note: Set device.ip on your server from the incoming request's IP address. Do not collect it client-side.


#Required Device Fields

FieldTypeDescription
ipStringCustomer's IP address (set server-side)
userAgentStringBrowser User-Agent string
acceptStringBrowser Accept header value
acceptLanguageStringBrowser Accept-Language header
javaEnabledBooleanWhether Java is enabled
javaScriptEnabledBooleanWhether JavaScript is enabled
deviceLanguageStringDevice language (e.g., en)
colorDepthStringScreen color depth (e.g., "24")
screenHeightStringScreen height in pixels
screenWidthStringScreen width in pixels
deviceTimezoneStringDevice timezone (e.g., America/New_York)

Missing device fields will cause 3DS to fail or fall back to a less optimal authentication path.


#When to Use requestThreeDSecure

ValueBehavior
trueRequest 3DS authentication. Recommended for all transactions.
falseSkip 3DS. The issuer or card network may still force authentication if required by regulations (SCA/PSD2).
(omitted)The downstream processor decides based on card network rules and risk assessment.

Recommendation: Always set requestThreeDSecure: true. Even when the flow is frictionless, successful 3DS provides liability shift protection.


#Sandbox Testing

Use these test card numbers to simulate different 3DS scenarios:

Card NumberScenario
4263982640269299Challenge flow — returns challenge link with ACCEPT/DECLINE buttons
4580271111111234Frictionless flow — redirects to success URL after short delay
(any other number)Failed flow — redirects to failure URL after short delay

Test in sandbox at https://sandbox.api.exirom.com/api with your sandbox credentials. See Sandbox Testing for full details.


#Common 3DS Errors

ScenarioResultFix
Missing device fieldsAuthentication fails before challengeSend all 11 device fields
Customer abandons challengeStatus → FAILED via webhookShow timeout message, offer retry
Challenge page timeoutStatus → FAILED with decline codeCheck declineCode for details
Invalid challengeUrlCustomer sees error pageUse the URL from the response, not a cached one
Missing redirect URLsCustomer stuck after authenticationAlways include both successRedirectUrl and failureRedirectUrl
Relying on redirect instead of webhookMissed payments, inconsistent stateAlways use webhook as source of truth

#Regulatory Context (SCA / PSD2)

In the European Economic Area (EEA), Strong Customer Authentication (SCA) is required by PSD2 regulation for most online card payments. 3DS2 is the primary method for SCA compliance.

When required, 3DS is enforced automatically by the card network or issuer — regardless of the requestThreeDSecure value. Exemptions exist for:

  • Low-value transactions (under €30, up to 5 consecutive or €100 cumulative)
  • Trusted beneficiaries (whitelisted by the cardholder)
  • Recurring payments (after initial authentication)
  • Merchant-initiated transactions

#See Also

Note: 3DS can also be triggered during tokenization and saved-card charges. The same challenge handling logic applies.

Was this helpful?