> ## Documentation Index
> Fetch the complete documentation index at: https://docs.rinne.com.br/llms.txt
> Use this file to discover all available pages before exploring further.

# 3D Secure authentication

> Implement official 3DS session-first and transaction-first flows with self and merchant endpoints.

3D Secure (3DS) authenticates the cardholder before card authorization and helps reduce fraud in card-not-present payments.

<Info>
  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.
</Info>

## Endpoint context (self vs merchant)

Use endpoint family based on the API key context you are operating in.

| Context                            | Create 3DS session                                                                                                         | Authenticate pending transaction                                                                                                                            | Typical permission                                   |
| ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------- |
| Self (company acting as itself)    | [`POST /v1/3ds-sessions`](/api-reference/3d-secure/create-a-3d-secure-session)                                             | [`POST /v1/transactions/{transactionId}/authenticate`](/api-reference/3d-secure/authenticate-a-transaction-with-3d-secure)                                  | `3ds.create`, `transaction.create`                   |
| Organization on behalf of merchant | [`POST /v1/merchants/{merchantId}/3ds-sessions`](/api-reference/3d-secure/create-a-3d-secure-session-linked-to-a-merchant) | [`POST /v1/merchants/{merchantId}/transactions/{transactionId}/authenticate`](/api-reference/3d-secure/authenticate-a-merchants-transaction-with-3d-secure) | `merchant.3ds.create`, `merchant.transaction.create` |

## Security contract with rinne-js

Use rinne-js secure components as your source of card values:

* [Card Element](/rinne-js/card-element) for card number (encrypted), CVC (encrypted), and expiry (plain month/year).
* [3D Secure Element](/rinne-js/three-d-secure-element) for challenge handling.

<Warning>
  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.
</Warning>

## Critical ID mapping

| Value            | From                 | Use                                                                             |
| ---------------- | -------------------- | ------------------------------------------------------------------------------- |
| `id` (UUID)      | 3DS session response | Send as `three_d_secure_session_id` in transaction create/authenticate requests |
| `tds_session_id` | 3DS session response | Pass 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.

<Warning>
  `require_3ds` and `refuse_on_challenge` cannot both be `true` in the same transaction request.
</Warning>

<Note>
  These flags are transaction-creation policies only. They do not create a 3DS session or run challenge automatically.
</Note>

### Choosing the right flag strategy

| Strategy              | Immediate transaction outcome                                           | What you still implement                                              | Recommended when                                                                          |
| --------------------- | ----------------------------------------------------------------------- | --------------------------------------------------------------------- | ----------------------------------------------------------------------------------------- |
| `require_3ds`         | Card transaction is created in `AWAITING_3DS` every time                | Create session, run challenge when required, and call `/authenticate` | You want mandatory 3DS while keeping one transaction-first backend path                   |
| `refuse_on_challenge` | Challenge-triggered paths return `REFUSED` with `CHALLENGE_NOT_ALLOWED` | Refusal handling/retry logic only                                     | You 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.

<Info>
  With `refuse_on_challenge`, non-challenge transactions continue normal provider processing. Only challenge-triggered paths are refused.
</Info>

## Flow A: Session-first

Use this flow when authentication should happen before transaction creation.

<Steps>
  <Step title="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.

    <Tabs>
      <Tab title="Self endpoint">
        ```bash cURL theme={null}
        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": "customer@example.com",
            "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"
            }
          }'
        ```
      </Tab>

      <Tab title="Merchant endpoint">
        ```bash cURL theme={null}
        curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/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": "customer@example.com",
            "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"
            }
          }'
        ```
      </Tab>
    </Tabs>

    <ResponseExample>
      ```json 201 Created theme={null}
      {
        "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"
      }
      ```

      ```json 201 Created (session creation failed — card rejected before flow started) theme={null}
      {
        "id": "550e8400-e29b-41d4-a716-446655440002",
        "tds_session_id": "tds_visa_e7c2yfa3867e",
        "auth_status": "FAILED",
        "consumption_status": "NOT_CONSUMED",
        "authentication_flow": null,
        "liability_shift": null,
        "failure_reason": "Card not enrolled in 3DS",
        "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"
      }
      ```
    </ResponseExample>

    <Note>
      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.
    </Note>

    <Note>
      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.
    </Note>
  </Step>

  <Step title="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.

    ```typescript theme={null}
    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
    ```
  </Step>

  <Step title="Create transaction with three_d_secure_session_id">
    Use session `id` as `three_d_secure_session_id`.

    <Tabs>
      <Tab title="Self endpoint">
        ```bash cURL theme={null}
        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",
              "cardholder_name": "Maria Silva",
              "last_digits": "1111"
            },
            "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000"
          }'
        ```
      </Tab>

      <Tab title="Merchant endpoint">
        ```bash cURL theme={null}
        curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/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",
              "cardholder_name": "Maria Silva",
              "last_digits": "1111"
            },
            "three_d_secure_session_id": "550e8400-e29b-41d4-a716-446655440000"
          }'
        ```
      </Tab>
    </Tabs>
  </Step>

  <Step title="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.

    <Note>
      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.).
    </Note>
  </Step>
