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

# Adding 3DS authentication

This guide explains how to add 3DS Authentication to your native mobile integration. By using the native 3DS flow, you handle the "Identify" and "Challenge" phases directly within your app, providing a seamless experience that avoids browser redirects and improves conversion rates.

## Prerequisites

This guide explains how to add 3DS authentication to your native mobile integration. Before you begin, ensure you have:

1. The Gr4vy SDK installed in your project (see [native vaulting guide](./vault) for installation steps).
2. Your backend set up to create checkout sessions and process transactions.

<Warning>
  Native mobile 3DS requires merchant account-level 3DS configuration to be enabled and properly configured. This feature is currently in beta. Please contact the support team to enable it for your account, then follow the [3DS Setup guide](/guides/features/3ds/setup) to configure it before proceeding with this integration.
</Warning>

## How it works

The native 3DS implementation automatically handles all device fingerprinting requirements, including collecting the device's IP address, user agent, location data, and other authentication metadata needed for 3-D Secure compliance. This ensures full compliance with 3-D Secure specifications without requiring manual fingerprint collection on your side.

### Limitations & Considerations

When you use native 3DS with these native SDKs, it is important to pass the transaction context (for example, `metadata`, `merchant_initiated`, or `cart_items`) when you create the Checkout Session.
As native 3DS runs before transaction creation, so this context must already be present at session creation time to find the right 3DS profile, as well as take into consideration any flow rules.

Additionally, it's important to note that the system runs 3DS Flow rules at the time of the enrollment check, which is run at the time of submitting Secure Fields. Currently, this Flow rule check doesn't
support anti-fraud checks as these are currently tied to a transactions, though there are plans to add this in the near future.

## Implementation

### 1. Create a Checkout Session

First, create a checkout session on your backend with the transaction amount and currency. This session stores the authenticated card data for 3DS processing.

<CodeGroup>
  ```csharp C# theme={"system"}
  using Gr4vy;
  using Gr4vy.Models.Components;

  var sdk = new Gr4vySDK(
      merchantAccountId: "default",
      bearerAuth: "<YOUR_BEARER_TOKEN_HERE>"
  );

  var checkoutSession = await sdk.CheckoutSessions.CreateAsync(
      checkoutSessionCreate: new CheckoutSessionCreate() {
          Amount = 1800,
          Currency = "EUR",
      }
  );

  // Return the session ID to your mobile app
  var sessionId = checkoutSession.CheckoutSession.Id;
  ```

  ```go Go theme={"system"}
  package main

  import(
  	"context"
  	gr4vygo "github.com/gr4vy/gr4vy-go"
  	"os"
  	"github.com/gr4vy/gr4vy-go/models/components"
  	"log"
  )

  func main() {
      ctx := context.Background()

      s := gr4vygo.New(
          gr4vygo.WithMerchantAccountID("default"),
          gr4vygo.WithSecurity(os.Getenv("GR4VY_BEARER_AUTH")),
      )

      checkoutSession, err := s.CheckoutSessions.Create(ctx, &components.CheckoutSessionCreate{
          Amount: gr4vygo.Int64(1800),
          Currency: gr4vygo.String("EUR"),
      })
      if err != nil {
          log.Fatal(err)
      }
      if checkoutSession != nil {
          // Return the session ID to your mobile app
          log.Printf("Session ID: %s", checkoutSession.CheckoutSession.ID)
      }
  }
  ```

  ```java Java theme={"system"}
  import com.gr4vy.sdk.Gr4vy;
  import com.gr4vy.sdk.models.components.CheckoutSessionCreate;
  import com.gr4vy.sdk.models.operations.CreateCheckoutSessionResponse;
  import java.lang.Exception;

  public class Application {
      public static void main(String[] args) throws Exception {
          Gr4vy sdk = Gr4vy.builder()
                  .merchantAccountId("default")
                  .bearerAuth("<YOUR_BEARER_TOKEN_HERE>")
              .build();

          CreateCheckoutSessionResponse res = sdk.checkoutSessions().create(
              CheckoutSessionCreate.builder()
                  .amount(1800L)
                  .currency("EUR")
                  .build()
          ).call();

          if (res.checkoutSession().isPresent()) {
              // Return the session ID to your mobile app
              String sessionId = res.checkoutSession().get().id();
              System.out.println("Session ID: " + sessionId);
          }
      }
  }
  ```

  ```php PHP theme={"system"}
  declare(strict_types=1);

  require 'vendor/autoload.php';

  use Gr4vy;

  $sdk = Gr4vy\SDK::builder()
      ->setMerchantAccountId('default')
      ->setSecurity('<YOUR_BEARER_TOKEN_HERE>')
      ->build();

  $response = $sdk->checkoutSessions->create(
      checkoutSessionCreate: new Gr4vy\CheckoutSessionCreate(
          amount: 1800,
          currency: 'EUR',
          country: 'DE'
      )
  );

  if ($response->checkoutSession !== null) {
      // Return the session ID to your mobile app
      echo "Session ID: " . $response->checkoutSession->id;
  }
  ```

  ```python Python theme={"system"}
  from gr4vy import Gr4vy
  import os

  with Gr4vy(
      merchant_account_id="default",
      bearer_auth=os.getenv("GR4VY_BEARER_AUTH", ""),
  ) as g_client:
      # Create a checkout session with amount and currency
      checkout_session = g_client.checkout_sessions.create(
          checkout_session_create={
              "amount": 1800,
              "currency": "EUR",
          }
      )
      # Return the session ID to your mobile app
      print(checkout_session.id)
  ```

  ```ts TypeScript theme={"system"}
  import { Gr4vy } from '@gr4vy/sdk'

  const gr4vy = new Gr4vy({
      gr4vyId: 'your-gr4vy-id',
      privateKey: 'your-private-key',
      environment: 'sandbox',
  })

  async function createCheckoutSession() {
      const checkoutSession = await gr4vy.checkoutSessions.create({
          amount: 1800,
          currency: 'EUR',
      })
      // Return the session ID to your mobile app
      console.log(checkoutSession.id)
  }

  createCheckoutSession()
  ```
