Skip to main content
Elements can drive 3D Secure authentication directly in the browser, using the same tokenization session you already create for a card payment. Your server pins the amount to authenticate, the browser runs the challenge (if any), and your server charges using the resulting reference.

How it works

  1. Your server creates a tokenization session and pins the net payable amount.
  2. The shopper enters their card; Elements tokenizes it (elements.submit()).
  3. The browser starts 3DS authentication (threeDS.authenticate()), which may run frictionless or present a challenge.
  4. Your server authorizes or purchases using the card token plus the 3DS reference. The gateway independently verifies the reference before it authorizes the charge.

Before you begin

Requires enablement on your account, a CSP that allows the 3DS iframe and fetch origins — including your API origin in connect-src, or a challenge times out after 5 minutes instead of failing fast (see Content Security Policy) — and Elements SDK v1.6.0 or later.

Steps

1

Create a payment session and pin the amount

Create a session as usual, then pin the amount that 3DS will authenticate — this must be the amount you’ll actually charge, which may be lower than the gross order amount if you redeem loyalty points.
There’s no public PATCH /v1/sessions/{id} reference page yet — see the ThreeDS reference for how this step fits the browser-driven flow. Keep ttl_minutes at 30 or less for a 3DS checkout — see Token lifetime below.
2

Tokenize the card in the browser

3

Authenticate with 3D Secure

A decline resolves, it doesn’t throw. See Authentication results for every status and its meaning, and for 3DS error remedies.
4

Charge with the 3DS reference on your server

Send the token and the 3DS ref from the browser to your server, then purchase or authorize with three_ds:{ref} (API reference).

Handle the result

Any 2xx response is a result you must branch on status — never on response_code (that’s the verbatim host/acquirer code; useful for support tickets, not for your app logic).
Balance inquiry also takes a request_id field, but it isn’t an idempotency key — there’s no dedup or replay store. Every call re-queries the rewards host, even with the same request_id.
Keys are 8–64 characters, [a-zA-Z0-9-] only, unique per merchant account. Generate one key per order attempt and persist it to your database before you send the first request — never mint a new key just to retry the same attempt. See Prevent duplicate payments.
PENDING means the outcome isn’t known yet — most often after a processor timeout. Don’t assume success or failure. Recover it one of two ways:
  1. Wait for a webhook (payment.*, authorization.*, refund.* — see Webhook event types).
  2. Call GET /v1/transactions/{id}/status for a live inquiry against the acquirer.
If you don’t have the transaction id yet — a client-side timeout before the first response arrived — replay the same request with the same request_id and body. The replay returns the stored transaction and its id, whatever status it’s reached. Never re-submit with a new idempotency key just because the first attempt is slow — that risks a second charge for the same order. If the charge itself is rejected for a 3DS reason (for example three-ds:ref-invalid or three-ds:ref-expired), see Authentication results for remedies, or Handle 3D Secure failures in Elements for the recovery-action version of the same failures.

Server policy

Charge only after your server receives a ref from this checkout’s own session flow — never accept a ref from an unrelated session, and never skip straight to charging because the browser reported AUTHENTICATED. The gateway independently checks the ref is single-use, unexpired, authenticated, and bound to the same card token, currency and amount before it authorizes.

Token lifetime

The card token from elements.submit() is transient (about 30 minutes) and the 3DS authentication is bound to it. Keep ttl_minutes at 30 or less for a 3DS checkout. If the token lapses or the shopper switches cards mid-flow:
  • Before charging: authenticate() rejects with three-ds:card-token-invalid — re-bind the card (elements.submit() again) and re-authenticate.
  • At charge time: the purchase/authorize call rejects with 422 urn:radiumone:three-ds:card-token-mismatch if the charged token differs from the one that was authenticated — re-bind and re-authenticate rather than retrying the charge with the same ref.
See Handle 3D Secure failures in Elements and Handle expired sessions and card tokens for the full recovery flow.
Never use a secret key (r1sk_…) in browser code, mobile apps, or anywhere a shopper can inspect it. Secret keys belong on your server only.

Test your integration

See Test your integration for 3DS scenario coverage.

Go-live notes

  • Confirm your account has 3DS enabled before relying on this flow in production.
  • Make sure your CSP allows the 3DS iframe/fetch origins — see Content Security Policy.
  • Review the go-live checklist before launch.

Next steps

Challenge presentation and redirects

Customize where the challenge renders, and handle redirect return pages.

Authentication results

Every status, ECI value and 3DS error remedy.
Last modified on September 15, 2026