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": [],
"pix_payload": {},
"qra_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 →