</CodeGroup>

Pass this session ID to your mobile app so it can be used in the next step.

### 2. Update SDK logic

<CodeGroup>
  ```swift Swift (iOS) {16, 22-34, 59, 65-67} theme={"system"}
  /// Create card data
  let cardData = Gr4vyCardData(
      paymentMethod: .card(CardPaymentMethod(
          number: "4111111111111111",
          expirationDate: "12/25",
          securityCode: "123"
      ))
  )

  // Tokenize with 3DS authentication (async/await)
  do {
      let result = try await gr4vy.tokenize(
          checkoutSessionId: "session_123",
          cardData: cardData,
          sdkMaxTimeoutMinutes: 5,
          authenticate: true
      )

      if result.tokenized {
          print("Payment method tokenized successfully")

          // Check authentication details
          if let auth = result.authentication {
              print("3DS attempted: \(auth.attempted)")
              print("Authentication type: \(auth.type ?? "N/A")")
              print("Transaction status: \(auth.transactionStatus ?? "N/A")")

              if auth.hasCancelled {
                  print("User cancelled authentication")
              }
              if auth.hasTimedOut {
                  print("Authentication timed out")
              }
          }
      }
  } catch {
      print("Error tokenizing payment method: \(error)")
  }

  // With explicit view controller (async/await)
  do {
      let result = try await gr4vy.tokenize(
          checkoutSessionId: "session_123",
          cardData: cardData,
          viewController: self, // Explicitly provide the presenting view controller
          sdkMaxTimeoutMinutes: 5,
          authenticate: true
      )
      print("Tokenization complete: \(result.tokenized)")
  } catch {
      print("Error: \(error)")
  }

  // Completion handler variant
  gr4vy.tokenize(
      checkoutSessionId: "session_123",
      cardData: cardData,
      sdkMaxTimeoutMinutes: 5,
      authenticate: true
  ) { result in
      switch result {
      case .success(let tokenizeResult):
          if tokenizeResult.tokenized {
              print("Payment method tokenized successfully")
              if let auth = tokenizeResult.authentication {
                  print("Transaction status: \(auth.transactionStatus ?? "N/A")")
              }
          }
      case .failure(let error):
          print("Error tokenizing payment method: \(error)")
      }
  }
  ```

  ```kotlin Kotlin (Android) {18,24-36, 49, 56-58} theme={"system"}
  // Create card data
  val cardData = Gr4vyCheckoutSessionRequest(
      paymentMethod = Gr4vyPaymentMethod.Card(
          number = "4111111111111111",
          expirationDate = "12/25",
          securityCode = "123"
      )
  )

  // Tokenize with 3DS authentication (suspend function)
  lifecycleScope.launch {
      try {
          val result = gr4vy.tokenize(
              checkoutSessionId = "session_123",
              cardData = cardData,
              activity = this@PaymentActivity,
              sdkMaxTimeoutMinutes = 5,
              authenticate = true
          )

          if (result.tokenized) {
              println("Payment method tokenized successfully")

              // Check authentication details
              result.authentication?.let { auth ->
                  println("3DS attempted: ${auth.attempted}")
                  println("Authentication type: ${auth.type}")
                  println("Transaction status: ${auth.transactionStatus}")

                  if (auth.hasCancelled) {
                      println("User cancelled authentication")
                  }
                  if (auth.hasTimedOut) {
                      println("Authentication timed out")
                  }
              }
          }
      } catch (error: Gr4vyError) {
          println("Error tokenizing payment method: $error")
      }
  }

  // Callback version
  gr4vy.tokenize(
      checkoutSessionId = "session_123",
      cardData = cardData,
      activity = this@PaymentActivity,
      sdkMaxTimeoutMinutes = 5,
      authenticate = true
  ) { result ->
      when {
          result.isSuccess -> {
              val tokenizeResult = result.getOrNull()
              if (tokenizeResult?.tokenized == true) {
                  println("Payment method tokenized successfully")
                  tokenizeResult.authentication?.let { auth ->
                      println("Transaction status: ${auth.transactionStatus}")
                  }
              }
          }
          result.isFailure -> {
              println("Error tokenizing payment method: ${result.exceptionOrNull()}")
          }
      }
  }
  ```
