Skip to main content
3D Secure (3DS) authenticates the cardholder before card authorization and helps reduce fraud in card-not-present payments.
Rinne officially supports two 3DS integration patterns: session-first and transaction-first. 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.
ContextCreate 3DS sessionAuthenticate pending transactionTypical permission
Self (company acting as itself)POST /v1/3ds-sessionsPOST /v1/transactions/{transactionId}/authenticate3ds.create, transaction.create
Organization on behalf of merchantPOST /v1/merchants/{merchantId}/3ds-sessionsPOST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticatemerchant.3ds.create, merchant.transaction.create

Security contract with RinneJS

Use RinneJS secure components as your source of card values:
Do not collect raw PAN/CVC in custom form fields. The Card Element encrypts the card number and CVC client-side before exposing them via mountedCard.values. Expiry month and year are collected but not encrypted — they are sent as plain strings to the 3DS session creation endpoint.

Critical ID mapping

ValueFromUse
id (UUID)3DS session responseSend as three_d_secure_session_id in transaction create/authenticate requests
tds_session_id3DS session responsePass to rinne.elements.threeDSecure().mount(tds_session_id)

How transactions enter AWAITING_3DS

A card transaction can enter AWAITING_3DS through any of the following:
  1. Risk/anti-fraud challenge requirement.
  2. Soft-decline recovery path.
  3. require_3ds: true in transaction create request.
refuse_on_challenge: true modifies challenge-trigger behavior:
  • Challenge-triggered paths are refused immediately (status = REFUSED).
  • status_reason is set to CHALLENGE_NOT_ALLOWED.
  • This avoids waiting in AWAITING_3DS when your organization does not run challenge UX.
require_3ds and refuse_on_challenge cannot both be true in the same transaction request.
These flags are transaction-creation policies only. They do not create a 3DS session or run challenge automatically.

Choosing the right flag strategy

StrategyImmediate transaction outcomeWhat you still implementRecommended when
require_3dsCard transaction is created in AWAITING_3DS every timeCreate session, run challenge when required, and call /authenticateYou want mandatory 3DS while keeping one transaction-first backend path
refuse_on_challengeChallenge-triggered paths return REFUSED with CHALLENGE_NOT_ALLOWEDRefusal handling/retry logic onlyYou do not want to implement challenge UX for a merchant, channel, or transaction segment
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.
cURL
curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/3ds-sessions' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "amount": 12990,
    "currency": "BRL",
    "card": {
      "number": "ev:encrypted:card_number",
      "expiry": { "month": "12", "year": "2028" }
    },
    "merchant": {
      "website": "https://support.yourstore.com"
    },
    "payer_email": "[email protected]",
    "payer_name": "Maria Silva",
    "payer_document": "123.456.789-00",
    "billing_address": {
      "line": "Rua das Flores, 123",
      "city": "São Paulo",
      "state": "SP",
      "postal_code": "01001-000",
      "country": "BR"
    }
  }'
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "tds_session_id": "tds_visa_e7c2yfa3867c",
  "auth_status": "ACTION_REQUIRED",
  "consumption_status": "NOT_CONSUMED",
  "authentication_flow": null,
  "liability_shift": null,
  "failure_reason": null,
  "amount": 12990,
  "currency": "BRL",
  "expires_at": "2026-02-25T20:35:00.000Z",
  "created_at": "2026-02-25T19:35:00.000Z",
  "updated_at": "2026-02-25T19:35:00.000Z"
}
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 and liability_shift are null. They are populated only after the 3DS element completes the authentication flow.
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.
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: mount the element with tds_session_id. The SDK runs the full 3DS flow internally.
When the element is mounted, the issuer decides the authentication path:
  • Frictionless: the issuer authenticates the card silently in the background with no visible UI. onSuccess fires automatically.
  • Challenge: the issuer presents a verification step (OTP, app notification, etc.) inside the element. onSuccess fires after the user completes it.
Your code handles both identically — onSuccess is the signal to proceed in either case.
if (session.auth_status === 'FAILED') {
  showError(session.failure_reason ?? 'Authentication failed')
  return
}

if (session.auth_status === 'AUTHENTICATED') {
  continueCheckout()
  return
}

const threeDS = await rinne.elements.threeDSecure({
  target: '#three-ds-container',
  onSuccess: () => continueCheckout(),
  onFailure: () => showError('Authentication failed'),
  onError: (error) => showError(error.message)
})
await threeDS.mount(session.tds_session_id)
// onSuccess/onFailure/onError callbacks drive the next step
3

Create transaction with three_d_secure_session_id

Use session id as three_d_secure_session_id.
cURL
curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "provider": "RINNE",
    "request_id": "order-3ds-0001",
    "amount": 12990,
    "currency": "BRL",
    "capture_method": "ECOMMERCE",
    "payment_method": "CREDIT_CARD",
    "card_data": {
      "number": "ev:encrypted:card_number",
      "cvv": "ev:encrypted:cvc",
      "expiry_month": "12",
      "expiry_year": "2028"
    },
    "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000"
  }'
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

