3-D Secure

Autenticação 3-D Secure, uma chamada e a charge final.

Uma charge de cartão pode nascer requires_action pedindo autenticação 3-D Secure. O SDK hospedado roda o desafio e conclui em POST /charges/{id}/confirm-3ds, resolvendo a charge canônica final.

Quando o 3DS acontece

Quando uma regra de 3DS exige autenticação E a adquirente resolvida suporta 3DS, a charge nasce requires_action e inclui next_action em vez de autorizar imediatamente. Se não houver 3DS (nenhuma regra, ou adquirente sem suporte), next_action fica ausente e o fluxo segue normal.

O next_action da charge

next_action só aparece enquanto a charge está requires_action. sdk_payload é opaco, específico da adquirente e ligado ao browser — repasse ao SDK sem inspecionar.

next_action
{  "id": "ch_xxx",  "object": "charge",  "status": "requires_action",  "amount_cents": 10990,  "currency": "BRL",  "payment_method": "credit_card",  "capture_method": "automatic",  "captured": false,  "installments": 1,  "next_action": {    "type": "three_ds_challenge",    "three_ds": {      "authentication_id": "tds_xxx",      "acquirer": "cielo",      "sdk_payload": {}    }  },  "request_id": "req_xxx"}

Inclua o SDK hospedado

Carregue o script hospedado e crie um cliente com create(). Ele instala o global Bunne. Não passe chave no browser: hoje só emitimos chaves secretas (sk_), que nunca podem ir para o navegador. O desafio roda no browser sem chave; o confirm é feito pelo seu backend.

HTML
<script src="https://bunne.com.br/sdk/v1/bunne-3ds.js"></script><script>  // Installs window.Bunne. Bind non-secret config once.  // No API key in the browser: we only issue secret keys (sk_), which must  // never be exposed. The browser only runs the challenge; confirm-3ds is a  // backend call. Point baseUrl at your API (or your proxy).  const client = Bunne.create({ baseUrl: "https://bunne.com.br" });</script>

O jeito de uma chamada

threeDSecure(charge) faz tudo: se a charge não exige 3DS, resolve a charge inalterada (frictionless, sem chamada de rede); caso contrário roda o desafio e faz POST em confirm-3ds, resolvendo a charge final. Como o confirm exige a chave sk_, use este atalho só onde a chave fica segura (backend ou renderização no servidor). Hoje, sem chave publishable, prefira o fluxo em duas etapas abaixo.

JavaScript
// charge is the JSON returned by POST /charges (created from your backend).// Its status may be "requires_action" with a next_action, or already final.const finalCharge = await client.threeDSecure(charge);// finalCharge.status is now final: "paid" / "authorized" / "refused".// No 3DS? threeDSecure resolves the same charge unchanged, with no network call.

Recomendado: desafio no browser, confirm no backend

No browser, rode initThreeDS(next_action): ele roda o desafio SEM chave e resolve { authentication_id, acquirer, result }. Envie esse objeto ao SEU backend, que faz o POST em /charges/{id}/confirm-3ds com a chave sk_ (veja abaixo). Assim nenhuma chave passa pelo navegador.

JavaScript
// 1. Run the challenge only (acquirer-agnostic). No network call yet.//    Resolves { authentication_id, acquirer, result }.const resolved = await client.initThreeDS(charge.next_action);// 2. Send `resolved` ({ authentication_id, result }) to YOUR backend, which//    confirms with the sk_ key (see the backend example below). If you ever//    issue a browser-safe key, client.confirm3DS(charge.id, resolved) does//    this call for you.

Só backend (rode o desafio você mesmo)

Times que rodam o desafio 3DS por conta própria confirmam direto: POST /charges/{id}/confirm-3ds com { authentication_id, result }. Retorna a charge canônica. É idempotente — um confirm duplicado é replay e nunca autoriza duas vezes.

cURL
curl -X POST https://bunne.com.br/api/v1/charges/ch_xxx/confirm-3ds \  -H "Authorization: Bearer sk_test_xxx" \  -H "Idempotency-Key: order_123_confirm_3ds" \  -H "Content-Type: application/json" \  -d '{    "authentication_id": "tds_xxx",    "result": { "authenticated": true }  }'

Segurança: nunca exponha sk_ no browser

Nunca coloque uma chave secreta sk_... no browser (o SDK avisa se detectar). Hoje emitimos apenas chaves secretas, então o browser só roda o desafio e o /charges/{id}/confirm-3dsé chamado pelo seu backend com sk_.

Referência do endpoint

POST/charges/{id}/confirm-3ds

Confirmar 3-D Secure

Conclui uma charge `requires_action` depois que o cliente roda o desafio 3-D Secure. Envie o `authentication_id` presente no `next_action` da charge e o `result` opaco produzido pelo desafio. Autoriza somente a adquirente ligada no prepare (sem fallback entre adquirentes) e retorna a charge canônica. Um confirm duplicado é replay idempotente e nunca autoriza duas vezes.

Auth: Bearer API keycharges:createIdempotency-Key suportada

Headers

CampoTipoDescrição
Idempotency-KeystringRecomendado para retentativas de confirmação.

Parâmetros e body

CampoTipoDescrição
idobrigatóriopath stringId da charge com prefixo `ch_`, atualmente `requires_action`.
authentication_idobrigatóriostringO id `tds_` de `next_action.three_ds.authentication_id` da charge.
resultobrigatórioobjectResultado opaco do desafio produzido pelo SDK de 3-D Secure. Repassado literalmente; nunca deve conter dado bruto de cartão.

Campos da resposta

CampoTipoDescrição
idstringId público com prefixo `ch_`.
statusstring`paid`/`authorized` em caso de sucesso, `refused` quando a autenticação falha.
capturedbooleanTrue depois do pagamento/captura.
paid_atdatetimeData/hora UTC em que a charge foi paga.
request_idstringIdentificador de rastreio da requisição.
Exemplos de requisição
curl -X POST https://bunne.com.br/api/v1/charges/ch_xxx/confirm-3ds \  -H "Authorization: Bearer sk_test_xxx" \  -H "Idempotency-Key: order_123_confirm_3ds" \  -H "Content-Type: application/json" \  -d '{  "authentication_id": "tds_xxx",  "result": {    "authenticated": true  }}'
Resposta
{  "id": "ch_xxx",  "object": "charge",  "status": "paid",  "amount_cents": 10990,  "currency": "BRL",  "payment_method": "credit_card",  "capture_method": "automatic",  "captured": true,  "installments": 1,  "authorization_code": "MOCK-AUTH",  "acquirer_return_code": null,  "acquirer_return_message": null,  "refused_reason": null,  "buyer": {    "name": "Ada Lovelace",    "email": "ada@example.com",    "phone": "5511999990000",    "document": "93541134780",    "document_type": "cpf",    "ip": "203.0.113.10",    "address": {      "line_1": "Av. Paulista, 1000",      "line_2": null,      "zip_code": "01310100",      "city": "Sao Paulo",      "state": "SP",      "country": "BR"    }  },  "metadata": { "order_id": "order_123" },  "paid_at": "2026-06-25T12:10:05.000Z",  "created_at": "2026-06-25T12:09:40.000Z",  "updated_at": "2026-06-25T12:10:05.000Z",  "request_id": "req_xxx"}