How authentication runs
Every 3DS integration is the same three moves, split between your backend and your frontend:- Backend — create a 3DS session with the card data. The response carries two IDs:
tds_session_id(for the frontend) andid(for the backend). - Frontend — mount the 3D Secure Element with
tds_session_id. Mounting is what runs the authentication. The element completes frictionless authentications silently and renders the issuer’s verification UI (OTP, approve-in-app instructions, biometric) when a challenge is required.onSuccessfires when authentication completes — in both paths. - Backend — link the session to a transaction using the session
idasthree_d_secure_session_id.
Rinne officially supports two 3DS integration patterns: session-first and transaction-first. They differ only in when the transaction is created — the three moves above are identical in both. There is no platform-level preferred flow. Choose the flow that matches your organization architecture.
Endpoint context (self vs merchant)
Use endpoint family based on the API key context you are operating in.API keys created with full access already carry the view permissions. A restricted API key or a custom role that grants
3ds.create but not 3ds.view (or merchant.3ds.create without merchant.3ds.view) gets 403 when it reads a session. To read sessions, grant the view permission or the 3ds.* / merchant.3ds.* wildcard.Security contract with rinne-js
Use rinne-js secure components as your source of card values:- Card Element for card number (encrypted), CVC (encrypted), and expiry (plain month/year).
- 3D Secure Element for running the authentication itself — frictionless and challenge.
Critical ID mapping
How transactions enter AWAITING_3DS
A card transaction can enter AWAITING_3DS through any of the following:
- Risk/anti-fraud challenge requirement.
- Soft-decline recovery path.
require_3ds: truein transaction create request.
refuse_on_challenge: true modifies challenge-trigger behavior:
- Challenge-triggered paths are refused immediately (
status = REFUSED). status_reasonis set toCHALLENGE_NOT_ALLOWED.- This avoids waiting in
AWAITING_3DSwhen your organization does not run challenge UX.
These flags are transaction-creation policies only. They do not create a 3DS session or run challenge automatically.
Choosing the right flag strategy
require_3ds is useful when your transaction-first integration already handles AWAITING_3DS and /authenticate, but you need deterministic 3DS enforcement.
refuse_on_challenge is useful when you prefer immediate refusal over any pending challenge workload.
With
refuse_on_challenge, non-challenge transactions continue normal provider processing. Only challenge-triggered paths are refused.Flow A: Session-first
Use this flow when authentication should happen before transaction creation.1
Create a 3DS session
Required fields are
amount, currency, and card. The merchant.website field is the support URL shown to cardholders during the challenge screen. If omitted, Rinne falls back to the website configured on your company or parent organization. The request fails if no website URL can be resolved from any source.Providing payer context (payer_email, payer_name, payer_document, billing_address) is optional but improves authentication rates. Issuers use this context to assess risk and reduce unnecessary challenges.- Self endpoint
- Merchant endpoint
cURL
In practice, sessions come back as
ACTION_REQUIRED or FAILED at creation — even for frictionless flows, authentication completes through the SDK element, not at session creation time. The API contract does allow AUTHENTICATED as a creation-time response for forward compatibility, so always branch on all three statuses rather than assuming only two.When auth_status is ACTION_REQUIRED, authentication_flow, liability_shift and the protocol identifiers (acs_transaction_id, ds_transaction_id, server_transaction_id, protocol_version) are null. They are populated only after the 3DS element completes the authentication flow.A session that already has its outcome at creation sends no three-d-secure-session.status-changed webhook: the create response carries the outcome.Sessions expire 1 hour after creation. If the user takes too long to complete a challenge, you will need to create a new session and restart the authentication step. Once a session is
AUTHENTICATED, its expires_at moves to 1 hour after authentication, which is how long you have to link it to a transaction.2
Branch by auth_status and mount when required
Always branch on
auth_status before deciding whether to mount:FAILED: the card was rejected before the flow could start. Do not mount — surface the error and let the user retry with a different card.AUTHENTICATED: frictionless authentication completed at session creation time. This is rare but allowed by the API contract. Skip mounting and proceed directly to transaction creation.ACTION_REQUIRED: authentication has not run yet. Mount the element withtds_session_id; the SDK runs the full 3DS flow internally.
- Frictionless: the issuer authenticates the card silently in the background with no visible UI.
onSuccessfires automatically. - Challenge: the issuer presents a verification step (OTP, app approval, biometric) inside the element. Even when that screen tells the cardholder to approve the purchase in their banking app, the screen is issuer-hosted, rendered by the element — which also detects the approval.
onSuccessfires after the user completes it.
onSuccess is the signal to proceed in either case.3
Create transaction with three_d_secure_session_id
Use session
id as three_d_secure_session_id.- Self endpoint
- Merchant endpoint
cURL
4
Handle transaction result
Card transactions typically resolve synchronously — the response already contains the final status (
APPROVED, AUTHORIZED, or REFUSED). Use this status to drive your UI immediately.A successful 3DS authentication does not guarantee the transaction will be approved. The issuer can still decline the transaction for reasons unrelated to authentication (insufficient funds, card blocked, etc.).
Flow B: Transaction-first
Use this flow when transaction orchestration starts before 3DS authentication and your backend owns the payment lifecycle from the first call.In transaction-first, transaction status is the source of truth for when 3DS is required. Call
/authenticate only for transactions currently in AWAITING_3DS.Transaction-first status contract
Without
require_3ds: true, some transactions can enter AWAITING_3DS later through two paths:- Risk assessment: Rinne’s anti-fraud rules determine the transaction needs cardholder verification.
- Soft-decline recovery: the payment provider initially declines the transaction with a response code indicating 3DS authentication could resolve the decline (e.g., issuer requires authentication). Rinne automatically retries via the 3DS path instead of refusing immediately.
PROCESSING → AWAITING_3DS. Handle this either synchronously or asynchronously through webhooks or transaction polling, then run the same 3DS session/challenge/authenticate steps described below.1
Create card transaction
For deterministic transaction-first 3DS entry, set
require_3ds: true.- Self endpoint
- Merchant endpoint
cURL
Example response
/authenticate only accepts three_d_secure_session_id. CVV is not sent again in this step.2
Branch by transaction status
- If status is
AWAITING_3DS, continue to 3DS session creation. - If status is
PROCESSING,AUTHORIZED,APPROVED, orREFUSED, do not call/authenticate. - If your asynchronous update later moves transaction to
AWAITING_3DS, start 3DS at that point.
3
Create 3DS session and run challenge when required
Create a 3DS session for the same payment attempt:
- Same company/merchant context as the transaction.
- Same
amountandcurrencyas the transaction. - Encrypted card values from rinne-js.
- Optionally include
payer_email,payer_name,payer_document, andbilling_addressto improve authentication rates.
tds_session_id for frontend challenge mount and keep id for backend /authenticate.Branch on auth_status:FAILED: do not mount — surface the error and let the user retry with a different card.AUTHENTICATED: skip mounting and proceed directly to the/authenticatecall.ACTION_REQUIRED: mount the element. The SDK handles frictionless and challenge flows internally;onSuccessfires in both cases. Call/authenticatefromonSuccessusing the sessionid.
4
Authenticate pending transaction with session id
- Self endpoint
- Merchant endpoint
cURL
Example response
5
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.If the response still shows AWAITING_3DS, the session failed validation and was not consumed. Check the error response for details and create a new session to retry.Transaction-first authenticate failures and recovery
Read a session from your backend
The 3D Secure Element reports the outcome to the browser throughonSuccess, onFailure and onError. To get the same outcome on your backend, subscribe to the three-d-secure-session.status-changed webhook or read the session by its id. Both carry the session’s auth_status along with the rest of the session fields: the webhook’s payload.entity is the session as the read returns it.
Use them when your backend needs the outcome itself instead of relying on what the browser reports. They are optional in the payment flows above: transaction create and /authenticate check the session themselves before they link it (see Session validation before consumption).
Rinne writes one webhook event per session, when it reaches AUTHENTICATED or FAILED. Its delivery can repeat, so deduplicate by the event id (see Webhooks best practices). A late delivery can arrive with the session already past its expires_at, and such a session can no longer pay a transaction (see Session validation before consumption). If you read instead, read once, after the element fires onSuccess, onFailure or onError, and don’t poll in a loop. Reads draw from the 50 req/s pool, not the 1000 req/s pool that session creation and authentication use.
Not every session sends the webhook. A session that already has its outcome when you create it, such as a card not enrolled in 3DS, sends none: the create response carries the outcome. A session the cardholder leaves before the challenge starts may never reach an outcome. If your backend needs an answer by a given time, read the session once at a cutoff of your own, such as its expires_at. ACTION_REQUIRED at that read means the cardholder has not completed the challenge; if an outcome arrives later, the webhook reports it.
- Self endpoint
- Merchant endpoint
cURL
200 OK (the Flow A session after the cardholder completed a challenge)
How the status is resolved
- Rinne records a session’s outcome once, from the 3DS provider’s report or from a read, whichever comes first, and sends the webhook at that moment.
- While a session is
ACTION_REQUIRED, Rinne fetches the latest authentication result from the 3DS provider before it responds. A challenge the cardholder finished a moment ago shows up in the read. AUTHENTICATEDandFAILEDare final. Rinne returns them as stored.- A read never links the session to a transaction.
consumption_statusstays as it was, and the session can still pay a transaction if it passes validation.
Act on the result
Errors
Session validation before consumption
Rinne validates all checks below before linking a 3DS session to a transaction:- Same company/merchant scope.
- Same amount.
- Same currency.
auth_status = AUTHENTICATED.- Session has cryptogram.
- Session is not expired (
expires_at, 1 hour after authentication). consumption_status = NOT_CONSUMED.- On transaction create, when the session was created with
card.card_idand the transaction pays withcard_data.card_id, both reference the same stored card.
400 validation error identifying which condition was not met.
Session create request fields
integer
required
Amount in cents. Must exactly match the transaction amount you will use this session for.
string
required
ISO 4217 currency code (e.g.
BRL). Must exactly match the transaction currency.object
required
The card to authenticate: either a stored card (
card_id) or the encrypted number and expiry from the rinne-js Card Element. Send one or the other, never both.string
Support URL shown to the cardholder during the challenge screen. Falls back to the website configured on your company or parent organization. The request fails if no URL can be resolved from any source.
string
Cardholder email. Optional but helps issuers assess risk and reduces unnecessary challenges.
string
Cardholder name as it appears on the card. Not allowed with
card.card_id, which takes the name from the stored card’s cardholder_name.string
Cardholder document number (e.g. CPF for Brazilian cardholders).
object
Cardholder billing address.
Session response fields
string
Internal session UUID. Use this as
three_d_secure_session_id in transaction create or /authenticate requests, and to read the session.string
Provider session ID. Pass this to
rinne.elements.threeDSecure().mount(tds_session_id).string | null
The stored card the session authenticates, when it was created with
card.card_id. null when the card was sent inline. When both this and the transaction’s card_data.card_id are set, they must match, or transaction create rejects the request.string
Authentication status.
ACTION_REQUIRED means authentication has not run yet — mount the 3DS element to run it. AUTHENTICATED and FAILED are outcomes. AUTHENTICATED with authentication_flow: attempt means the cardholder was not authenticated: only proof of the attempt exists.string
Whether the session has been linked to a transaction. Sessions are single-use.
string | null
How authentication completed. Null at session creation — populated after the 3DS element finishes the authentication flow.
boolean | null
Whether liability for fraud chargebacks shifts away from the merchant. Null at session creation — populated after authentication completes.
true: fraud liability shifts away from the merchant. Forfrictionlessandchallengeflows the issuer accepts liability. Forattemptflows the card network stands in and accepts liability when the issuer’s ACS is unavailable.false: the merchant retains fraud liability.
string | null
Human-readable failure reason when
auth_status is FAILED. Null otherwise.integer
Amount in cents the session authenticates, as sent at creation.
string
ISO 4217 currency code the session authenticates, as sent at creation.
string | null
Transaction ID the issuer’s Access Control Server (ACS) assigned to the authentication. Recorded with the outcome:
null while auth_status is ACTION_REQUIRED, and when it was not reported.string | null
Transaction ID the card network’s Directory Server (DS) assigned to the authentication. The network uses it to find the authentication, for example in a chargeback dispute. Recorded with the outcome:
null while auth_status is ACTION_REQUIRED, and when it was not reported.string | null
Transaction ID the 3DS Server assigned to the authentication. Recorded with the outcome:
null while auth_status is ACTION_REQUIRED, and when it was not reported.string | null
EMV 3-D Secure protocol version of the authentication, such as
2.2.0. Recorded with the outcome: null while auth_status is ACTION_REQUIRED, and when it was not reported.string
ISO 8601 timestamp. 1 hour after creation while the session is not yet authenticated. When the session becomes
AUTHENTICATED, Rinne moves it to 1 hour after authentication. After this time, the session cannot be used and a new one must be created.string
ISO 8601 timestamp of when the session was created.
string
ISO 8601 timestamp of the last session update. Equals
created_at initially — updated when Rinne records the authentication outcome.Idempotency and retry
- Transaction create remains idempotent via
request_id. - 3DS session create is non-idempotent by design.
- If session is expired/consumed/mismatched, create a new session and retry.
- If
/authenticateresult is uncertain (timeout/network failure), fetch transaction first. Retry only if transaction is stillAWAITING_3DSand use a new session.
Related docs
- Card transactions
- Wallet transactions
- Testing
- Webhooks —
three-d-secure-session.status-changedand the other events - Guide: Card + 3DS Checkout (rinne-js) — complete frontend example (HTML + code) for both flows

