Skip to main content
Use this guide to run card payments that may require 3D Secure, using either the session-first or transaction-first architecture. Both flows are officially supported. Choose the one that matches how your backend orchestrates transaction state and authentication.
In both flows, mounting the 3D Secure Element is what runs the authentication. Just creating a session is not enough for the cardholder or the bank to complete the authorization — mount the element for every session with auth_status: ACTION_REQUIRED and let onSuccess/onFailure drive the next step.

Choose a Flow

Both flows are official in Rinne. There is no platform-level preferred flow.

Backend Endpoint Mapping

Shared Frontend Setup

Before processing card transactions, your merchant or organization must have an active Rinne card affiliation.
Use encrypted values from mountedCard.values only. Do not collect raw PAN/CVC in custom inputs.
checkout.html
checkout.ts

Session-First Flow

1

Create 3DS session on backend

Send the encrypted card number and plain expiry (month/year) from mountedCard.values to your backend. The Card Element encrypts the card number client-side; expiry values are not encrypted.
2

Run authentication in the 3DS element

Create threeDSecure once and call mount(tdsSessionId). Mounting starts the authentication — the element resolves frictionless flows silently and renders the issuer’s challenge when one is required.
3

Create transaction after authentication

On onSuccess, call your backend and include the authenticated session ID.

Backend session request example

The /api/... routes are your backend: they forward these payloads to the Rinne endpoints in Backend Endpoint Mapping with your API key. See 3D Secure authentication for the backend contract.

Transaction-First Flow

In this flow, your backend transaction status determines whether frontend should run 3DS.
1

Create transaction and branch by status

Backend creates the transaction and returns the initial status.Use require_3ds: true when you want deterministic AWAITING_3DS entry in transaction-first.
2

Start 3DS only for AWAITING_3DS

If transaction is AWAITING_3DS, create a 3DS session with the same amount/currency and encrypted card number/expiry.
3

Mount challenge when required and authenticate

Branch on auth_status: mount only for ACTION_REQUIRED, skip for AUTHENTICATED, abort for FAILED. After challenge success (or if already AUTHENTICATED), backend authenticates the pending transaction with session id using:
  • Self: POST /v1/transactions/{transactionId}/authenticate
  • Merchant: POST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate
4

Handle transaction result

Card transactions typically resolve synchronously — the /authenticate response already contains the final status (APPROVED, AUTHORIZED, or REFUSED). Use this status to drive your UI immediately.

Challenge strategy flags (backend)

  • require_3ds: true: create directly in AWAITING_3DS, so you can enforce 3DS while staying on one transaction-first backend flow.
  • refuse_on_challenge: true: fail fast with REFUSED + status_reason=CHALLENGE_NOT_ALLOWED when a challenge-triggered path would occur.
  • Use refuse_on_challenge when you intentionally do not support challenge UX for a merchant, channel, or transaction segment.
  • The two flags cannot both be true.

Transaction-first status branching

If a transaction is initially PROCESSING and later becomes AWAITING_3DS (for example, soft-decline recovery), run the same transaction-first 3DS steps at that point.

Transaction-first orchestration example

Authenticate requests must use session id as three_d_secure_session_id. Do not send tds_session_id to your backend authenticate route.
Wire runTransactionFirst to your submit handler the same way as runSessionFirst in the session-first flow.

Session ID Mapping

  • tds_session_id: use in threeDSecure.mount(tds_session_id) on frontend.
  • id: use as three_d_secure_session_id in backend transaction create/authenticate calls.

Handle 3DS Statuses

In practice, sessions come back as ACTION_REQUIRED or FAILED at creation — even frictionless authentication completes through the SDK element. The API contract allows AUTHENTICATED as a creation-time response for forward compatibility, so always branch on all three statuses.

Frictionless vs challenge

When the element is mounted, the issuer decides whether the transaction can be authenticated silently (frictionless) or needs cardholder interaction (challenge):
  • Frictionless: authentication completes in the background with no visible UI. onSuccess fires automatically, often within seconds.
  • Challenge: an issuer-hosted verification step appears inside the element (OTP, app approval, biometric). Even when that screen tells the cardholder to approve the purchase in their banking app, it is rendered by the element, which also detects the approval — do not replace it with a waiting screen of your own. onSuccess fires after the user completes it.
Your frontend code handles both identically — onSuccess is the signal to proceed in either case. You can inspect authentication_flow on the backend after the session is authenticated to see which path was taken.

Retry Strategy

Sessions are single-use: after a failure, an expired session, or a rejected /authenticate call, the attempt must restart with a new session. In this guide’s architecture that happens naturally — the submit handler re-enables the button, and runSessionFirst/runTransactionFirst create a fresh session on every attempt.
The SDK automatically cleans up the previous element instance when a new one mounts.