</CodeGroup>

### 3. Backend Authorization

Once the SDK returns a successful result, the 3DS verification data is securely stored within the Checkout Session. When your app notifies your backend, create the transaction by referencing the session ID in the `payment_method` object.

The system automatically detects the 3DS data and ensures it is passed to your payment processor to satisfy SCA requirements.

```json theme={"system"}
POST /transactions
{

    "amount": 1800,
    "currency": "EUR",
    "country": "DE",
    "intent": "capture",
    "payment_method": {
        "method": "checkout-session",
        "id": "[checkout-session-id]"
    },
    ...
}
```

### Handling failed authentication

If a customer fails or cancels their 3DS authentication, you have the option to still process the payment using the vaulted card data. This is useful for scenarios where:

* The customer wants to retry with a different payment method later
* You want to reduce friction by allowing offline card storage
* Your business model supports processing payments without 3DS authentication (subject to regional regulations and processor rules)

## Theming

When a 3DS challenge is required, the Gr4vy SDK presents a
native interface provided by the card issuer. You can customize the appearance of
these screens to ensure they match your app's branding and provide a
consistent user experience.

Customization is handled via a `uiCustomization` object passed during the SDK initialization. This allows you to define colors, fonts, and button styles that are applied to the 3DS challenge modals.

* **Text:** Colors and font sizes for headings, body text, and input fields.
* **Buttons:** Background colors, text colors, and corner radii for "Continue" and "Cancel" actions.
* **Navigation Bar:** Background and title colors for the header.
* **Input Fields:** Border colors and focus states.

<CodeGroup>
  ```swift Swift theme={"system"}
  // Create toolbar customization
  let toolbar = Gr4vyThreeDSToolbarCustomization(
      textColorHex: "#FFFFFF",
      backgroundColorHex: "#007AFF",
      headerText: "Secure Verification",
      buttonText: "Cancel"
  )

  // Create button customizations
  let submitButton = Gr4vyThreeDSButtonCustomization(
      textFontSize: 16,
      textColorHex: "#FFFFFF",
      backgroundColorHex: "#007AFF",
      cornerRadius: 8
  )

  let cancelButton = Gr4vyThreeDSButtonCustomization(
      textFontSize: 16,
      textColorHex: "#007AFF",
      backgroundColorHex: "#F0F0F0",
      cornerRadius: 8
  )

  // Create label customization
  let label = Gr4vyThreeDSLabelCustomization(
      textFontSize: 14,
      textColorHex: "#333333",
      headingTextFontSize: 18,
      headingTextColorHex: "#000000"
  )

  // Create text box customization
  let textBox = Gr4vyThreeDSTextBoxCustomization(
      textFontSize: 16,
      textColorHex: "#000000",
      borderWidth: 1,
      borderColorHex: "#CCCCCC",
      cornerRadius: 4
  )

  // Combine into UI customization
  let uiCustomization = Gr4vyThreeDSUiCustomization(
      label: label,
      toolbar: toolbar,
      textBox: textBox,
      buttons: [
          .submit: submitButton,
          .cancel: cancelButton
      ]
  )

  // Use with tokenization
  let result = try await gr4vy.tokenize(
      checkoutSessionId: "session_123",
      cardData: cardData,
      authenticate: true,
      uiCustomization: Gr4vyThreeDSUiCustomizationMap(default: uiCustomization)
  )
  ```

  ```kotlin Kotlin theme={"system"}
  // Create toolbar customization
  val toolbar = Gr4vyThreeDSToolbarCustomization(
      textFontName = "sans-serif-medium",
      textFontSize = 17,
      textColorHex = "#FFFFFF",
      backgroundColorHex = "#007AFF",
      headerText = "Secure Verification",
      buttonText = "Cancel"
  )

  // Create button customizations
  val submitButton = Gr4vyThreeDSButtonCustomization(
      textFontSize = 16,
      textColorHex = "#FFFFFF",
      backgroundColorHex = "#007AFF",
      cornerRadius = 8
  )

  val cancelButton = Gr4vyThreeDSButtonCustomization(
      textFontSize = 16,
      textColorHex = "#007AFF",
      backgroundColorHex = "#F0F0F0",
      cornerRadius = 8
  )

  // Create label customization
  val label = Gr4vyThreeDSLabelCustomization(
      textFontSize = 14,
      textColorHex = "#333333",
      headingTextFontSize = 18,
      headingTextColorHex = "#000000"
  )

  // Create text box customization
  val textBox = Gr4vyThreeDSTextBoxCustomization(
      textFontSize = 16,
      textColorHex = "#000000",
      borderWidth = 1,
      borderColorHex = "#CCCCCC",
      cornerRadius = 4
  )

  // Combine into UI customization
  val uiCustomization = Gr4vyThreeDSUiCustomization(
      label = label,
      toolbar = toolbar,
      textBox = textBox,
      buttons = mapOf(
          Gr4vyThreeDSButtonType.SUBMIT to submitButton,
          Gr4vyThreeDSButtonType.CANCEL to cancelButton
      )
  )

  // Use with tokenization
  val result = gr4vy.tokenize(
      checkoutSessionId = "session_123",
      cardData = cardData,
      activity = this@PaymentActivity,
      authenticate = true,
      uiCustomization = Gr4vyThreeDSUiCustomizationMap(default = uiCustomization)
  )
  ```
