3D Secure
3D Secure (3DS) is the card-network protocol that lets card issuers verify the cardholder is actually present and authorizing a charge. The customer interacts with their issuing bank — typing a one-time code, approving a push notification, completing a biometric prompt — and the issuer returns a cryptographic proof of authentication to Frame. The charge then proceeds with the issuer on the hook for fraud liability, not the merchant.
You'll see 3DS referred to under network-specific brand names: Visa Secure, Mastercard Identity Check, American Express SafeKey. They're all 3DS implementations.
Why it matters
Three concrete effects when a charge runs through 3DS:
- Fraud liability shifts to the issuer. If a 3DS-authenticated charge later gets disputed for fraud, the merchant is generally not on the hook — the issuing bank authenticated the cardholder, so it owns the loss. This is the biggest single thing 3DS does for merchant economics.
- Higher success rates on flagged transactions. Issuers that would otherwise soft-decline a transaction often let it through after 3DS. The
authentication_requireddecline code is the issuer asking for 3DS specifically. - Regulatory compliance. Some jurisdictions (EU/UK PSD2, parts of LATAM, India) require Strong Customer Authentication for many transactions. 3DS is how Frame meets those requirements.
The trade-off: 3DS adds a step to checkout, which costs conversion. Frame triggers it selectively — only when the issuer or risk engine demands it, not on every charge.
When Frame triggers 3DS
Frame's engine decides per-transaction, before the charge attempt — 3DS isn't a retry mechanism that fires after a soft decline; it's a pre-charge gate that runs when the rules require authentication. The triggers include:
- Risk-engine-flagged — Frame's internal risk signals (Sonar) escalate a transaction to require authentication before the charge proceeds.
- Regulatory requirement — the cardholder's jurisdiction mandates SCA for this transaction category.
- Card issuer always requires — some cards are configured for 3DS on every charge.
- Merchant policy — your platform's risk configuration may require 3DS on certain transaction shapes.
The authentication_required decline code is what the issuer returns when a non-3DS attempt is rejected pending authentication. In Frame's flow, this scenario typically doesn't arise because Frame initiates 3DS upfront when authentication will be needed — the engine routes the charge through 3DS rather than letting it fail with an auth-required decline. If you do see authentication_required on a failed charge, that's the issuer telling you the charge needed 3DS that wasn't run; the resolution is to retry through frame-js's confirmCardPayment (which triggers Frame's 3DS flow), not a manual re-charge.
You don't request 3DS explicitly on a charge in V1; Frame decides whether to run it. Off-session payments (subscription renewals, saved-card charges where the customer isn't actively present) typically don't trigger 3DS at charge time because the cardholder isn't there to authenticate — for these, the protection comes from cryptograms stored at the initial 3DS-enabled link-time (see card verification).
The flow
When 3DS triggers on a charge:
- Charge is initiated — typically through
confirmCardPaymentin frame-js or a direct API call. - Frame evaluates — does this transaction support and require 3DS?
- If 3DS is required — Frame contacts the issuer's 3D Secure Access Control Server (ACS) and starts the authentication flow. The charge's underlying state moves to
requires_3d_secure. - Frame displays the authentication UI — when you use
confirmCardPaymentin frame-js, the SDK opens a modal (or redirects, depending on the issuer) where the customer completes the authentication step with their bank. - Issuer returns the result — authenticated, failed, or attempted-but-acknowledged.
- Frame proceeds with the charge — if authentication succeeded (or hit an acknowledged-attempt edge case), the charge attempts settlement. If authentication failed, the charge transitions to
failedwith an authentication-related failure code.
For the merchant, frame-js handles the UI surface automatically. If you're not using frame-js, you'd need to display the authentication challenge yourself — Frame returns the ACS URL and necessary parameters, and you'd render the authentication UI in your own iframe or popup.
The cryptogram
When 3DS completes successfully, the issuer returns a 3DS cryptogram — a cryptographic value proving the cardholder authenticated at this specific transaction. Frame stores the cryptogram against the payment method.
The cryptogram has two downstream uses:
- Dispute defense. When a future transaction on this card is disputed, Frame can submit the cryptogram as evidence. Disputed transactions backed by a 3DS cryptogram have materially higher win rates on fraud-coded chargebacks — because the cryptogram shifts liability to the issuer.
- Re-use within a session. Some flows let a 3DS-authenticated transaction's cryptogram cover subsequent transactions on the same card within a window, reducing friction for repeat charges.
This is the main argument for running card verification on new cards: card verification's onboarding flow includes a 3DS challenge that produces a cryptogram, which then defends every future charge against that card. One-time friction at link time, persistent benefit for the lifetime of the card.
What it doesn't do
3DS is authentication, not authorization. It tells the issuer "this is the real cardholder," but it doesn't:
- Guarantee the charge will succeed — the issuer can still decline after authentication (insufficient funds, etc.).
- Cover non-fraud disputes —
product_not_received,subscription_canceled, and similar reason categories aren't defended by 3DS cryptograms. - Apply to ACH or other non-card rails — 3DS is a card-network protocol.
- Work on off-session charges — the cardholder isn't there to authenticate.
Testing 3DS
Frame's sandbox accepts test cards that simulate 3DS flows:
| Test card | Behavior |
|---|---|
4000000000003220 | 3DS required; authentication must succeed for the charge to complete |
4111110116638870 | 3DS required; frictionless flow (no customer prompt) |
4111111738973695 | 3DS frictionless then declines with card_not_enrolled |
4000008400001629 | 3DS required then declines with generic_decline |
Use these against your sandbox key to verify your application handles each 3DS outcome before going live. See Testing for the full test-card set.
Gotchas
Symptom: you implemented 3DS but conversion dropped sharply. Why: you're triggering 3DS on every charge instead of letting Frame's engine decide. Forced 3DS adds friction to transactions that the issuer would have approved without it. Fix: don't request 3DS explicitly. Frame's engine knows when it's needed; let it run.
Symptom: a 3DS challenge fails for a customer you know is legitimate. Why: 3DS UX is issuer-controlled and varies wildly — some banks use SMS codes that get filtered as spam, some require app-based authentication the customer doesn't have installed, some have outage windows. The customer experience varies by issuer. Fix: surface a clear "try a different card" path on 3DS failure; don't keep retrying the same card with the same authentication failing.
Symptom: you ran card verification (which includes 3DS) and now the cardholder is disputing a later charge that didn't run 3DS at charge-time. Why: the cryptogram from card verification is stored, but its dispute-defense effectiveness depends on the issuer + reason. Some issuers honor the verification-time cryptogram for fraud disputes on later charges; some don't. Fix: still worth running card verification — it raises win rates on average — but don't assume 100% defense. Track your win rate by failure_code category over time to know what's actually working.
Reference
For the charge-side surface, see POST/v1/transfers. For the integration walk-through with frame-js, see Handle 3D Secure.