Transaction statusMeaning in this flowNext action
AWAITING_3DS3DS is required nowCreate 3DS session, complete challenge when needed, then call /authenticate
PROCESSINGProvider processing continues without immediate challengeWait for webhook/status update; do not call /authenticate yet
AUTHORIZED or APPROVEDPayment completed without additional challengeContinue fulfillment/capture flow
REFUSED + CHALLENGE_NOT_ALLOWEDChallenge would be required, but policy blocked itFail fast path, retry with different policy only if desired
REFUSED (other reasons)Regular declineFollow your normal decline handling
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.
In both cases the transition is PROCESSINGAWAITING_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.
cURL
curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "provider": "RINNE",
    "request_id": "order-3ds-0002",
    "amount": 12990,
    "currency": "BRL",
    "capture_method": "ECOMMERCE",
    "payment_method": "CREDIT_CARD",
    "card_data": {
      "number": "ev:encrypted:card_number",
      "cvv": "ev:encrypted:cvc",
      "expiry_month": "12",
      "expiry_year": "2028"
    },
    "require_3ds": true
  }'
{
  "id": "tx_123456789",
  "status": "AWAITING_3DS",
  "amount": 12990,
  "currency": "BRL"
}
/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, or REFUSED, 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 amount and currency as the transaction.
  • Encrypted card values from RinneJS.
  • Optionally include payer_email, payer_name, payer_document, and billing_address to improve authentication rates.
Use 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 /authenticate call.
  • ACTION_REQUIRED: mount the element. The SDK handles frictionless and challenge flows internally; onSuccess fires in both cases. Call /authenticate from onSuccess using the session id.
4

Authenticate pending transaction with session id

cURL
curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/transactions/TRANSACTION_ID/authenticate' \
  -H 'x-api-key: YOUR_API_KEY' \
  -H 'Content-Type: application/json' \
  -d '{
    "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000"
  }'
{
  "id": "tx_123456789",
  "status": "APPROVED",
  "amount": 12990,
  "currency": "BRL"
}
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

Failure conditionTypical API behaviorRecovery action
Transaction not in AWAITING_3DS400 validation errorFetch latest transaction state and branch from actual status; do not keep calling /authenticate blindly
Session not authenticated/failed400 validation errorCreate a new 3DS session and complete challenge again
Session expired/already consumed400 validation errorCreate a new session; never reuse previous session
Session scope/amount/currency mismatch400 validation errorRecreate session in correct context with exact transaction amount and currency
Duplicate or concurrent authenticate attemptsOne attempt succeeds, others failTreat authenticate as single-attempt per session and rely on transaction status as source of truth

Session validation before consumption

Rinne validates all checks below before linking a 3DS session to a transaction:
  1. Same company/merchant scope.
  2. Same amount.
  3. Same currency.
  4. auth_status = AUTHENTICATED.
  5. Session has cryptogram.
  6. Session is not expired (sessions expire 1 hour after creation).
  7. consumption_status = NOT_CONSUMED.
Sessions are single-use and become unavailable after consumption. If any check fails, the request is rejected with a 400 validation error identifying which condition was not met.

Session create request fields

amount
integer
required
Amount in cents. Must exactly match the transaction amount you will use this session for.
currency
string
required
ISO 4217 currency code (e.g. BRL). Must exactly match the transaction currency.
card
object
required
Encrypted card data from RinneJS Card Element.
merchant.website
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.
payer_email
string
Cardholder email. Optional but helps issuers assess risk and reduces unnecessary challenges.
payer_name
string
Cardholder name as it appears on the card.
payer_document
string
Cardholder document number (e.g. CPF for Brazilian cardholders).
billing_address
object
Cardholder billing address.

Session response fields

id
string
Internal session UUID. Use this as three_d_secure_session_id in transaction create or /authenticate requests.
tds_session_id
string
Provider session ID. Pass this to rinne.elements.threeDSecure().mount(tds_session_id).
auth_status
string
Authentication outcome. One of ACTION_REQUIRED, AUTHENTICATED, or FAILED.
consumption_status
string
Whether the session has been linked to a transaction. Sessions are single-use.
ValueMeaning
NOT_CONSUMEDAvailable for use — not yet linked to any transaction
PROCESSINGCurrently being claimed by a transaction request. This is a transient internal state that lasts only during the atomic link operation
CONSUMEDAlready used — cannot be reused. Create a new session if needed
authentication_flow
string | null
How authentication completed. Null at session creation — populated after the 3DS element finishes the authentication flow.
ValueMeaning
frictionlessIssuer authenticated the cardholder silently with no user interaction
challengeIssuer presented a verification step (OTP, app notification, biometric) that the cardholder completed
attemptIssuer’s directory server responded but the ACS was unavailable or the cardholder was not enrolled in interactive authentication. A proof-of-attempt is recorded
liability_shift
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. For frictionless and challenge flows the issuer accepts liability. For attempt flows the card network stands in and accepts liability when the issuer’s ACS is unavailable.
  • false: the merchant retains fraud liability.
You can still proceed with a transaction when liability_shift is false. The 3DS cryptogram is still valid — the difference is only in who bears the chargeback risk.
failure_reason
string | null
Human-readable failure reason when auth_status is FAILED. Null otherwise.
expires_at
string
ISO 8601 timestamp. Sessions expire 1 hour after creation. After this time, the session cannot be used and a new one must be created.
created_at
string
ISO 8601 timestamp of when the session was created.
updated_at
string
ISO 8601 timestamp of the last session update. Equals created_at initially — updated when the 3DS element completes authentication.

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 /authenticate result is uncertain (timeout/network failure), fetch transaction first. Retry only if transaction is still AWAITING_3DS and use a new session.