</CodeGroup>

All color values should be provided as hexadecimal strings (for example, `#007AFF`).

## Testing and validation

To test your implementation, use the 3DS Test Cards defined here in your sandbox environment.

* Frictionless Flow: Use a card that is configured to skip the challenge phase. The SDK can return success immediately after the authentication call.
* Challenge Flow: Use a card that requires an SMS or Biometric challenge. The SDK can automatically present the native modal for you to complete.

Use these card numbers and scenarios to test your native 3DS implementation in the Gr4vy Sandbox environment. These cards allow you to simulate various outcomes, including frictionless authentication, mandatory challenges, and specific failure states.

### Testing scenarios

#### Visa

| Card Number           | Expected 3DS Outcome                                           |
| --------------------- | -------------------------------------------------------------- |
| `4000 0000 0000 0010` | Frictionless Success: Authenticated without user interaction.  |
| `4000 0000 0000 0028` | Challenge Required: Triggers a native 3DS challenge UI.        |
| `4000 0000 0000 0044` | Authentication Failed: Simulates a "Not Authenticated" result. |
| `4000 0000 0000 1042` | Rejected: The authentication request was explicitly rejected.  |

#### Mastercard

| Card Number           | Expected 3DS Outcome                                                       |
| --------------------- | -------------------------------------------------------------------------- |
| `5100 0000 0000 0010` | Challenge Required: Triggers a native 3DS challenge UI.                    |
| `5200 0000 0000 2052` | Frictionless Success: Authenticated without user interaction.              |
| `5200 0000 0000 1039` | Unavailable: Simulates a scenario where 3DS is not available for the card. |
| `5100 0000 0000 0065` | Suspected Fraud: Frictionless failure due to suspected fraud.              |

#### American Express

| Card Number          | Expected 3DS Outcome                                          |
| -------------------- | ------------------------------------------------------------- |
| `3415 0209 8634 895` | Frictionless Success: Authenticated without user interaction. |
| `3486 3826 7931 507` | Challenge Required: Triggers a native 3DS challenge UI.       |
| `3456 9539 9207 589` | Authentication Failed: Frictionless failure.                  |

### Challenge screen inputs

When a challenge is triggered, use the following values to simulate different transaction results within the native UI.

#### OTP (One-Time Password)

When the challenge screen asks for a code, enter one of the following:

| OTP Code | Resulting Transaction Status | Description                       |
| -------- | ---------------------------- | --------------------------------- |
| `1234`   | Y                            | Success / Authenticated.          |
| `1111`   | N                            | Not Authenticated.                |
| `2222`   | R                            | Rejected.                         |
| `3333`   | U                            | Unavailable / Unable to complete. |

#### Selection options

Some challenges may present a list of options (for example, "Select your favorite city").

**Single-select**

* Paris or Nice: Returns Success (Y).

**Multi-select**

* Paris & Lyon: Returns Success (Y).
* Toulouse & Lyon: Returns Success (Y).

## Best practices for testing

1. Verify UI transitions: Ensure your app correctly transitions from the card entry screen to the native 3DS challenge modal and back.
2. Test error states: Use the "Rejected" or "Failed" cards to ensure your app displays helpful messaging to the user when a card cannot be verified.
3. **Check Backend Session:** After a successful test, verify that the checkout\_session\_id on your server reflects the successful authentication before attempting the final transaction.