</Steps>

## 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.

<Info>
  In transaction-first, transaction status is the source of truth for when 3DS is required. Call `/authenticate` only for transactions currently in `AWAITING_3DS`.
</Info>

### Transaction-first status contract

| Transaction status                  | Meaning in this flow                                      | Next action                                                                   |
| ----------------------------------- | --------------------------------------------------------- | ----------------------------------------------------------------------------- |
| `AWAITING_3DS`                      | 3DS is required now                                       | Create 3DS session, complete challenge when needed, then call `/authenticate` |
| `PROCESSING`                        | Provider processing continues without immediate challenge | Wait for webhook/status update; do not call `/authenticate` yet               |
| `AUTHORIZED` or `APPROVED`          | Payment completed without additional challenge            | Continue fulfillment/capture flow                                             |
| `REFUSED` + `CHALLENGE_NOT_ALLOWED` | Challenge would be required, but policy blocked it        | Fail fast path, retry with different policy only if desired                   |
| `REFUSED` (other reasons)           | Regular decline                                           | Follow your normal decline handling                                           |

<Note>
  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 `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.
</Note>

<Steps>
  <Step title="Create card transaction">
    For deterministic transaction-first 3DS entry, set `require_3ds: true`.

    <Tabs>
      <Tab title="Self endpoint">
        ```bash cURL theme={null}
        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",
              "cardholder_name": "Maria Silva",
              "last_digits": "1111"
            },
            "require_3ds": true
          }'
        ```
      </Tab>

      <Tab title="Merchant endpoint">
        ```bash cURL theme={null}
        curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/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",
              "cardholder_name": "Maria Silva",
              "last_digits": "1111"
            },
            "require_3ds": true
          }'
        ```
      </Tab>
    </Tabs>

    <ResponseExample>
      ```json Example response theme={null}
      {
        "id": "tx_123456789",
        "status": "AWAITING_3DS",
        "amount": 12990,
        "currency": "BRL"
      }
      ```
    </ResponseExample>

    <Note>
      `/authenticate` only accepts `three_d_secure_session_id`. CVV is not sent again in this step.
    </Note>
  </Step>

  <Step title="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.
  </Step>

  <Step title="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 rinne-js.
    * 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`.
  </Step>

  <Step title="Authenticate pending transaction with session id">
    <Tabs>
      <Tab title="Self endpoint">
        ```bash cURL theme={null}
        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"
          }'
        ```
      </Tab>

      <Tab title="Merchant endpoint">
        ```bash cURL theme={null}
        curl -X POST 'https://api-sandbox.rinne.com.br/core/v1/merchants/MERCHANT_ID/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"
          }'
        ```
      </Tab>
    </Tabs>

    <ResponseExample>
      ```json Example response theme={null}
      {
        "id": "tx_123456789",
        "status": "APPROVED",
        "amount": 12990,
        "currency": "BRL"
      }
      ```
    </ResponseExample>
  </Step>

  <Step title="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.
  </Step>
</Steps>

### Transaction-first authenticate failures and recovery

