Skip to main content

3D Secure

Quick Reference

WhatCardholder authentication before authorization
WhyLiability shift to issuer and fraud prevention with challenge or frictionless flow
Reading Time15 min
DifficultyIntermediate
PrerequisitesHost-to-Host or SDK → Authentication

How 3DS works with A55

Set threeds_authentication: true on the charge and supply a rich device_info object. A55 handles the 3DS protocol, routes to the issuer, and returns the result.

SDK vs API

The JavaScript SDK runs Device Data Collection (DDC) automatically via A55Pay.authentication(). Server-side integrations (H2H) send device_info fields in the charge JSON.


Frictionless vs challenge

AspectFrictionlessChallenge
DecisionIssuer approves silently based on risk scoreIssuer requires cardholder interaction
UX impactZero (invisible to buyer)Redirect to url_3ds or iframe popup
When it happensLow-risk + rich device dataHigh-risk or issuer policy
ConversionHighestLower (adds friction)
Liability shiftYesYes
Typical rate70–85% of 3DS transactions15–30%

ECI values


Step-by-step integration

1

Collect device_info from the browser

Capture real browser values on the client side. Every field improves the issuer's risk model.

const deviceInfo = {
ip_address: await fetch('/api/ip').then(r => r.text()),
user_agent: navigator.userAgent,
http_accept_content: 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
http_accept_browser_value: '*/*',
http_browser_language: navigator.language,
http_browser_java_enabled: navigator.javaEnabled?.() ?? false,
http_browser_javascript_enabled: true,
http_browser_color_depth: String(screen.colorDepth),
http_browser_screen_height: String(screen.height),
http_browser_screen_width: String(screen.width),
http_browser_time_difference: String(new Date().getTimezoneOffset()),
};
// Send deviceInfo to your backend for the charge request
2

Create a charge with threeds_authentication: true

curl -sS -X POST "https://core-manager.a55.tech/api/v1/bank/wallet/charge/" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"wallet_uuid": "00000000-0000-4000-8000-000000000001",
"type_charge": "credit_card",
"card_name": "JANE DOE",
"card_number": "4111111111111111",
"card_expiry_month": 12,
"card_expiry_year": 2030,
"card_cvv": "123",
"amount": 10000,
"currency": "BRL",
"installment_count": 1,
"threeds_authentication": true,
"device_info": {
"ip_address": "203.0.113.10",
"user_agent": "Mozilla/5.0 ...",
"http_accept_content": "text/html",
"http_accept_browser_value": "*/*",
"http_browser_language": "pt-BR",
"http_browser_java_enabled": false,
"http_browser_javascript_enabled": true,
"http_browser_color_depth": "24",
"http_browser_screen_height": "900",
"http_browser_screen_width": "1440",
"http_browser_time_difference": "180"
},
"webhook_url": "https://merchant.example/webhooks/a55",
"redirect_url": "https://merchant.example/return"
}'
3

Handle the response

{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"eci": "05",
"authentication_status": "success",
"reference_external_id": "ord_001"
}

No redirect needed — the charge was authenticated silently.

4

Handle the 3DS challenge (if pending)

If the response includes url_3ds, redirect the buyer. After the challenge, listen for postMessage:

window.addEventListener('message', function (event) {
if (event.data?.event === '3ds-auth-complete') {
const chargeUuid = event.data.chargeUuid;
// Fetch charge status from your backend, then update UI
}
});
How the challenge works internally
  1. Your page redirects (or iframes) to confirmacion.a55.tech/charge/{uuid}
  2. The 3DS page loads the issuer's ACS (Access Control Server) in a nested iframe
  3. The cardholder completes verification (OTP, biometrics, etc.)
  4. The ACS redirects to confirmacion.a55.tech/validate/{uuid}
  5. A55 validates the authentication and sends postMessage with 3ds-auth-complete back up the window chain
5

Confirm via webhook

Always use the webhook as the source of truth. Configure webhook_url on the charge so you receive async updates even if the buyer closes the browser.

{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "paid",
"transaction_reference": "txn_ref_001"
}

End-to-end flow


device_info fields

FieldTypeRequiredDescription
ip_addressstringYesClient public IP
user_agentstringYesBrowser user agent string
http_accept_contentstringYesHTTP Accept header
http_accept_browser_valuestringYesBrowser Accept value
http_browser_languagestringYesBrowser language (e.g., pt-BR)
http_browser_java_enabledbooleanYesJava plugin enabled
http_browser_javascript_enabledbooleanYesJavaScript enabled
http_browser_color_depthstringYesScreen color depth
http_browser_screen_heightstringYesScreen height in pixels
http_browser_screen_widthstringYesScreen width in pixels
http_browser_time_differencestringYesUTC offset in minutes
device_idstringNoStable device identifier
session_idstringNoCheckout session ID
Data quality

Incomplete or synthetic device_info increases challenge rates and declines. Collect real browser values on the client and pass them to your server. Never hardcode these values.


SDK DDC (A55Pay.authentication)

When using the JavaScript SDK, you can run DDC standalone:

A55Pay.authentication({
transactionReference: 'charge-uuid-or-ref',
cardBrand: 'visa',
cardNumber: '4111111111111111',
cardExpiryMonth: '12',
cardExpiryYear: '2030',
onSuccess: (payload) => {
// payload.sessionId, payload.accessToken, payload.referenceId
// Use sessionId for the charge request
},
onError: (err) => {
// DDC failed — proceed without session or handle error
},
});

Note: payV2() runs DDC automatically before processing. You only need authentication() if you are managing the flow manually.


Validation endpoint

POST /api/v1/bank/public/charge/authentication/{uuid}/validate

Use the charge uuid from the 3DS flow. A55 validates the authentication metadata and returns the final result.


Test with sandbox cards

Card NumberBrandScenarioExpected Status
4111 1111 1111 1111Visa3DS frictionless — confirmedconfirmed
4000 0000 0000 0101Visa3DS challenge requiredconfirmed
4000 0000 0000 0002Visa3DS — declined by issuerdeclined
5500 0000 0000 0004Mastercard3DS frictionless — confirmedconfirmed
4000 0000 0000 0069Visa3DS — processing errorerror

Want higher approval rates without challenges?

Visa Data Only shares enriched transaction data with issuers through 3DS rails — without a single challenge redirect. Merchants in Square's pilot saw up to +646 basis points improvement over 9 months across 6 million transactions.

No friction. No cart abandonment. A55 supports this today with one flag: data_only: true.

The trade-off: no liability shift (merchant bears fraud). For high-volume, low-risk transactions in LATAM, this trade-off often pays for itself many times over.

Learn how to activate Data Only →