Skip to main content

3D Secure

Add bank-issued second-factor authentication to reduce fraud by 70%+ and shift chargeback liability to card issuers.

Overviewโ€‹

3D Secure (3DS) is an additional security layer for online credit and debit card transactions that requires cardholders to complete an extra verification step with their card-issuing bank. This authentication significantly reduces fraud and shifts liability for chargebacks from merchants to banks.

Key Benefits:

  • โœ… 70%+ fraud reduction - Extra authentication prevents unauthorized use
  • โœ… Liability shift - Banks assume chargeback responsibility for authenticated transactions
  • โœ… Lower chargeback rates - Reduces disputes and associated fees
  • โœ… Frictionless option - 3DS2 allows instant approval for low-risk transactions
  • โœ… Required for compliance - Mandatory for certain business types and regions

3D Secure 1 vs 3D Secure 2โ€‹

Feature3DS1 (Deprecated)3DS2 (Current Standard)
StatusDeprecated October 2022Active and required
Authentication MethodsOTP/SMS onlyOTP, SMS, biometrics, facial recognition
Frictionless FlowNot availableAvailable for low-risk transactions
Mobile SupportLimitedFull in-app support
Data SharingLimitedRich transaction context (device, location, history)
User ExperienceAlways requires interactionSmart risk-based decisions
Authorization RateLowerHigher (frictionless reduces cart abandonment)
3DS1 Sunset

3D Secure 1 was deprecated in October 2022. All implementations should use 3D Secure 2 (3DS2).

How 3DS2 Worksโ€‹

Frictionless Flowโ€‹

  • Bank analyzes transaction risk using rich data (device ID, geolocation, purchase history)
  • If risk is low enough, transaction approved instantly without customer interaction
  • Same liability protection as challenge flow
  • Significantly better user experience - no redirect, faster checkout

Challenge Flowโ€‹

  • Used for higher-risk transactions requiring additional verification
  • Customer redirected to bank's authentication page
  • Multiple authentication options:
    • OTP (One-Time Password via SMS)
    • Biometric (fingerprint, face ID)
    • Bank app push notification
  • Typically takes 1-3 minutes
  • May extend to 10 minutes if first-time enrollment

When to Enable 3D Secureโ€‹

Mandatory Forโ€‹

  • โœ… Travel & accommodation - Hotels, airlines, booking platforms
  • โœ… Digital goods - Music, movies, software, games
  • โœ… Virtual items - Game currency, in-app purchases
  • โœ… Prepaid cards - Gift cards, top-up services
  • โœ… High-risk merchants - As determined by fraud analysts
  • High-value transactions (> THB 10,000 or equivalent)
  • Businesses with chargeback rates > 0.3%
  • International card transactions
  • First-time customers
  • Suspicious transaction patterns
  • โŒ Recurring subscriptions - Prevents automatic charging
  • โŒ One-click repeat purchases - Requires interaction each time
  • โŒ Low-value transactions - May reduce conversion on small purchases
Subscription Workaround

For subscriptions: Use 3DS for initial card setup, then charge saved customer ID without 3DS for subsequent payments (subject to bank rules).

Implementation Guideโ€‹

Step 1: Enable 3DS on Your Accountโ€‹

Contact Omise support to enable 3D Secure:

Email: support@omise.co
Subject: Enable 3D Secure for [Your Account]

Step 2: Create Token (Standard Process)โ€‹

// Client-side: Create token as usual
Omise.setPublicKey("pkey_test_YOUR_KEY");

Omise.createToken("card", {
name: "John Doe",
number: "4242424242424242",
expiration_month: 12,
expiration_year: 2027,
security_code: "123"
}, function(statusCode, response) {
if (statusCode === 200) {
// Send token to server
submitToServer(response.id);
}
});

Step 3: Create Charge with return_uriโ€‹

The return_uri parameter is required for 3DS:

curl https://api.omise.co/charges \
-u skey_test_YOUR_SECRET_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "return_uri=https://yourdomain.com/payment/callback"

Step 4: Handle Customer Redirectโ€‹

If authorize_uri is present in the response, redirect customer:

// Server-side
app.post('/create-charge', async (req, res) => {
const charge = await omise.charges.create({
amount: req.body.amount,
currency: 'THB',
card: req.body.token,
return_uri: 'https://yourdomain.com/payment/callback',
metadata: {
order_id: req.body.order_id
}
});

if (charge.authorize_uri) {
// 3DS authentication required
res.json({
requires_3ds: true,
authorize_uri: charge.authorize_uri,
charge_id: charge.id
});
} else {
// Frictionless - charge completed
res.json({
requires_3ds: false,
status: charge.status,
charge_id: charge.id
});
}
});
// Client-side
fetch('/create-charge', {
method: 'POST',
body: JSON.stringify({ token, amount, order_id })
})
.then(res => res.json())
.then(data => {
if (data.requires_3ds) {
// Redirect to 3DS authentication
window.location.href = data.authorize_uri;
} else {
// Payment complete
window.location.href = '/payment-success';
}
});

Step 5: Handle Callbackโ€‹

