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://sandbox.api.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

The response always returns the full charge object. The key fields that change by outcome are status, action_url, and message.

{
"charge_uuid": "a34f2499-2a40-40a5-8f5a-578002a88f51",
"local_currency": 159.8,
"currency": "EUR",
"usd_currency": 185.59,
"type": "credit_card",
"date": "2026-04-01",
"description": "#18770 Payments Cabo HDMI 2.1 Ultra 8K",
"due_date": "2025-10-11",
"status": "confirmed",
"message": [],
"installment_count": 1,
"installments": [],
"payment_payload": {},
"applepay_payload": {},
"charge_payment_url": null,
"action_url": "",
"session_id": null,
"subscription": {},
"reference_external_id": "10ccf81a-1de7-4a3e-b86d-f3f685e8ee57",
"is_async": false
}

No redirect needed — the charge was authenticated silently.

4

Handle the challenge redirect (if pending)

If the response includes action_url, redirect the buyer to that URL. After the payer completes the 3DS challenge or fingerprint collection, they are automatically redirected to your redirect_url. Always rely on the webhook for the final transaction status.

if (response.action_url) {
window.location.href = response.action_url;
}
How the challenge flow works
  1. Your page redirects the payer to action_url
  2. The payer completes the required action (3DS OTP, biometrics, fingerprint, etc.)
  3. After completion, the payer is redirected to your redirect_url
  4. A55 sends a webhook to webhook_url with the final transaction status (paid or error)
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 →