3D Secure
Quick Reference
Como o 3DS funciona com a A55
Defina threeds_authentication: true na cobrança e envie um objeto device_info completo. A A55 trata o protocolo 3DS, encaminha ao emissor e devolve o resultado.
O SDK JavaScript executa a coleta de dados do dispositivo (DDC) automaticamente via A55Pay.authentication(). Integrações no servidor (H2H) enviam os campos de device_info no JSON da cobrança.
Sem fricção vs desafio
| Aspecto | Sem fricção | Desafio |
|---|---|---|
| Decisão | Emissor aprova em silêncio com base no risco | Emissor exige interação do portador |
| Impacto na UX | Zero (invisível para o comprador) | Redirecionamento para url_3ds ou popup em iframe |
| Quando ocorre | Risco baixo + dados ricos do dispositivo | Risco alto ou política do emissor |
| Conversão | Maior | Menor (adiciona fricção) |
| Transferência de responsabilidade | Sim | Sim |
| Taxa típica | 70–85% das transações 3DS | 15–30% |
Valores de ECI
Integração passo a passo
Coletar device_info no navegador
Capture valores reais do navegador no cliente. Cada campo melhora o modelo de risco do emissor.
- JavaScript
- Python (backend)
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
# Receive device_info from your frontend
device_info = request.json.get("device_info", {})
# Validate required fields
required = [
"user_agent", "ip_address", "http_accept_content",
"http_browser_language", "http_browser_screen_height",
"http_browser_screen_width", "http_browser_time_difference",
"http_accept_browser_value", "http_browser_color_depth",
"http_browser_java_enabled", "http_browser_javascript_enabled",
]
missing = [f for f in required if f not in device_info]
if missing:
raise ValueError(f"Missing device_info fields: {missing}")
Criar cobrança com threeds_authentication: true
- cURL
- Python
- Node.js
- Go
- Java
- PHP
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"
}'
import os, requests
r = requests.post(
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/",
headers={"Authorization": f"Bearer {os.environ['ACCESS_TOKEN']}",
"Content-Type": "application/json"},
json={
"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",
"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",
},
)
print(r.status_code, r.json())
const res = await fetch(
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ACCESS_TOKEN}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
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",
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",
}),
}
);
console.log(await res.json());
payload := map[string]interface{}{
"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",
"threeds_authentication": true,
"device_info": map[string]interface{}{
"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",
}
body, _ := json.Marshal(payload)
req, _ := http.NewRequest("POST",
"https://core-manager.a55.tech/api/v1/bank/wallet/charge/", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer "+os.Getenv("ACCESS_TOKEN"))
req.Header.Set("Content-Type", "application/json")
resp, _ := http.DefaultClient.Do(req)
defer resp.Body.Close()
io.Copy(os.Stdout, resp.Body)
String json = """
{"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","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"}
""";
HttpRequest req = HttpRequest.newBuilder()
.uri(URI.create("https://core-manager.a55.tech/api/v1/bank/wallet/charge/"))
.header("Authorization", "Bearer " + System.getenv("ACCESS_TOKEN"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json)).build();
HttpResponse<String> resp = HttpClient.newHttpClient()
.send(req, HttpResponse.BodyHandlers.ofString());
System.out.println(resp.body());
$payload = [
"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",
"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",
];
$ch = curl_init("https://core-manager.a55.tech/api/v1/bank/wallet/charge/");
curl_setopt_array($ch, [
CURLOPT_POST => true, CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"Authorization: Bearer " . getenv("ACCESS_TOKEN"),
"Content-Type: application/json"],
CURLOPT_POSTFIELDS => json_encode($payload),
]);
echo curl_exec($ch);
Tratar a resposta
- Sem fricção (confirmado)
- Desafio (pendente)
- Falha na autenticação
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "confirmed",
"eci": "05",
"authentication_status": "success",
"reference_external_id": "ord_001"
}
Não é necessário redirecionamento — a cobrança foi autenticada em silêncio.
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "pending",
"url_3ds": "https://confirmacion.a55.tech/charge/a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"reference_external_id": "ord_001"
}
Redirecione o comprador para url_3ds e siga para o passo 4.
{
"uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "error",
"message": [{"message": "3DS authentication failed"}],
"code": "error_threeds_authentication"
}
Tratar o desafio 3DS (se pendente)
Se a resposta incluir url_3ds, redirecione o comprador. Após o desafio, escute 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
}
});
- Sua página redireciona (ou usa iframe) para
confirmacion.a55.tech/charge/{uuid} - A página 3DS carrega o ACS (Access Control Server) do emissor em um iframe aninhado
- O portador conclui a verificação (OTP, biometria etc.)
- O ACS redireciona para
confirmacion.a55.tech/validate/{uuid} - A A55 valida a autenticação e envia
postMessagecom3ds-auth-completede volta pela cadeia de janelas
Confirmar via webhook
Use sempre o webhook como fonte da verdade. Configure webhook_url na cobrança para receber atualizações assíncronas mesmo se o comprador fechar o navegador.
{
"charge_uuid": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"status": "paid",
"transaction_reference": "txn_ref_001"
}
Fluxo ponta a ponta
Campos de device_info
| Campo | Tipo | Obrigatório | Descrição |
|---|---|---|---|
ip_address | string | Sim | IP público do cliente |
user_agent | string | Sim | String do user agent do navegador |
http_accept_content | string | Sim | Cabeçalho HTTP Accept |
http_accept_browser_value | string | Sim | Valor Accept do navegador |
http_browser_language | string | Sim | Idioma do navegador (ex.: pt-BR) |
http_browser_java_enabled | boolean | Sim | Plugin Java habilitado |
http_browser_javascript_enabled | boolean | Sim | JavaScript habilitado |
http_browser_color_depth | string | Sim | Profundidade de cor da tela |
http_browser_screen_height | string | Sim | Altura da tela em pixels |
http_browser_screen_width | string | Sim | Largura da tela em pixels |
http_browser_time_difference | string | Sim | Deslocamento UTC em minutos |
device_id | string | Não | Identificador estável do dispositivo |
session_id | string | Não | ID da sessão de checkout |
device_info incompleto ou sintético aumenta taxas de desafio e recusas. Colete valores reais do navegador no cliente e repasse ao servidor. Nunca fixe esses valores no código.
DDC no SDK (A55Pay.authentication)
Com o SDK JavaScript, você pode executar o DDC de forma independente:
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
},
});
Observação: payV2() executa o DDC automaticamente antes de processar. Você só precisa de authentication() se estiver gerenciando o fluxo manualmente.
Endpoint de validação
POST /api/v1/bank/public/charge/authentication/{uuid}/validate
Use o uuid da cobrança do fluxo 3DS. A A55 valida os metadados da autenticação e retorna o resultado final.
Testar com cartões de sandbox
| Card Number | Brand | Scenario | Expected Status |
|---|---|---|---|
4111 1111 1111 1111 | Visa | 3DS sem fricção — confirmado | confirmed |
4000 0000 0000 0101 | Visa | 3DS com desafio obrigatório | confirmed |
4000 0000 0000 0002 | Visa | 3DS — recusado pelo emissor | declined |
5500 0000 0000 0004 | Mastercard | 3DS sem fricção — confirmado | confirmed |
4000 0000 0000 0069 | Visa | 3DS — erro de processamento | error |
Quer mais aprovações sem desafio?
Visa Data Only compartilha dados enriquecidos da transação com emissores pelas trilhas do 3DS — sem um único redirecionamento de desafio. Comerciantes no piloto da Square viram até +646 pontos-base de melhora em 9 meses em 6 milhões de transações.
Sem fricção. Sem abandono de carrinho. A A55 oferece isso hoje com um único flag: data_only: true.
O trade-off: não há transferência de responsabilidade (a fraude fica com o comerciante). Para alto volume e baixo risco na América Latina, esse trade-off muitas vezes se paga várias vezes.