After authentication, customer is redirected back to your return_uri:

app.get('/payment/callback', async (req, res) => {
// Retrieve charge status
const chargeId = req.query.charge_id || req.session.charge_id;

const charge = await omise.charges.retrieve(chargeId);

if (charge.status === 'successful') {
// 3DS authentication passed
await processOrder(charge.metadata.order_id);
res.redirect('/payment-success?order=' + charge.metadata.order_id);
} else if (charge.status === 'failed') {
// 3DS authentication failed
res.redirect('/payment-failed?reason=' + encodeURIComponent(charge.failure_message));
} else {
// Still pending - check again or use webhooks
res.redirect('/payment-pending');
}
});

Step 6: Implement Webhook Handlerโ€‹

For reliable status updates, use webhooks:

app.post('/webhooks/omise', (req, res) => {
const event = req.body;

if (event.key === 'charge.complete') {
const charge = event.data;

if (charge.status === 'successful') {
// Process successful payment
processOrder(charge.metadata.order_id);
} else if (charge.status === 'failed') {
// Handle failed payment
handleFailedPayment(charge.metadata.order_id, charge.failure_message);
}
}

res.sendStatus(200);
});

Complete Implementation Exampleโ€‹

<!DOCTYPE html>
<html>
<head>
<title>3DS Checkout</title>
<script src="https://cdn.omise.co/omise.js"></script>
</head>
<body>
<form id="payment-form">
<input type="text" id="card-name" placeholder="Card Holder" required />
<input type="text" id="card-number" placeholder="Card Number" required />
<input type="text" id="expiry-month" placeholder="MM" required />
<input type="text" id="expiry-year" placeholder="YYYY" required />
<input type="text" id="cvv" placeholder="CVV" required />
<button type="submit">Pay THB 1,000</button>
</form>

<script>
Omise.setPublicKey("pkey_test_YOUR_KEY");

document.getElementById('payment-form').addEventListener('submit', async (e) => {
e.preventDefault();

// Step 1: Create token
Omise.createToken("card", {
name: document.getElementById('card-name').value,
number: document.getElementById('card-number').value,
expiration_month: parseInt(document.getElementById('expiry-month').value),
expiration_year: parseInt(document.getElementById('expiry-year').value),
security_code: document.getElementById('cvv').value
}, async (statusCode, response) => {
if (statusCode !== 200) {
alert('Error: ' + response.message);
return;
}

// Step 2: Create charge on server
const result = await fetch('/create-charge', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token: response.id,
amount: 100000,
order_id: 'ORD-12345'
})
}).then(r => r.json());

// Step 3: Handle response
if (result.requires_3ds) {
// Redirect to 3DS authentication
window.location.href = result.authorize_uri;
} else {
// Frictionless - payment complete
window.location.href = '/payment-success';
}
});
});
</script>
</body>
</html>

Trade-offs & Considerationsโ€‹

Advantagesโ€‹

  • Fraud reduction: 70%+ decrease in unauthorized transactions
  • Liability shift: Banks assume chargeback liability
  • Lower fees: Fewer disputes = lower dispute fees
  • Compliance: Required for certain business types
  • Customer trust: Professional security = increased confidence

Disadvantagesโ€‹

  • Conversion impact: Some customers abandon at redirect (frictionless helps)
  • No recurring payments: Cannot automatically charge saved cards
  • User friction: Challenge flow adds 1-3 minutes to checkout
  • Technical complexity: Additional redirect handling required
  • International variations: Bank implementation quality varies

Mitigation Strategiesโ€‹

Reduce Cart Abandonment:

// Show clear messaging before redirect
function initiate3DS(authorizeUri) {
const modal = showModal({
title: "Secure Payment Verification",
message: "You'll be redirected to your bank for verification. This keeps your payment secure.",
button: "Continue to Verification"
});

modal.onConfirm = () => {
window.location.href = authorizeUri;
};
}

Handle Recurring Payments:

// First charge: Use 3DS
const firstCharge = await omise.charges.create({
amount: 29900,
currency: 'THB',
card: tokenId,
return_uri: 'https://example.com/callback',
metadata: { type: 'subscription_signup' }
});

// Save customer for future charges
const customer = await omise.customers.create({
email: 'customer@example.com',
card: tokenId
});

// Subsequent charges: No 3DS (use customer ID)
const recurringCharge = await omise.charges.create({
amount: 29900,
currency: 'THB',
customer: customer.id,
// No return_uri = no 3DS required
});

Testing 3D Secureโ€‹

Enable Test Mode 3DSโ€‹

Contact support to enable 3DS in test mode:

Email: support@omise.co
Subject: Enable 3DS for Test Mode

Test Cardsโ€‹

Use these cards for testing different 3DS scenarios:

note

3DS testing requires a 3DS-enabled test account. Contact support@omise.co to enable.

Standard Success Cards (no 3DS issues):

Card NumberBrandExpected Result
4242 4242 4242 4242VisaFrictionless approval
5555 5555 5555 4444MastercardFrictionless approval

3DS Enrollment Failure Cards:

Card NumberBrandExpected Result
4111 1111 1115 0002Visa3DS enrollment fails
5555 5511 1112 0002Mastercard3DS enrollment fails
3530 1111 1110 0002JCB3DS enrollment fails

3DS Authentication Failure Cards:

Card NumberBrandExpected Result
4111 1111 1114 0003Visa3DS authentication fails
5555 5511 1111 0003Mastercard3DS authentication fails
3771 3816 1111 003Amex3DS authentication fails

Test Authentication Flowโ€‹

  1. Create charge with test card and return_uri
  2. If authorize_uri present, visit the URL
  3. Complete mock authentication on test page
  4. Verify redirect back to return_uri
  5. Check final charge status
# Test charge with 3DS
curl https://api.omise.co/charges \
-u skey_test_YOUR_KEY: \
-d "amount=100000" \
-d "currency=THB" \
-d "card=tokn_test_..." \
-d "return_uri=https://example.com/callback"

Common Issues & Troubleshootingโ€‹

Issue: "3d secure is requested, but return URI is not set"โ€‹

Cause: 3DS enabled on account but return_uri not provided

Solution:

// Always include return_uri when 3DS is enabled
const charge = await omise.charges.create({
amount: 100000,
currency: 'THB',
card: tokenId,
return_uri: 'https://yourdomain.com/callback' // Required!
});

Issue: Customer abandoned at 3DS pageโ€‹

Causes:

  • Confusing bank authentication page
  • Long authentication time
  • Customer doesn't know bank password

Solutions:

  • Show clear messaging before redirect
  • Set customer expectations ("You'll verify with your bank")
  • Provide support contact info
  • Implement timeout handling

Issue: return_uri not being calledโ€‹

Causes:

  • Customer closed browser
  • Bank redirect failed
  • Incorrect return_uri URL

Solutions:

  • Use webhooks as backup (charge.complete event)
  • Allow customers to check order status
  • Implement order status page with charge ID lookup

Issue: Frictionless not workingโ€‹

Causes:

  • Insufficient transaction data sent to bank
  • Customer new to merchant
  • High-risk transaction characteristics

Solutions:

  • Provide complete billing address
  • Include customer email and phone
  • Build transaction history over time

Best Practicesโ€‹

  1. Provide Clear Communication

    <div class="3ds-notice">
    <p>For your security, you'll be asked to verify this payment with your bank.</p>
    <p>This typically takes less than 1 minute.</p>
    </div>
  2. Implement Proper Error Handling

    if (charge.failure_code === '3d_secure_authentication_failed') {
    showMessage('Bank authentication failed. Please try again or use a different card.');
    }
  3. Use Webhooks for Reliability

    • Don't rely solely on redirect callbacks
    • Implement charge.complete webhook handler
    • Update order status from webhook events
  4. Optimize for Frictionless

    • Collect complete customer data
    • Provide billing address
    • Include phone and email
    • Build positive payment history
  5. Handle Timeouts Gracefully

    // Set reasonable timeout
    setTimeout(() => {
    if (!paymentConfirmed) {
    showMessage('Payment verification is taking longer than expected. Check your order status.');
    }
    }, 5 * 60 * 1000); // 5 minutes
  6. Test Thoroughly

    • Test both frictionless and challenge flows
    • Test failure scenarios
    • Test on mobile devices
    • Test with actual test banking apps

FAQโ€‹

Is 3D Secure mandatory?

3D Secure is mandatory for certain business types (travel, digital goods, gaming) as determined by Omise's fraud analysts. For other merchants, it's optional but recommended for high-value or high-risk transactions.

Does 3D Secure guarantee no chargebacks?

No, but it significantly reduces them. With successful 3DS authentication, liability shifts to the issuing bank, meaning chargebacks are less likely to be approved. However, banks may still dispute 3DS transactions in rare cases.

Can I use 3D Secure for subscriptions?

Not directly for automatic recurring payments. However, you can:

  1. Use 3DS for initial card setup
  2. Save card as customer
  3. Charge customer ID without 3DS for recurring payments (subject to bank rules)
What's the difference between frictionless and challenge flow?

Frictionless: Bank approves instantly based on risk analysis - no customer interaction needed. Better UX.

Challenge: Bank requires additional verification (OTP, biometric) - customer must interact. More secure but slower.

Banks decide which flow to use based on transaction risk.

How long does 3DS authentication take?
  • Frictionless: Instant (< 1 second)
  • Challenge: 1-3 minutes typically
  • First-time enrollment: Up to 10 minutes

Most modern implementations use frictionless flow for low-risk transactions.

Does 3D Secure work on mobile?

Yes! 3DS2 is fully optimized for mobile with:

  • In-app authentication (no browser redirect)
  • Biometric options (fingerprint, Face ID)
  • Banking app deep links
  • Better mobile UX than 3DS1

Next Stepsโ€‹

  1. Contact support to enable 3DS
  2. Implement return_uri handling
  3. Set up webhook notifications
  4. Test with test cards
  5. Monitor fraud reduction
  6. Go live