| Failure condition                             | Typical API behavior              | Recovery action                                                                                           |
| --------------------------------------------- | --------------------------------- | --------------------------------------------------------------------------------------------------------- |
| Transaction not in `AWAITING_3DS`             | `400` validation error            | Fetch latest transaction state and branch from actual status; do not keep calling `/authenticate` blindly |
| Session not authenticated/failed              | `400` validation error            | Create a new 3DS session and complete challenge again                                                     |
| Session expired/already consumed              | `400` validation error            | Create a new session; never reuse previous session                                                        |
| Session scope/amount/currency mismatch        | `400` validation error            | Recreate session in correct context with exact transaction amount and currency                            |
| Duplicate or concurrent authenticate attempts | One attempt succeeds, others fail | Treat `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

<ParamField body="amount" type="integer" required>
  Amount in cents. Must exactly match the transaction amount you will use this session for.
</ParamField>

<ParamField body="currency" type="string" required>
  ISO 4217 currency code (e.g. `BRL`). Must exactly match the transaction currency.
</ParamField>

<ParamField body="card" type="object" required>
  Encrypted card data from the rinne-js Card Element.

  <Expandable title="card fields">
    <ParamField body="card.number" type="string" required>
      Encrypted card number (`ev:` prefix) from the Card Element. Card numbers that reach the API unencrypted are rejected with `400 VALIDATION_ERROR`.

      Only Visa, Mastercard, and American Express can be 3DS-authenticated. A card from any other network (Elo, Hipercard, Discover, Diners, JCB) is rejected with a `400 VALIDATION_ERROR` on `card.number` — no session is created, so there is no `auth_status` to branch on. Route these cards through a card transaction without 3DS instead.
    </ParamField>

    <ParamField body="card.expiry.month" type="string" required>
      Two-digit expiry month (`MM` format).
    </ParamField>

    <ParamField body="card.expiry.year" type="string" required>
      Four-digit expiry year (`YYYY` format).
    </ParamField>
  </Expandable>
</ParamField>

<ParamField body="merchant.website" type="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.
</ParamField>

<ParamField body="payer_email" type="string">
  Cardholder email. Optional but helps issuers assess risk and reduces unnecessary challenges.
</ParamField>

<ParamField body="payer_name" type="string">
  Cardholder name as it appears on the card.
</ParamField>

<ParamField body="payer_document" type="string">
  Cardholder document number (e.g. CPF for Brazilian cardholders).
</ParamField>

<ParamField body="billing_address" type="object">
  Cardholder billing address.

  <Expandable title="billing_address fields">
    <ParamField body="billing_address.line" type="string">Street address.</ParamField>
    <ParamField body="billing_address.city" type="string">City.</ParamField>
    <ParamField body="billing_address.state" type="string">State or province code.</ParamField>
    <ParamField body="billing_address.postal_code" type="string">Postal or ZIP code.</ParamField>
    <ParamField body="billing_address.country" type="string">Two-letter ISO 3166 country code (e.g. `BR`).</ParamField>
  </Expandable>
</ParamField>

## Session response fields

<ResponseField name="id" type="string">
  Internal session UUID. Use this as `three_d_secure_session_id` in transaction create or `/authenticate` requests.
</ResponseField>

<ResponseField name="tds_session_id" type="string">
  Provider session ID. Pass this to `rinne.elements.threeDSecure().mount(tds_session_id)`.
</ResponseField>

<ResponseField name="auth_status" type="string">
  Authentication outcome. One of `ACTION_REQUIRED`, `AUTHENTICATED`, or `FAILED`.
</ResponseField>

<ResponseField name="consumption_status" type="string">
  Whether the session has been linked to a transaction. Sessions are single-use.

  | Value          | Meaning                                                                                                                               |
  | -------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
  | `NOT_CONSUMED` | Available for use — not yet linked to any transaction                                                                                 |
  | `PROCESSING`   | Currently being claimed by a transaction request. This is a transient internal state that lasts only during the atomic link operation |
  | `CONSUMED`     | Already used — cannot be reused. Create a new session if needed                                                                       |
</ResponseField>

<ResponseField name="authentication_flow" type="string | null">
  How authentication completed. Null at session creation — populated after the 3DS element finishes the authentication flow.

  | Value          | Meaning                                                                                                                                                          |
  | -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
  | `frictionless` | Issuer authenticated the cardholder silently with no user interaction                                                                                            |
  | `challenge`    | Issuer presented a verification step (OTP, app notification, biometric) that the cardholder completed                                                            |
  | `attempt`      | Issuer's directory server responded but the ACS was unavailable or the cardholder was not enrolled in interactive authentication. A proof-of-attempt is recorded |
</ResponseField>

<ResponseField name="liability_shift" type="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.

  <Tip>
    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.
  </Tip>
</ResponseField>

<ResponseField name="failure_reason" type="string | null">
  Human-readable failure reason when `auth_status` is `FAILED`. Null otherwise.
</ResponseField>

<ResponseField name="expires_at" type="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.
</ResponseField>

<ResponseField name="created_at" type="string">
  ISO 8601 timestamp of when the session was created.
</ResponseField>

<ResponseField name="updated_at" type="string">
  ISO 8601 timestamp of the last session update. Equals `created_at` initially — updated when the 3DS element completes authentication.
</ResponseField>

## 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.

## Related docs

* [Card transactions](/guides/card-transactions)
* [Wallet transactions](/guides/wallet-transactions)
* [Testing](/guides/testing)
* [Guide: Card + 3DS Checkout (rinne-js)](/rinne-js/guides/card-3ds-checkout)
