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.
{ "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.
<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.
// 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.
// 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 -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
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
/charges/{id}/confirm-3dsConfirmar 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.
Headers
| Campo | Tipo | Descrição |
|---|---|---|
Idempotency-Key | string | Recomendado para retentativas de confirmação. |
Parâmetros e body
| Campo | Tipo | Descrição |
|---|---|---|
idobrigatório | path string | Id da charge com prefixo `ch_`, atualmente `requires_action`. |
authentication_idobrigatório | string | O id `tds_` de `next_action.three_ds.authentication_id` da charge. |
resultobrigatório | object | Resultado opaco do desafio produzido pelo SDK de 3-D Secure. Repassado literalmente; nunca deve conter dado bruto de cartão. |
Campos da resposta
| Campo | Tipo | Descrição |
|---|---|---|
id | string | Id público com prefixo `ch_`. |
status | string | `paid`/`authorized` em caso de sucesso, `refused` quando a autenticação falha. |
captured | boolean | True depois do pagamento/captura. |
paid_at | datetime | Data/hora UTC em que a charge foi paga. |
request_id | string | Identificador de rastreio da 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 }}'{ "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"}