# Android SDK+ Integration Guide

> Product: Rokt Ecommerce
>
> Intended implementer: Rokt Ecommerce partner
>
> Integration surface: The partner’s owned website, app, or transaction experience
>
> Not intended for: Advertisers implementing Rokt Ads
>
> Canonical documentation: [https://docs.rokt.com/integration-guides/ecommerce/sdk/android/](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/)
>
> This is a complete machine-readable guide generated from the same MDX source as the rendered documentation. Read the complete guide before implementing the integration, and do not edit this generated file directly.

These SDK+ integration guides are for Rokt Ecommerce partners—ecommerce businesses integrating Rokt into transaction experiences they own, including websites, apps, carts, checkouts, payment pages, and confirmation flows.
They are not implementation guides for advertisers using Rokt Ads to acquire customers across the Rokt Network. References to advertiser offers describe content rendered within an ecommerce partner’s owned experience. Advertisers should use the [Rokt Ads integration guides](https://docs.rokt.com/integration-guides/ads/).

This page explains how to implement the Rokt Ecommerce Android SDK+. The SDK+ passes user and transaction data to Rokt on configured screens so Rokt can render relevant experiences, such as offers on confirmation screens.

## 1. Add the Rokt SDK+ to Your Android App

Rokt SDK+ requires a minimum Android API level of **21+** (Android 5.0 Lollipop).

Update your Gradle file to include the necessary dependencies:

### build.gradle.kts dependencies

```kotlin
dependencies {
  implementation("com.mparticle:android-rokt-kit:6.0.0")
  implementation("com.mparticle:android-core:6.0.0")
}
```

### build.gradle dependencies

```groovy
dependencies {
  implementation "com.mparticle:android-rokt-kit:6.0.0"
  implementation "com.mparticle:android-core:6.0.0"
}
```

## 2. Initialize the Rokt SDK+

Insert the following initialization snippet in the `onCreate()` method of your `Application` class. The SDK+ must be initialized before any other SDK+ API calls. Replace `your-key` and `your-secret` with the key and secret provided by your Rokt team.

### Application.onCreate initialization

```kotlin
import com.mparticle.MParticle
import com.mparticle.MParticleOptions
import com.mparticle.networking.NetworkOptions

class YourApplicationClass : Application() {
  override fun onCreate() {
      super.onCreate()

      // Identify the current user:
      // If you do not have the user's email address, you can pass in a null value
      val identifyRequest = IdentityApiRequest.withEmptyUser()
          // Preferred: pass the customer's raw, unhashed email via .email().
          // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both.
          .email("j.smith@example.com")
          .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email")  // only if raw email unavailable
          // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both.
          .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number")  // only if raw mobile unavailable
          // Customer phone number in E.164 format.
          .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")
          // Partner's internal customer/account identifier (if the user is logged in).
          .customerId("cust_10482")
          .build()

      // If the user is identified with their email address, set additional user attributes.
      val identifyTask = BaseIdentityTask()
          .addSuccessListener { identityApiResult ->
              val user = identityApiResult.user
              user.setUserAttribute("example attribute key", "example attribute value")
          }

      // Enter your custom subdomain if you are using a first-party domain configuration (optional)
      val networkOptions = NetworkOptions.builder()
          .setCustomBaseURL("https://rkt.example.com")
          .build()

      val options: MParticleOptions = MParticleOptions.builder(this)
          .credentials(
              "your-key",   // The key provided by your Rokt account manager
              "your-secret" // The secret provided by your Rokt account manager
          )
          // Specify the data environment with environment:
          // Set it to Development if you are still testing your integration.
          // Set it to Production if your integration is ready for production data.
          // The default is AutoDetect which attempts to detect the environment automatically.
          .environment(MParticle.Environment.Development)
          .networkOptions(networkOptions)
          .identify(identifyRequest)
          .identifyTask(identifyTask)
          .build()

      MParticle.start(options)
  }
}
```

### Application.onCreate initialization

```java
import com.mparticle.MParticle;
import com.mparticle.MParticleOptions;
import com.mparticle.networking.NetworkOptions;

public class YourApplicationClass extends Application {
  @Override
  public void onCreate() {
      super.onCreate();

      // Identify the current user:
      // If you do not have the user's email address, you can pass in a null value
      IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser()
          // Preferred: pass the customer's raw, unhashed email via .email().
          // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both.
          .email("j.smith@example.com")
          .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email")  // only if raw email unavailable
          // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both.
          .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number")  // only if raw mobile unavailable
          // Customer phone number in E.164 format.
          .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")
          // Partner's internal customer/account identifier (if the user is logged in).
          .customerId("cust_10482")
          .build();

      // If the user is identified with their email address, set additional user attributes.
      BaseIdentityTask identifyTask = new BaseIdentityTask()
          .addSuccessListener(new TaskSuccessListener() {
              @Override
              public void onSuccess(IdentityApiResult identityApiResult) {
                  MParticleUser user = identityApiResult.getUser();
                  user.setUserAttribute("example attribute key", "example attribute value");
              }
          });

      // Enter your custom subdomain if you are using a first-party domain configuration (optional)
      NetworkOptions networkOptions = NetworkOptions.builder()
          .setCustomBaseURL("https://rkt.example.com")
          .build();

      MParticleOptions options = MParticleOptions.builder(this)
          .credentials(
              "your-key",   // The key provided by your Rokt account manager
              "your-secret" // The secret provided by your Rokt account manager
          )
          // Specify the data environment with environment:
          // Set it to Development if you are still testing your integration.
          // Set it to Production if your integration is ready for production data.
          // The default is AutoDetect which attempts to detect the environment automatically.
          .environment(MParticle.Environment.Development)
          .networkOptions(networkOptions)
          .identify(identifyRequest)
          .identifyTask(identifyTask)
          .build();

      MParticle.start(options);
  }
}
```

When inserting the initialization snippet into your `Application` class, you will see customizable fields for:

### 1. Entering your Rokt key and secret

Set `your-key` and `your-secret` inside `credentials` to the key and secret values provided by your Rokt account manager.

### 2. Setting your data environment

Set `environment` to `MParticle.Environment.Development` while testing to route data to the Development environment, and `MParticle.Environment.Production` to send live customer activity to Production.

### 3. Entering a custom first-party domain

Follow the instructions in [First-Party Domain Configuration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/), and set `customBaseURL` on `NetworkOptions` to your custom subdomain. Routing the Rokt SDK+ through your own domain reduces the risk of ad blockers and browsers from blocking ads or data. Omit `networkOptions` to send traffic to Rokt's default endpoints.

### 4. Identifying your user and setting attributes

In `identifyRequest`, pass the user's raw, un-hashed email via `.email()`. For hashed emails and other identifiers, see [Supported User Identifiers](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#supported-user-identifiers). Once identified, use the `identifyTask` success listener to set additional user attributes — see [User Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#user-attributes) for the recommended list.

#### identifyTask success listener

```kotlin
val identifyTask = BaseIdentityTask()
  .addSuccessListener { identityApiResult ->
      val user = identityApiResult.user
      user.setUserAttribute("example attribute key", "example attribute value")
  }
```

#### identifyTask success listener

```java
BaseIdentityTask identifyTask = new BaseIdentityTask()
  .addSuccessListener(new TaskSuccessListener() {
      @Override
      public void onSuccess(IdentityApiResult identityApiResult) {
          MParticleUser user = identityApiResult.getUser();
          user.setUserAttribute("example attribute key", "example attribute value");
      }
  });
```

> **Note**
>
> Always include `identifyRequest` in the initialization snippet. If you don't have the user's email at initialization, omit the `.email()` call — the SDK+ will still initialize, and you can identify the user later via [3. Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#identify-android). See [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#error-handling) for how to handle the `addFailureListener` callback — without error handling you may see data consistency issues at scale.

### Initializing with fonts

Instead of or in addition to supplying fonts on One Platform, you may use fonts already bundled with your application. This removes the potential for fonts to be downloaded at initialization time, reducing network utilization and the chance of download errors.

#### Using font assets

Pass a map to the `roktOptions` builder method that maps font PostScript names to their filepath in the `assets` directory. Check with your account manager if you are unsure of the PostScript names being used in your layout.

### Font assets

```kotlin
import com.mparticle.MParticle
import com.mparticle.MParticleOptions

class YourApplication : Application() {
  override fun onCreate() {
      super.onCreate()
      val options: MParticleOptions = MParticleOptions.builder(this)
          .credentials("your-key", "your-secret")
          .environment(MParticle.Environment.Development)
          .roktOptions(RoktOptions(fontFilePathMap = mapOf("Arial-Bold" to "fonts/arialbold.otf")))
          .build()
      MParticle.start(options)
  }
}
```

### Font assets

```java
import com.mparticle.MParticle;
import com.mparticle.MParticleOptions;

public class YourApplication extends Application {
  @Override
  public void onCreate() {
      super.onCreate();
      Map<String, String> fontFilePathMap = new HashMap<>();
      fontFilePathMap.put("Arial-Bold", "fonts/arialbold.otf");

      MParticleOptions options = MParticleOptions.builder(this)
          .credentials("your-key", "your-secret")
          .environment(MParticle.Environment.Development)
          .roktOptions(new RoktOptions(fontFilePathMap))
          .build();

      MParticle.start(options);
  }
}
```

## 3. Identify the User

The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#initialize-android) identifies the current user using the identifiers you provided in the script's `identifyRequest` object. After SDK initialization, you should keep the user's identity in sync whenever they log in, log out, or otherwise provide an identifier (for example, during checkout) using the appropriate method as described below.

### Supported user identifiers

### Show supported user identifiers

| Field        | Type   | Description                                                                                                                                    |
| ------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`      | string | Pass the customer's raw, unhashed email address via `.email("j.smith@example.com")`.                                                           |
| `mobile`     | string | Pass the customer's phone number in E.164 format via `.userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")`.                     |
| `customerid` | string | Pass your internal customer/account identifier via `.customerId("cust_10482")`. Send on every screen for logged-in users.                      |
| `other`      | string | Pass a SHA-256-hashed email via `.userIdentity(MParticle.IdentityType.Other, "hashed email")`. Only use when the raw email cannot be provided. |
| `other2`     | string | Pass a SHA-256-hashed mobile number via `.userIdentity(MParticle.IdentityType.Other2, "hashed mobile")`.                                       |

To identify the user:

### 1. Create an identifyRequest object

Create an `identifyRequest` object to contain the user's identifiers. You should integrate the user's raw, unhashed email address into the `email` field.

### 2. Create an identityCallback

To set additional user attributes, create an `identityCallback`. If the `identifyRequest` succeeds, then any user attributes you set inside the callback are assigned to the identified user.

### 3. Send the request using the method that matches the user's action

Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action:

- **`MParticle.getInstance()?.Identity()?.login`:** call when the user logs in or creates an account.
- **`MParticle.getInstance()?.Identity()?.identify`:** call when you obtain the user's email mid-session without a login transition (for example, a guest enters their email at checkout).
- **`MParticle.getInstance()?.Identity()?.logout`:** call when the user logs out.

Calling these methods transitions the SDK's record of the current user's state. The `login` and `logout` methods also automatically log a corresponding event to improve Rokt's attribution.

For example, to identify a user named Jane Smith with the email address `j.smith@example.com`, mobile number `+13125551515`, and customer ID `cust_10482`:

#### Identify Jane Smith

```kotlin
// 1. Create the identifyRequest object
val identifyRequest = IdentityApiRequest.withEmptyUser()
  // Preferred: pass the customer's raw, unhashed email via .email().
  // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both.
  .email("j.smith@example.com")
  .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email")  // only if raw email unavailable
  // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both.
  .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number")  // only if raw mobile unavailable
  .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")
  .customerId("cust_10482")
  .build()

// 2. Optionally set user attributes once the request succeeds.
val identityCallback = BaseIdentityTask()
  .addSuccessListener { identityApiResult ->
      val user = identityApiResult.user
      user.setUserAttribute("firstname", "Jane")
      user.setUserAttribute("lastname", "Smith")
  }

// 3. Call one of the following methods that best matches the user's action:
MParticle.getInstance()?.Identity()?.login(identifyRequest, identityCallback) // Call when the user logs in or creates an account
MParticle.getInstance()?.Identity()?.identify(identifyRequest, identityCallback) // Call when you obtain the user's email mid-session, but not during a login
MParticle.getInstance()?.Identity()?.logout() // Call when the user logs out
```

#### Identify Jane Smith

```java
// 1. Create the identifyRequest object
IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser()
  // Preferred: pass the customer's raw, unhashed email via .email().
  // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both.
  .email("j.smith@example.com")
  .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email")  // only if raw email unavailable
  // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both.
  .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number")  // only if raw mobile unavailable
  .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")
  .customerId("cust_10482")
  .build();

// 2. Optionally set user attributes once the request succeeds.
BaseIdentityTask identityCallback = new BaseIdentityTask()
  .addSuccessListener(new TaskSuccessListener() {
      @Override
      public void onSuccess(IdentityApiResult identityApiResult) {
          MParticleUser user = identityApiResult.getUser();
          user.setUserAttribute("firstname", "Jane");
          user.setUserAttribute("lastname", "Smith");
      }
  });

// 3. Call one of the following methods that best matches the user's action:
MParticle.getInstance().Identity().login(identifyRequest, identityCallback); // Call when the user logs in or creates an account
MParticle.getInstance().Identity().identify(identifyRequest, identityCallback); // Call when you obtain the user's email mid-session, but not during a login
MParticle.getInstance().Identity().logout(); // Call when the user logs out
```

## 4. Set User Attributes

Set user attributes **progressively** as the user navigates your app, not just at checkout. The more attributes you set, the better Rokt can resolve the customer and deliver relevant offers.

### Set User Attributes

```kotlin
import com.mparticle.MParticle

// Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method.
val currentUser = MParticle.getInstance()?.Identity()?.currentUser

// Once you have successfully set the current user to `currentUser`, you can set user attributes with:
currentUser?.setUserAttribute("custom-attribute-name", "custom-attribute-value")
// Note: all user attributes (including list attributes and tags) must have distinct names.

// Rokt recommends setting as many of the following user attributes as possible:
currentUser?.setUserAttribute("firstname", "John")
currentUser?.setUserAttribute("lastname", "Doe")
// Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901'
currentUser?.setUserAttribute("mobile", "3125551515")
currentUser?.setUserAttribute("age", "33")
currentUser?.setUserAttribute("gender", "M")
currentUser?.setUserAttribute("billingcity", "Brooklyn")
currentUser?.setUserAttribute("billingstate", "NY")
currentUser?.setUserAttribute("billingzipcode", "123456")
currentUser?.setUserAttribute("dob", "yyyymmdd")
currentUser?.setUserAttribute("title", "Mr")
currentUser?.setUserAttribute("language", "en")
currentUser?.setUserAttribute("predictedltv", "136.23")

// You can create a user attribute to contain a list of values
currentUser?.setUserAttributeList("favorite-genres", listOf("documentary", "comedy", "romance", "drama"))

// To remove a user attribute, call removeUserAttribute and pass in the attribute name. All user attributes share the same key space.
currentUser?.removeUserAttribute("attribute-to-remove")
```

### Set User Attributes

```java
import com.mparticle.MParticle;
import com.mparticle.MParticleUser;

// Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method.
MParticleUser currentUser = MParticle.getInstance().Identity().getCurrentUser();

// Once you have successfully set the current user to `currentUser`, you can set user attributes with:
currentUser.setUserAttribute("custom-attribute-name", "custom-attribute-value");
// Note: all user attributes (including list attributes and tags) must have distinct names.

// Rokt recommends setting as many of the following user attributes as possible:
currentUser.setUserAttribute("firstname", "John");
currentUser.setUserAttribute("lastname", "Doe");
// Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901'
currentUser.setUserAttribute("mobile", "3125551515");
currentUser.setUserAttribute("age", "33");
currentUser.setUserAttribute("gender", "M");
currentUser.setUserAttribute("billingcity", "Brooklyn");
currentUser.setUserAttribute("billingstate", "NY");
currentUser.setUserAttribute("billingzipcode", "123456");
currentUser.setUserAttribute("dob", "yyyymmdd");
currentUser.setUserAttribute("title", "Mr");
currentUser.setUserAttribute("language", "en");
currentUser.setUserAttribute("predictedltv", "136.23");

// You can create a user attribute to contain a list of values
List<String> attributeList = new ArrayList<>();
attributeList.add("documentary");
attributeList.add("comedy");
attributeList.add("romance");
attributeList.add("drama");
currentUser.setUserAttributeList("favorite-genres", attributeList);

// To remove a user attribute, call removeUserAttribute and pass in the attribute name. All user attributes share the same key space.
currentUser.removeUserAttribute("attribute-to-remove");
```

### User attributes

Set as many of the following as you can collect:

### Show all user attributes

| Field                | Type    | Description                                                                                                                       |
| -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `firstname`          | string  | Customer's first name. Used for personalization.                                                                                  |
| `lastname`           | string  | Customer's last name. Used for personalization.                                                                                   |
| `mobile`             | string  | Phone number formatted as `1112345678` or `+1 (222) 345-6789`. Used for identity resolution and relevance.                        |
| `birthyear`          | integer | Customer's birth year (e.g. `1990`). Preferred date-of-birth field. Alternates: `dob`, `age`. Used for eligibility and relevance. |
| `age`                | integer | Customer's age. Alternate to `dob`. Used for eligibility and relevance.                                                           |
| `dob`                | string  | Date of birth, `yyyymmdd`. Alternate to `age`. Used for eligibility and relevance.                                                |
| `gender`             | string  | Customer's gender. For example, `M`, `F`, `Male`, or `Female`. Used for relevance.                                                |
| `title`              | string  | Honorific. For example, `Mr`, `Mrs`, `Ms`. Used for personalization.                                                              |
| `language`           | string  | ISO 639-1 language code associated with the purchase. Used for relevance.                                                         |
| `billingaddress1`    | string  | Street address (e.g. `123 Main St`). Used for identity resolution and relevance.                                                  |
| `billingaddress2`    | string  | Apartment/unit (e.g. `Apt 4B`). Used for identity resolution.                                                                     |
| `billingcity`        | string  | Billing city. Used for relevance.                                                                                                 |
| `billingstate`       | string  | Billing state / province / region. Used for relevance and eligibility.                                                            |
| `billingzipcode`     | string  | Full ZIP or postcode (US preference is ZIP+4). Used for identity resolution and relevance.                                        |
| `country`            | string  | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `AU`). Used for eligibility and relevance.                                      |
| `newcustomer`        | boolean | Whether this is a first-time buyer. Used for relevance.                                                                           |
| `customertype`       | string  | Whether the user is authenticated (`guest` / `logged_in`). Used for relevance.                                                    |
| `loyaltytier`        | string  | Partner loyalty program tier. Used for relevance and eligibility.                                                                 |
| `loyaltyid`          | string  | Loyalty program member ID. Used for identity resolution.                                                                          |
| `predictedltv`       | decimal | Predicted total lifetime value, typically from a partner ML model. Used for relevance.                                            |
| `subscriptionstatus` | string  | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility.            |
| `customersegment`    | string  | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance.                                  |
| `acquisitionchannel` | string  | How the customer was originally acquired. Used for relevance.                                                                     |

All user attributes (including list attributes) must have distinct names.

## 5. Log Events

Track screen views, commerce events, and custom events so Rokt can understand where each customer is in their journey.

### Event category: Screen views

Call `MParticle.getInstance()?.logScreen()` with the name of the screen (e.g. `"homepage"`, `"product_detail_page"`). Include any additional custom attributes in the info map.

#### Log a screen view

```kotlin
MParticle.getInstance()?.logScreen(
  "homepage",
  mapOf("custom-attribute" to "custom-value")
)
```

#### Log a screen view

```java
HashMap<String, String> screenInfo = new HashMap<>();
screenInfo.put("custom-attribute", "custom-value");

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logScreen("homepage", screenInfo);
}
```

### Event category: Commerce events

Commerce events carry product-level details for the user's journey. Trigger a separate commerce event for each product action the customer takes.

Investing in full commerce event coverage is one of the highest-leverage things you can do during your integration. Each event tells Rokt something different about where the customer is in their journey: a product view signals exploration, an add-to-cart signals consideration, a checkout start signals purchase intent, and a completed purchase confirms conversion. With a richer signal, Rokt can personalize offers more effectively, measure placement performance accurately, and attribute conversions to the right touchpoints. Doing this work during your initial integration also avoids a retrofit later. The signal compounds over time: each event Rokt receives adds context used to sharpen personalization, improve attribution accuracy, and better resolve and segment your customer base on future visits.

Commerce events are logged with `CommerceEvent`, using a product action constant that identifies the customer action (viewing a product, adding to cart, starting checkout, completing a purchase, etc.).

#### Show all product action types

| Customer action            | Product action constant        |
| -------------------------- | ------------------------------ |
| Product detail page viewed | `Product.VIEW_DETAIL`          |
| Product clicked            | `Product.CLICK`                |
| Item added to cart         | `Product.ADD_TO_CART`          |
| Item removed from cart     | `Product.REMOVE_FROM_CART`     |
| Item added to wishlist     | `Product.ADD_TO_WISHLIST`      |
| Item removed from wishlist | `Product.REMOVE_FROM_WISHLIST` |
| Checkout flow initiated    | `Product.CHECKOUT`             |
| Checkout option selected   | `Product.CHECKOUT_OPTION`      |
| Order confirmed            | `Product.PURCHASE`             |
| Order refunded             | `Product.REFUND`               |

Tracking a commerce event takes three phases:

#### 1. Define the product

Build a `Product` with the `Product.Builder`. Set name, SKU, and price as constructor args; set additional fields like `quantity`, `category`, `brand`, and `variant` via builder calls.

##### Define a product

```kotlin
val product = Product.Builder("Double Room - Econ Rate", "econ-1", 100.00)
  .quantity(4.0)
  .category("room")
  .brand("lodge-o-rama")
  .variant("standard")
  .build()
```

##### Define a product

```java
Product product = new Product.Builder("Double Room - Econ Rate", "econ-1", 100.00)
  .quantity(4.0)
  .category("room")
  .brand("lodge-o-rama")
  .variant("standard")
  .build();
```

#### 2. Summarize the transaction

Build a `TransactionAttributes` for `Purchase`, `Checkout`, and `CheckoutOption` events. Include shipping and order-level coupons when applicable — order-level coupons belong here, not on individual products.

##### Summarize the transaction

```kotlin
val transactionAttributes = TransactionAttributes("ORDER-12345")
  .setRevenue(149.99)
  .setTax(12.50)
  .setShipping(5.99)
  .setCouponCode("SUMMER20")
```

##### Summarize the transaction

```java
TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-12345")
  .setRevenue(149.99)
  .setTax(12.50)
  .setShipping(5.99)
  .setCouponCode("SUMMER20");
```

#### 3. Log the commerce event

Build a `CommerceEvent` with `CommerceEvent.Builder`, passing a product action constant from the table above and your product(s). Attach `transactionAttributes` when applicable, then call `MParticle.getInstance()?.logEvent(event)`. Pick the customer action you want to log:

##### Commerce event: PLP impression

Log a product listing page (or category page) view as a product impression. Pass every visible product in a single `CommerceEvent` built with `addImpression`, and set the impression's list name to the list / category the customer is browsing.

| Field      | Type   | Required | Description                                                                         |
| ---------- | ------ | -------- | ----------------------------------------------------------------------------------- |
| `Name`     | string | yes      | List or category name (e.g. `"Mens Running Shoes"`). Becomes `list_name`.           |
| `Products` | array  | yes      | Product objects from `createProduct`. Set `Position` to each item's 1-indexed rank. |
| `currency` | string | yes      | ISO 4217 currency code (passed as event-level customAttribute).                     |

###### Example PLP impression event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Impression
import com.mparticle.commerce.Product

val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .category("Shoes")
  .brand("BrandX")
  .position(1)
  .build()

val impression = Impression("Mens Running Shoes", product)

val event = CommerceEvent.Builder(impression)
  .customAttributes(mapOf("currency" to "USD"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example PLP impression event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Impression;
import com.mparticle.commerce.Product;

Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .category("Shoes")
  .brand("BrandX")
  .position(1)
  .build();

Impression impression = new Impression("Mens Running Shoes", product);

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(impression)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: ViewDetail

Log when a customer opens a product detail page.

| Field         | Type    | Required | Description                         |
| ------------- | ------- | -------- | ----------------------------------- |
| `productsku`  | string  | yes      | Product SKU.                        |
| `productname` | string  | yes      | Display name.                       |
| `itemprice`   | decimal | yes      | Per-unit price at the time of view. |
| `currency`    | string  | yes      | ISO 4217 currency code.             |
| `list_name`   | string  | no       | Set if the user arrived from a PLP. |

###### Example ViewDetail event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product

val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build()

val event = CommerceEvent.Builder(Product.VIEW_DETAIL, product)
  .customAttributes(mapOf("currency" to "USD", "list_name" to "PLP-Running"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example ViewDetail event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;

Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build();

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");
attributes.put("list_name", "PLP-Running");

CommerceEvent event = new CommerceEvent.Builder(Product.VIEW_DETAIL, product)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: AddToCart

Log when a customer adds an item to the cart.

| Field        | Type    | Required | Description                                 |
| ------------ | ------- | -------- | ------------------------------------------- |
| `productsku` | string  | yes      | Product SKU.                                |
| `quantity`   | integer | yes      | Units added.                                |
| `itemprice`  | decimal | yes      | Per-unit price at time of add.              |
| `currency`   | string  | yes      | ISO 4217 currency code.                     |
| `couponCode` | string  | no       | Order-level coupon, if applied at add-time. |

###### Example AddToCart event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product

val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build()

val event = CommerceEvent.Builder(Product.ADD_TO_CART, product)
  .customAttributes(mapOf("currency" to "USD"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example AddToCart event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;

Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build();

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(Product.ADD_TO_CART, product)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: RemoveFromCart

Log when a customer removes an item from the cart.

| Field        | Type    | Required | Description             |
| ------------ | ------- | -------- | ----------------------- |
| `productsku` | string  | yes      | Product SKU.            |
| `quantity`   | integer | yes      | Units removed.          |
| `currency`   | string  | yes      | ISO 4217 currency code. |

###### Example RemoveFromCart event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product

val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build()

val event = CommerceEvent.Builder(Product.REMOVE_FROM_CART, product)
  .customAttributes(mapOf("currency" to "USD"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example RemoveFromCart event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;

Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build();

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(Product.REMOVE_FROM_CART, product)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: Cart page view

Log when the customer arrives on the cart page. Since cart page views do not have a native product action, use `MPEvent.Builder` with the event name `"view_cart"` and `EventType.Other`. Pass the full cart contents as a custom attribute.

| Field           | Type      | Required | Description                                                                |
| --------------- | --------- | -------- | -------------------------------------------------------------------------- |
| `event_name`    | string    | yes      | Always `"view_cart"`.                                                      |
| `event_type`    | EventType | yes      | Use `MParticle.EventType.Other`.                                           |
| `cartitems`     | array     | yes      | Full cart contents, JSON-stringified before passing as a custom attribute. |
| `cartitemcount` | integer   | yes      | Number of cart lines.                                                      |
| `totalprice`    | decimal   | yes      | Cart total.                                                                |
| `currency`      | string    | yes      | ISO 4217 currency code.                                                    |
| `couponcode`    | string    | no       | Order-level promo, if applied.                                             |

Each entry in the `cartitems` array has the following shape:

| Field             | Type    | Description                                                                                                                                                                                      |
| ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cartitemid`      | string  | Stable partner-side cart-line identifier. Usually equals `productsku` when there is one line per SKU; use a unique value if you allow multiple lines for the same SKU (e.g. gift-wrap variants). |
| `productsku`      | string  | Product SKU / stock identifier.                                                                                                                                                                  |
| `productname`     | string  | Product display name.                                                                                                                                                                            |
| `productcategory` | string  | Product category / taxonomy leaf.                                                                                                                                                                |
| `productbrand`    | string  | Product brand.                                                                                                                                                                                   |
| `productvariant`  | string  | Variant identifier (size, color, etc.).                                                                                                                                                          |
| `itemprice`       | decimal | Per-unit price at event time.                                                                                                                                                                    |
| `unitprice`       | decimal | Per-unit list price pre-discount. Omit if equal to `itemprice`.                                                                                                                                  |
| `quantity`        | integer | Units in this line.                                                                                                                                                                              |
| `currency`        | string  | ISO 4217 code. Omit if matches the top-level currency.                                                                                                                                           |
| `couponcode`      | string  | Coupon applied to this line (if any). Order-level promos belong in `transactionAttributes.Coupon`.                                                                                               |
| `productposition` | integer | 1-indexed rank of the product within a list or search results.                                                                                                                                   |

###### Example cart page view event

```kotlin
val cartItems = """[
{"cartitemid":"SKU-001","productsku":"SKU-001","productname":"Trail Runner v3","itemprice":129.95,"quantity":1},
{"cartitemid":"SKU-002","productsku":"SKU-002","productname":"Cushion Insole","itemprice":19.95,"quantity":2}
]"""

val cartEvent = MPEvent.Builder("view_cart", EventType.Other)
  .customAttributes(mapOf(
      "cartitemcount" to "3",
      "totalprice" to "169.85",
      "currency" to "USD",
      "couponcode" to "SUMMER20",
      "cartitems" to cartItems
  ))
  .build()

MParticle.getInstance()?.logEvent(cartEvent)
```

###### Example cart page view event

```java
String cartItems = "[" +
  "{\"cartitemid\":\"SKU-001\",\"productsku\":\"SKU-001\",\"productname\":\"Trail Runner v3\",\"itemprice\":129.95,\"quantity\":1}," +
  "{\"cartitemid\":\"SKU-002\",\"productsku\":\"SKU-002\",\"productname\":\"Cushion Insole\",\"itemprice\":19.95,\"quantity\":2}" +
  "]";

Map<String, String> attributes = new HashMap<>();
attributes.put("cartitemcount", "3");
attributes.put("totalprice", "169.85");
attributes.put("currency", "USD");
attributes.put("couponcode", "SUMMER20");
attributes.put("cartitems", cartItems);

MPEvent cartEvent = new MPEvent.Builder("view_cart", EventType.Other)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(cartEvent);
}
```

##### Commerce event: Checkout

Log when the customer enters the checkout flow. Send the full set of cart products plus a `TransactionAttributes` summary.

| Field           | Type    | Required | Description                                                                |
| --------------- | ------- | -------- | -------------------------------------------------------------------------- |
| `cartitems`     | array   | yes      | Full cart contents, JSON-stringified before passing as a custom attribute. |
| `totalprice`    | decimal | yes      | Cart total before tax/shipping.                                            |
| `cartitemcount` | integer | yes      | Number of cart lines.                                                      |
| `currency`      | string  | yes      | ISO 4217 currency code.                                                    |
| `couponCode`    | string  | no       | Order-level promo, if applied.                                             |

###### Example Checkout event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product
import com.mparticle.commerce.TransactionAttributes

val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build()
val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build()

val transactionAttributes = TransactionAttributes()
  .setCouponCode("SUMMER20")
  .setRevenue(169.85)

val event = CommerceEvent.Builder(Product.CHECKOUT, product1)
  .products(listOf(product1, product2))
  .transactionAttributes(transactionAttributes)
  .customAttributes(mapOf(
      "currency" to "USD",
      "cartitemcount" to "3",
      "totalprice" to "169.85"
  ))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example Checkout event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;
import com.mparticle.commerce.TransactionAttributes;

Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build();
Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build();

TransactionAttributes transactionAttributes = new TransactionAttributes()
  .setCouponCode("SUMMER20")
  .setRevenue(169.85);

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");
attributes.put("cartitemcount", "3");
attributes.put("totalprice", "169.85");

CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT, product1)
  .products(Arrays.asList(product1, product2))
  .transactionAttributes(transactionAttributes)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: CheckoutOption (shipping)

Log when the customer completes the shipping step. Use the `Product.CHECKOUT_OPTION` action, set `checkoutOptions("shipping")`, and pass the shipping selections as custom attributes.

| Field            | Type    | Required | Description                                                                |
| ---------------- | ------- | -------- | -------------------------------------------------------------------------- |
| `cartitems`      | array   | yes      | Full cart contents, JSON-stringified before passing as a custom attribute. |
| `shippingmethod` | string  | yes      | `standard` / `express` / `next_day`.                                       |
| `zipcode`        | string  | yes      | Shipping ZIP / postcode.                                                   |
| `country`        | string  | yes      | ISO 3166-1 alpha-2 country code.                                           |
| `totalprice`     | decimal | yes      | Cart total.                                                                |
| `currency`       | string  | yes      | ISO 4217 currency code.                                                    |

###### Example CheckoutOption (shipping) event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product

val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build()
val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build()

val event = CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1)
  .products(listOf(product1, product2))
  .checkoutOptions("shipping")
  .customAttributes(mapOf(
      "shippingmethod" to "express",
      "zipcode" to "94103",
      "country" to "US",
      "totalprice" to "169.85",
      "currency" to "USD"
  ))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example CheckoutOption (shipping) event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;

Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build();
Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build();

Map<String, String> attributes = new HashMap<>();
attributes.put("shippingmethod", "express");
attributes.put("zipcode", "94103");
attributes.put("country", "US");
attributes.put("totalprice", "169.85");
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1)
  .products(Arrays.asList(product1, product2))
  .checkoutOptions("shipping")
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: CheckoutOption (payment)

Log when the customer completes the payment step. Use the `Product.CHECKOUT_OPTION` action, set `checkoutOptions("payment")`, and pass the payment method selected as custom attributes.

| Field                    | Type    | Required | Description                                                                |
| ------------------------ | ------- | -------- | -------------------------------------------------------------------------- |
| `cartitems`              | array   | yes      | Full cart contents, JSON-stringified before passing as a custom attribute. |
| `paymenttype`            | string  | yes      | `credit_card` / `paypal` / `apple_pay` / etc.                              |
| `payment_method`         | string  | no       | Specific method when relevant (e.g. card brand).                           |
| `paymentServiceProvider` | string  | no       | PSP identifier (e.g. `stripe`). Must be camelCase.                         |
| `ccbin`                  | string  | no       | First 6-8 digits of the card, if a card was used.                          |
| `totalprice`             | decimal | yes      | Cart total.                                                                |
| `currency`               | string  | yes      | ISO 4217 currency code.                                                    |

###### Example CheckoutOption (payment) event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product

val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build()
val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build()

val event = CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1)
  .products(listOf(product1, product2))
  .checkoutOptions("payment")
  .customAttributes(mapOf(
      "paymenttype" to "credit_card",
      "payment_method" to "visa",
      "paymentServiceProvider" to "stripe",
      "ccbin" to "424242",
      "totalprice" to "169.85",
      "currency" to "USD"
  ))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example CheckoutOption (payment) event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;

Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build();
Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build();

Map<String, String> attributes = new HashMap<>();
attributes.put("paymenttype", "credit_card");
attributes.put("payment_method", "visa");
attributes.put("paymentServiceProvider", "stripe");
attributes.put("ccbin", "424242");
attributes.put("totalprice", "169.85");
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1)
  .products(Arrays.asList(product1, product2))
  .checkoutOptions("payment")
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: Purchase

Log when an order is confirmed. Send the full set of cart products plus a `TransactionAttributes` summary including order ID, revenue, tax, shipping, and any order-level coupon.

| Field           | Type    | Required | Description                                                                                 |
| --------------- | ------- | -------- | ------------------------------------------------------------------------------------------- |
| `cartitems`     | array   | yes      | Full cart contents at time of order, JSON-stringified before passing as a custom attribute. |
| `transactionId` | string  | yes      | Order / transaction identifier.                                                             |
| `totalprice`    | decimal | yes      | Order total (Revenue).                                                                      |
| `tax`           | decimal | yes      | Total tax on the order.                                                                     |
| `shipping`      | decimal | yes      | Shipping cost.                                                                              |
| `currency`      | string  | yes      | ISO 4217 currency code.                                                                     |
| `couponCode`    | string  | no       | Order-level promo, if applied.                                                              |
| `cartitemcount` | integer | no       | Number of cart lines.                                                                       |

###### Example Purchase event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product
import com.mparticle.commerce.TransactionAttributes

val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build()
val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build()

val transactionAttributes = TransactionAttributes("ORDER-10482")
  .setRevenue(169.85)
  .setTax(14.20)
  .setShipping(5.99)
  .setCouponCode("SUMMER20")

val event = CommerceEvent.Builder(Product.PURCHASE, product1)
  .products(listOf(product1, product2))
  .transactionAttributes(transactionAttributes)
  .customAttributes(mapOf("currency" to "USD", "cartitemcount" to "3"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example Purchase event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;
import com.mparticle.commerce.TransactionAttributes;

Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build();
Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build();

TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-10482")
  .setRevenue(169.85)
  .setTax(14.20)
  .setShipping(5.99)
  .setCouponCode("SUMMER20");

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");
attributes.put("cartitemcount", "3");

CommerceEvent event = new CommerceEvent.Builder(Product.PURCHASE, product1)
  .products(Arrays.asList(product1, product2))
  .transactionAttributes(transactionAttributes)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

##### Commerce event: Refund

Log when an order (or a line within it) is refunded. Send only the products being refunded, plus a `TransactionAttributes` object referencing the original order.

| Field           | Type    | Required | Description                               |
| --------------- | ------- | -------- | ----------------------------------------- |
| `productsku`    | string  | yes      | SKU of the refunded line(s).              |
| `quantity`      | integer | yes      | Units refunded.                           |
| `transactionId` | string  | yes      | Original order ID being refunded against. |
| `totalprice`    | decimal | yes      | Refunded amount.                          |
| `currency`      | string  | yes      | ISO 4217 currency code.                   |

###### Example Refund event

```kotlin
import com.mparticle.commerce.CommerceEvent
import com.mparticle.commerce.Product
import com.mparticle.commerce.TransactionAttributes

val refundedProduct = Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build()

val transactionAttributes = TransactionAttributes("ORDER-10482")
  .setRevenue(129.95)

val event = CommerceEvent.Builder(Product.REFUND, refundedProduct)
  .transactionAttributes(transactionAttributes)
  .customAttributes(mapOf("currency" to "USD"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

###### Example Refund event

```java
import com.mparticle.commerce.CommerceEvent;
import com.mparticle.commerce.Product;
import com.mparticle.commerce.TransactionAttributes;

Product refundedProduct = new Product.Builder("Trail Runner v3", "SKU-001", 129.95)
  .quantity(1.0)
  .build();

TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-10482")
  .setRevenue(129.95);

Map<String, String> attributes = new HashMap<>();
attributes.put("currency", "USD");

CommerceEvent event = new CommerceEvent.Builder(Product.REFUND, refundedProduct)
  .transactionAttributes(transactionAttributes)
  .customAttributes(attributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

### Event category: Custom events

Track custom events using `MPEvent.Builder`, passing an event name, event type, and optional custom attributes.

#### Show custom event types

| Type                       | Use for                                                       |
| -------------------------- | ------------------------------------------------------------- |
| `EventType.Navigation`     | User navigation flows and screen transitions within your app. |
| `EventType.Location`       | Location-based interactions and movements.                    |
| `EventType.Search`         | Search queries and search-related actions.                    |
| `EventType.Transaction`    | Financial transactions and purchase-related activity.         |
| `EventType.UserContent`    | User-generated content like reviews, comments, or posts.      |
| `EventType.UserPreference` | User settings, preferences, and customization choices.        |
| `EventType.Social`         | Social media interactions and sharing activities.             |
| `EventType.Other`          | Anything that doesn't fit the categories above.               |

#### Log a custom event

```kotlin
val event = MPEvent.Builder("video_watched", EventType.Navigation)
  .customAttributes(mapOf("category" to "Destination Intro", "title" to "Paris"))
  .build()

MParticle.getInstance()?.logEvent(event)
```

#### Log a custom event

```java
Map<String, String> customAttributes = new HashMap<>();
customAttributes.put("category", "Destination Intro");
customAttributes.put("title", "Paris");

MPEvent event = new MPEvent.Builder("video_watched", EventType.Navigation)
  .customAttributes(customAttributes)
  .build();

if (MParticle.getInstance() != null) {
  MParticle.getInstance().logEvent(event);
}
```

## 6. Show a Placement

Call `selectPlacements` on every payment and confirmation screen you want Rokt to render content on. Include one of the following page identifiers to specify the screen type and whether it's for testing or production:

- `stg.rokt.conf`: A confirmation screen in a staging (or testing) environment.
- `prod.rokt.conf`: A confirmation screen in a production environment.
- `stg.rokt.payments`: A payments screen in a staging (or testing) environment.
- `prod.rokt.payments`: A payments screen in a production environment.

### Placement attributes

Pass these attributes in the `attributes` map of `selectPlacements`. Always supply the most recent value — attributes passed here override any earlier `setUserAttribute` calls.

### Show all placement attributes

| Field                     | Type    | Description                                                                                                                                                                                                                                                                                                                                                             |
| ------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`                   | string  | Customer email (unhashed). Used for identity resolution.                                                                                                                                                                                                                                                                                                                |
| `firstname`               | string  | Customer first name. Used for personalization.                                                                                                                                                                                                                                                                                                                          |
| `lastname`                | string  | Customer last name. Used for personalization.                                                                                                                                                                                                                                                                                                                           |
| `mobile`                  | string  | Customer mobile number in E.164 format. Used for identity resolution.                                                                                                                                                                                                                                                                                                   |
| `confirmationref`         | string  | Order / confirmation reference number. Used for relevance and deduplication.                                                                                                                                                                                                                                                                                            |
| `currency`                | string  | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance.                                                                                                                                                                                                                                                                                          |
| `country`                 | string  | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance.                                                                                                                                                                                                                                                                                                    |
| `language`                | string  | Customer's preferred language (ISO 639-1). Used for relevance.                                                                                                                                                                                                                                                                                                          |
| `totalprice`              | decimal | Total cart value including tax and shipping. Used for relevance.                                                                                                                                                                                                                                                                                                        |
| `amount`                  | decimal | Cart subtotal before tax and shipping. Distinct from `totalprice`. Used for relevance.                                                                                                                                                                                                                                                                                  |
| `cartItems`               | array   | Structured array of cart-line objects. Must be camelCase. Used for relevance.                                                                                                                                                                                                                                                                                           |
| `couponcode`              | string  | Promo code applied to the order, if any. Used for relevance.                                                                                                                                                                                                                                                                                                            |
| `newcustomer`             | boolean | Whether this is a first-time buyer. Used for relevance.                                                                                                                                                                                                                                                                                                                 |
| `customertype`            | string  | `guest` or `logged_in`. Used for relevance.                                                                                                                                                                                                                                                                                                                             |
| `value`                   | decimal | Customer's cumulative purchase value. Used for relevance.                                                                                                                                                                                                                                                                                                               |
| `subscriptionstatus`      | string  | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility.                                                                                                                                                                                                                                                  |
| `customersegment`         | string  | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance.                                                                                                                                                                                                                                                                        |
| `paymenttype`             | string  | Payment method selected (`credit_card`, `paypal`, `apple_pay`, etc.). Used for Pay+ eligibility.                                                                                                                                                                                                                                                                        |
| `paymentServiceProvider`  | string  | Comma-separated list of payment methods accepted on the screen (e.g. `applepay,paypal,cardpayment`). Values must be lowercase with no spaces. See [Payment Service Provider](https://docs.rokt.com/developer-reference/product-deep-dives/pay-plus/partners/data-integration#payment-service-provider) for the full list of accepted values. Used for Pay+ eligibility. |
| `ccbin`                   | string  | Credit card BIN (6-8 digits). Used for relevance.                                                                                                                                                                                                                                                                                                                       |
| `billingaddress1`         | string  | Billing street address. Used for identity resolution and relevance.                                                                                                                                                                                                                                                                                                     |
| `billingaddress2`         | string  | Billing apartment / unit. Used for identity resolution.                                                                                                                                                                                                                                                                                                                 |
| `billingcity`             | string  | Billing city. Used for relevance.                                                                                                                                                                                                                                                                                                                                       |
| `billingstate`            | string  | Billing state or province. Used for relevance.                                                                                                                                                                                                                                                                                                                          |
| `billingzipcode`          | string  | Billing ZIP / postcode. Used for identity resolution and relevance.                                                                                                                                                                                                                                                                                                     |
| `billingname`             | string  | Full cardholder name on the billing address. Used for identity resolution.                                                                                                                                                                                                                                                                                              |
| `shippingmethod`          | string  | Shipping method selected (`standard`, `express`, `next_day`). Used for relevance.                                                                                                                                                                                                                                                                                       |
| `shippingname`            | string  | Full recipient name on the shipping address. Used for relevance.                                                                                                                                                                                                                                                                                                        |
| `shippingaddress1`        | string  | Shipping street address. Used for relevance.                                                                                                                                                                                                                                                                                                                            |
| `shippingcity`            | string  | Shipping city. Used for relevance.                                                                                                                                                                                                                                                                                                                                      |
| `shippingstate`           | string  | Shipping state or province. Used for relevance.                                                                                                                                                                                                                                                                                                                         |
| `shippingzipcode`         | string  | Shipping ZIP or postcode. Used for relevance.                                                                                                                                                                                                                                                                                                                           |
| `shippingcountry`         | string  | Shipping country (ISO 3166-1 alpha-2). Used for relevance.                                                                                                                                                                                                                                                                                                              |
| `partnerpaymentreference` | string  | Non-guessable identifier for the customer's vaulted payment method. Used for Shoppable Ads card forwarding.                                                                                                                                                                                                                                                             |
| `last4digits`             | string  | Last 4 digits of the card used. Used for identity resolution.                                                                                                                                                                                                                                                                                                           |
| `plcc`                    | string  | `"yes"` or `"no"` — whether the customer has a private-label credit card. Used for Pay+ relevance.                                                                                                                                                                                                                                                                      |
| `discountamount`          | decimal | Order-level discount applied. Used for Pay+ relevance.                                                                                                                                                                                                                                                                                                                  |
| `prescreen`               | string  | `"yes"` or `"no"` — whether the customer has pre-qualified for a credit offer. Used for Pay+ relevance.                                                                                                                                                                                                                                                                 |
| `adsexperience`           | string  | Pass `"shoppable"` when targeting a Shoppable Ads experience.                                                                                                                                                                                                                                                                                                           |

### Placement position: Overlay

Overlay placements render on top of your confirmation screen in a Rokt-managed container, requiring no changes to your app's existing layout.

To insert an overlay placement, call `selectPlacements` once the confirmation screen loads:

#### Overlay placement

```kotlin
import com.mparticle.MParticle
import com.mparticle.rokt.RoktConfig

val attributes = mapOf(
  // Identity
  "email" to "j.smith@example.com",
  "firstname" to "Jenny",
  "lastname" to "Smith",
  "mobile" to "+13125551515",

  // Transaction
  "confirmationref" to "54321",
  "currency" to "USD",
  "country" to "US",
  "language" to "en",
  "totalprice" to "149.99",
  "couponcode" to "SUMMER20",

  // Customer context
  "newcustomer" to "false",
  "customertype" to "logged_in",
  "value" to "2340.00",
  "subscriptionstatus" to "active",
  "customersegment" to "vip",

  // Payment (include paymenttype and paymentServiceProvider for Pay+)
  "paymenttype" to "credit_card",
  "paymentServiceProvider" to "cardpayment",
  "ccbin" to "411112",

  // Billing address
  "billingaddress1" to "123 Main St",
  "billingcity" to "Brooklyn",
  "billingstate" to "NY",
  "billingzipcode" to "11201",

  // Shipping
  "shippingmethod" to "express",
  "shippingaddress1" to "175 Varick St",
  "shippingcity" to "New York",
  "shippingstate" to "NY",
  "shippingzipcode" to "10014",
  "shippingcountry" to "US"
)

val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build()

MParticle.getInstance()?.Rokt()?.selectPlacements(
  identifier = "RoktExperience",
  attributes = attributes,
  config = roktConfig
)
```

#### Overlay placement

```java
import com.mparticle.MParticle;
import com.mparticle.rokt.RoktConfig;

Map<String, String> attributes = new HashMap<>();
// Identity
attributes.put("email", "j.smith@example.com");
attributes.put("firstname", "Jenny");
attributes.put("lastname", "Smith");
attributes.put("mobile", "+13125551515");

// Transaction
attributes.put("confirmationref", "54321");
attributes.put("currency", "USD");
attributes.put("country", "US");
attributes.put("language", "en");
attributes.put("totalprice", "149.99");
attributes.put("couponcode", "SUMMER20");

// Customer context
attributes.put("newcustomer", "false");
attributes.put("customertype", "logged_in");
attributes.put("value", "2340.00");
attributes.put("subscriptionstatus", "active");
attributes.put("customersegment", "vip");

// Payment (include paymenttype and paymentServiceProvider for Pay+)
attributes.put("paymenttype", "credit_card");
attributes.put("paymentServiceProvider", "cardpayment");
attributes.put("ccbin", "411112");

// Billing address
attributes.put("billingaddress1", "123 Main St");
attributes.put("billingcity", "Brooklyn");
attributes.put("billingstate", "NY");
attributes.put("billingzipcode", "11201");

// Shipping
attributes.put("shippingmethod", "express");
attributes.put("shippingaddress1", "175 Varick St");
attributes.put("shippingcity", "New York");
attributes.put("shippingstate", "NY");
attributes.put("shippingzipcode", "10014");
attributes.put("shippingcountry", "US");

RoktConfig roktConfig = new RoktConfig.Builder()
  .colorMode(RoktConfig.ColorMode.LIGHT)
  .build();

if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) {
  MParticle.getInstance().Rokt().selectPlacements(
      "RoktExperience",
      attributes,
      null,
      null,
      null,
      roktConfig
  );
}
```

### Placement position: Embedded

Embedded placements render inline at a fixed position in your app that you control (for example, above the payment options on a cart screen). Both Thanks and Pay+ use embedded placements, but Pay+ must use embedded placements.

#### 1. Add RoktEmbeddedView to your layout XML

Add the `RoktEmbeddedView` to your layout XML at the position where you want the placement to render. Set its height to `wrap_content` so the placement can resize dynamically.

#### RoktEmbeddedView in layout XML

```xml
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
  xmlns:android="http://schemas.android.com/apk/res/android"
  xmlns:app="http://schemas.android.com/apk/res-auto"
  android:layout_width="match_parent"
  android:layout_height="match_parent">

  <com.mparticle.rokt.RoktEmbeddedView
      android:id="@+id/roktEmbeddedView"
      android:layout_width="match_parent"
      android:layout_height="wrap_content"
      app:layout_constraintEnd_toEndOf="parent"
      app:layout_constraintStart_toStartOf="parent"
      app:layout_constraintTop_toTopOf="parent" />

</androidx.constraintlayout.widget.ConstraintLayout>
```

#### 2. Wire up callbacks and call selectPlacements

Use the `MpRoktEventCallback` interface to respond to placement events (load, unload, loading indicator state, etc.), then call `selectPlacements` from your activity passing the embedded view, callbacks, and config.

#### Embedded placement with callbacks

```kotlin
import com.mparticle.rokt.RoktConfig
import com.mparticle.rokt.RoktEmbeddedView
import com.mparticle.MpRoktEventCallback
import com.mparticle.UnloadReasons

class ConfirmActivity : Activity() {
  val callbacks = object : MpRoktEventCallback {
      override fun onLoad() {
          // Optional callback for when the Rokt placement loads
      }

      override fun onUnload(reason: UnloadReasons) {
          // Optional callback for when the Rokt placement unloads
      }

      override fun onShouldShowLoadingIndicator() {
          // Optional callback to show a loading indicator
      }

      override fun onShouldHideLoadingIndicator() {
          // Optional callback to hide a loading indicator
      }
  }

  override fun onCreate(savedInstanceState: Bundle?) {
      super.onCreate(savedInstanceState)
      setContentView(R.layout.activity_main)

      val attributes = mapOf(
          "email" to "j.smith@example.com",
          "firstname" to "Jenny",
          "lastname" to "Smith",
          "billingzipcode" to "90210",
          "confirmationref" to "54321"
      )

      val roktWidget = findViewById<RoktEmbeddedView>(R.id.roktEmbeddedView)
      val embeddedViews = mapOf("RoktEmbedded1" to WeakReference(roktWidget))
      val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build()

      MParticle.getInstance()?.Rokt()?.selectPlacements(
          identifier = "RoktExperience",
          attributes = attributes,
          callbacks = callbacks,
          embeddedViews = embeddedViews,
          config = roktConfig
      )
  }
}
```

#### Embedded placement with callbacks

```java
import com.mparticle.rokt.RoktConfig;
import com.mparticle.rokt.RoktEmbeddedView;
import com.mparticle.MpRoktEventCallback;
import com.mparticle.UnloadReasons;

public class ConfirmActivity extends Activity {
  private MpRoktEventCallback callbacks = new MpRoktEventCallback() {
      @Override
      public void onLoad() {
          // Optional callback for when the Rokt placement loads
      }

      @Override
      public void onUnload(UnloadReasons reason) {
          // Optional callback for when the Rokt placement unloads
      }

      @Override
      public void onShouldShowLoadingIndicator() {
          // Optional callback to show a loading indicator
      }

      @Override
      public void onShouldHideLoadingIndicator() {
          // Optional callback to hide a loading indicator
      }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
      super.onCreate(savedInstanceState);
      setContentView(R.layout.activity_main);

      Map<String, String> attributes = new HashMap<>();
      attributes.put("email", "j.smith@example.com");
      attributes.put("firstname", "Jenny");
      attributes.put("lastname", "Smith");
      attributes.put("billingzipcode", "90210");
      attributes.put("confirmationref", "54321");

      RoktEmbeddedView roktWidget = findViewById(R.id.roktEmbeddedView);
      Map<String, WeakReference<RoktEmbeddedView>> embeddedViews = new HashMap<>();
      embeddedViews.put("RoktEmbedded1", new WeakReference<>(roktWidget));

      RoktConfig roktConfig = new RoktConfig.Builder()
          .colorMode(RoktConfig.ColorMode.LIGHT)
          .build();

      if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) {
          MParticle.getInstance().Rokt().selectPlacements(
              "RoktExperience",
              attributes,
              callbacks,
              embeddedViews,
              null,
              roktConfig
          );
      }
  }
}
```

> **Caution: Pay+**
>
> For Pay+ placements, include `paymenttype` and `paymentServiceProvider` in the `selectPlacements` call on each screen. `paymentServiceProvider` communicates what payment methods are available on the payment screen; `paymenttype` communicates what method the user paid with.

### Optional functions

| Function       | Purpose                        |
| -------------- | ------------------------------ |
| `Rokt.close()` | Auto-close overlay placements. |

### Additional configuration

Pass optional parameters such as `RoktConfig` to customize the placement UI (e.g. dark/light mode, caching). Font typefaces can also be supplied as a map of PostScript names to `Typeface` objects.

### selectPlacements with RoktConfig and font typefaces

```kotlin
val fontTypefaces: MutableMap<String, WeakReference<android.graphics.Typeface>> = HashMap()
fontTypefaces["Arial-Bold"] = WeakReference(yourTypefaceObject)

val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build()

MParticle.getInstance()?.Rokt()?.selectPlacements(
  identifier = "RoktExperience",
  attributes = attributes,
  fontTypefaces = fontTypefaces,
  config = roktConfig
)
```

### selectPlacements with RoktConfig and font typefaces

```java
Map<String, WeakReference<android.graphics.Typeface>> fontTypefaces = new HashMap<>();
fontTypefaces.put("Arial-Bold", new WeakReference<>(yourTypefaceObject));

RoktConfig roktConfig = new RoktConfig.Builder()
  .colorMode(RoktConfig.ColorMode.LIGHT)
  .build();

if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) {
  MParticle.getInstance().Rokt().selectPlacements(
      "RoktExperience",
      attributes,
      null,
      null,
      fontTypefaces,
      roktConfig
  );
}
```

> **Note**
>
> If you want to update the identifier `RoktExperience` or embedded identifier `RoktEmbedded1` with a different value, contact your Rokt account manager to ensure Rokt placements are configured consistently.

### Events API

The SDK+ provides placement lifecycle events as a stream through the `MParticle.getInstance()?.Rokt()?.events` API. Use Kotlin Flow to consume events produced by the SDK+.

### Subscribe to Placement Events

```kotlin
import com.mparticle.MParticle

// owner: LifecycleOwner
owner.lifecycleScope.launch {
  owner.lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) {
      MParticle.getInstance()?.Rokt()?.events("RoktExperience")?.collect { roktEvent ->
          // Handle the event
      }
  }
}
```

#### Standard events

### Show all standard events

| Event                   | Description                                                                                                                                                                                                               | Params                                                                                                                                                                               |
| ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| ShowLoadingIndicator    | Triggered before the SDK+ calls the Rokt backend.                                                                                                                                                                         |                                                                                                                                                                                      |
| HideLoadingIndicator    | Triggered when the SDK+ receives a success or failure from the Rokt backend.                                                                                                                                              |                                                                                                                                                                                      |
| PlacementInteractive    | Triggered when a placement has been rendered and is interactable.                                                                                                                                                         | placementId: String                                                                                                                                                                  |
| PlacementReady          | Triggered when a placement is ready to display but has not rendered content yet.                                                                                                                                          | placementId: String                                                                                                                                                                  |
| OfferEngagement         | Triggered when the user engages with the offer.                                                                                                                                                                           | placementId: String                                                                                                                                                                  |
| PositiveEngagement      | Triggered when the user positively engages with the offer.                                                                                                                                                                | placementId: String                                                                                                                                                                  |
| FirstPositiveEngagement | Triggered when the user positively engages with the offer for the first time.                                                                                                                                             | placementId: String, fulfillmentAttributes: FulfillmentAttributes                                                                                                                    |
| OpenUrl                 | Triggered when the user presses a URL that is configured to be sent to the partner app.                                                                                                                                   | placementId: String, url: String                                                                                                                                                     |
| PlacementClosed         | Triggered when a placement is closed by the user.                                                                                                                                                                         | placementId: String                                                                                                                                                                  |
| PlacementCompleted      | Triggered when the offer progression reaches the end and no more offers are available to display. Also triggered when cache is hit but the retrieved placement will not be displayed as it has previously been dismissed. | placementId: String                                                                                                                                                                  |
| PlacementFailure        | Triggered when a placement could not be displayed due to some failure or when no placements are available to show.                                                                                                        | placementId: String (optional)                                                                                                                                                       |
| CartItemInstantPurchase | Triggered when the catalog item purchase is initiated by the user.                                                                                                                                                        | placementId: String, cartItemId: String, catalogItemId: String, currency: String, description: String, linkedProductId: String, totalPrice: Double, quantity: Int, unitPrice: Double |

#### Global events

Use `Rokt.globalEvents()` to subscribe to SDK+-level events that are not tied to a specific placement.

### Subscribe to global events

```kotlin
import com.rokt.roktsdk.RoktEvent

// owner: LifecycleOwner
owner.lifecycleScope.launch {
  Rokt.globalEvents().collect { event ->
      if (event is RoktEvent.InitComplete) {
          // SDK+ initialization is complete
      }
  }
}
```

## 7. Appendix

### Appendix A: App configuration

Applications can pass configuration settings through `RoktConfig` so the Android SDK+ uses your app's custom configuration instead of system defaults.

#### ColorMode object

| Value  | Description                               |
| ------ | ----------------------------------------- |
| LIGHT  | Application is in Light Mode              |
| DARK   | Application is in Dark Mode               |
| SYSTEM | Application defaults to System Color Mode |

#### EdgeToEdgeDisplay

| Value            | Description                                            |
| ---------------- | ------------------------------------------------------ |
| `true` (default) | Application supports Edge to Edge display mode         |
| `false`          | Application does not support Edge to Edge display mode |

```kotlin title="RoktConfig with ColorMode and EdgeToEdgeDisplay"
import com.mparticle.MParticle
import com.mparticle.rokt.RoktConfig

val roktConfig = RoktConfig.Builder()
    .colorMode(RoktConfig.ColorMode.LIGHT)
    .edgeToEdgeDisplay(true)
    .build()

MParticle.getInstance()?.Rokt()?.selectPlacements(
    identifier = "RoktExperience",
    attributes = attributes,
    config = roktConfig
)
```

#### CacheConfig object

| Parameter                | Description                                                                                                                                                              |
| ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `cacheDurationInSeconds` | Optional duration in seconds for which the Rokt SDK+ should cache the experience. Maximum allowed value is 90 minutes; default is 90 minutes if not provided or invalid. |
| `cacheAttributes`        | Optional attributes to be used as cache key. If null, all attributes sent in `selectPlacements` will be used as the cache key.                                           |

```kotlin title="Cache for 1200 seconds"
import com.mparticle.rokt.CacheConfig
import com.mparticle.rokt.RoktConfig

// Cache the experience for 1200 seconds, using email and orderNumber as the cache key.
val roktConfig = RoktConfig.Builder()
    .cacheConfig(CacheConfig(
        cacheDurationInSeconds = 1200,
        cacheAttributes = mapOf("email" to "j.smith@example.com", "orderNumber" to "123")
    ))
    .build()

MParticle.getInstance()?.Rokt()?.selectPlacements(
    identifier = "RoktExperience",
    attributes = attributes,
    config = roktConfig
)
```

### Appendix B: Jetpack Compose support with RoktLayout

For screens implemented using Jetpack Compose, the SDK+ provides the `RoktLayout` composable for a modern, declarative integration of Rokt placements. `RoktLayout` supports Overlay, BottomSheet, and Embedded placement types without manually invoking `selectPlacements`.

```kotlin title="Jetpack Compose placement with RoktLayout"
import com.mparticle.kits.RoktLayout
import com.mparticle.MpRoktEventCallback
import com.mparticle.UnloadReasons

@Composable
fun MainScreen(modifier: Modifier = Modifier) {
    Column(
        modifier = modifier
            .background(Color.LightGray)
            .padding(8.dp),
    ) {
        val attributes = mapOf(
            "email" to "j.smith@example.com",
            "firstname" to "Jenny",
            "lastname" to "Smith",
            "billingzipcode" to "90210",
            "confirmationref" to "54321"
        )
        val callbacks = object : MpRoktEventCallback {
            override fun onLoad() = println("View loaded")
            override fun onUnload(reason: UnloadReasons) = println("View unloaded due to: $reason")
            override fun onShouldShowLoadingIndicator() = println("Show loading indicator")
            override fun onShouldHideLoadingIndicator() = println("Hide loading indicator")
        }
        val roktConfig = RoktConfig.Builder()
            .colorMode(RoktConfig.ColorMode.DARK)
            .cacheConfig(CacheConfig(
                cacheDurationInSeconds = 1200,
                cacheAttributes = mapOf("email" to "j.smith@example.com")
            ))
            .build()

        RoktLayout(
            sdkTriggered = true,
            identifier = "RoktExperience",
            attributes = attributes,
            location = "RoktEmbedded1",
            modifier = Modifier
                .fillMaxWidth()
                .background(Color.Black),
            mpRoktEventCallback = callbacks,
            config = roktConfig
        )
    }
}
```

#### Parameters

| Parameter             | Type                 | Description                                                                 |
| --------------------- | -------------------- | --------------------------------------------------------------------------- |
| `sdkTriggered`        | Boolean              | Controls when the placement should be triggered.                            |
| `identifier`          | String               | The identifier of the Rokt experience (e.g. `"RoktExperience"`).            |
| `location`            | String?              | Optional location name for embedded placements (e.g. `"RoktEmbedded1"`).    |
| `attributes`          | Map\<String, String> | Map of attributes to pass to the placement.                                 |
| `modifier`            | Modifier             | Compose `Modifier` to customize layout, styling, and UI behavior.           |
| `mpRoktEventCallback` | MpRoktEventCallback  | Optional callback to handle placement events (load, unload, loading state). |
| `config`              | RoktConfig?          | Optional configuration for color mode, caching, etc.                        |

### Appendix C: Error handling

The IDSync API is intended to be central to your app's state and is designed to be fast and highly-available. Similar to how your app may prevent users from logging in, logging out, or modifying their state without an internet connection — treat these APIs as gating operations to maintain a consistent user state. The SDK+ will not retry API calls automatically, but provides callback APIs so you can do so according to your business logic.

If you do not implement error handling, you may see data consistency issues at scale.

The SDK+ always returns the HTTP status and body of the underlying HTTP response. For client-side issues (device out of coverage, client-side timeout, invalid identity requests), the SDK+ returns `IdentityApi.UNKNOWN_ERROR` along with an informative error message.

```kotlin title="IDSync error handling"
MParticle.getInstance()?.Identity()?.identify(identifyRequest)
    ?.addFailureListener { identityHttpResponse ->
        if (identityHttpResponse?.httpCode == IdentityApi.UNKNOWN_ERROR) {
            // Device is likely offline — retry the request
        } else if (identityHttpResponse?.httpCode == IdentityApi.THROTTLE_ERROR) {
            // Throttled (429) — retry with backoff
        }
    }
    ?.addSuccessListener { identityApiResult ->
        // Proceed with the identified user
    }
```

```java title="IDSync error handling"
MParticle.getInstance().Identity().identify(identifyRequest)
    .addFailureListener(new TaskFailureListener() {
        @Override
        public void onFailure(IdentityHttpResponse identityHttpResponse) {
            if (identityHttpResponse.getHttpCode() == IdentityApi.UNKNOWN_ERROR) {
                // Device is likely offline — retry the request
            } else if (identityHttpResponse.getHttpCode() == IdentityApi.THROTTLE_ERROR) {
                // Throttled (429) — retry with backoff
            }
        }
    })
    .addSuccessListener(new TaskSuccessListener() {
        @Override
        public void onSuccess(IdentityApiResult identityApiResult) {
            // Proceed with the identified user
        }
    });
```

### Appendix D: Passing session ID from web to native

When a user journey spans both web and native platforms, you can maintain a consistent Rokt session by passing the session ID from the Web SDK+ to the Android SDK+. This is useful for hybrid flows where users complete an action in a WebView (such as a payment page) and return to the native app for confirmation.

#### Getting the session ID from Web SDK+

After calling `selectPlacements`, the session ID is available on the selection context:

```javascript title="Retrieve sessionId from the selection context"
const selection = await launcher.selectPlacements({
    identifier: "checkout",
    attributes: {
        email: "user@example.com",
        // ... other attributes
    }
});

const sessionId = await selection.context.sessionId;
```

> **Note**
>
> The **session ID** is a unique GUID assigned to the current user journey. It is useful for debugging and for correlating a user's activity across your web and native surfaces.

#### Passing to native app via deep link

Pass the session ID to your native app using a deep link:

```javascript title="Deep-link to native app"
const deepLink = `myapp://confirmation?sessionId=${encodeURIComponent(sessionId)}`;
window.location.href = deepLink;
```

#### Setting the session ID

Extract the session ID from the deep link and pass it to the SDK+ before calling `selectPlacements`.

```kotlin title="Handle deep link and set sessionId"
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)

    intent.data?.getQueryParameter("sessionId")?.let { sessionId ->
        MParticle.getInstance()?.Rokt()?.setSessionId(sessionId)
    }

    // Proceed with your confirmation flow
}
```

```java title="Handle deep link and set sessionId"
@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    Uri data = getIntent().getData();
    if (data != null) {
        String sessionId = data.getQueryParameter("sessionId");
        if (sessionId != null) {
            MParticle.getInstance().Rokt().setSessionId(sessionId);
        }
    }

    // Proceed with your confirmation flow
}
```

#### Notes

- Call `setSessionId` before `selectPlacements` to ensure the session is used.
- Empty strings are ignored and will not update the session.
- Always URL-encode the session ID when passing as a query parameter.

## 8. Test Your Integration

To confirm the SDK+ initializes and events log correctly:

### 1. Enable verbose SDK+ logging

Enable verbose SDK+ logging before initialization so you can see what's being sent.

```kotlin title="Enable verbose SDK+ logging"
MParticle.setLogLevel(MParticle.LogLevel.VERBOSE)
```

### 2. Build and run your app

Build and run your app with `environment = MParticle.Environment.Development`.

### 3. Trigger selectPlacements

Trigger `selectPlacements` on the screen where the placement should render and confirm the placement loads.

### 4. Verify events

Verify the events are logged and the `identifyRequest` call succeeds.

### Troubleshooting

If the placement doesn't render or events don't appear, check the Android Logcat for Rokt SDK+ errors. Common issues:

#### Initialization errors

- Confirm `credentials` key and secret match the values from your Rokt account manager.
- Confirm `MParticle.start(options)` runs in `Application.onCreate()` before any `selectPlacements` or `logEvent` call.

#### Identity errors

If the `identifyTask` failure listener fires, see [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#error-handling) for the `IdentityApi` error codes and retry guidance. Without error handling you may see data consistency issues at scale.

#### Placement not rendering

- Confirm the placement `identifier` (e.g. `RoktExperience`) matches what your Rokt account manager configured.
- For embedded placements, confirm the embedded view identifier (e.g. `RoktEmbedded1`) matches the layout configuration.
- Check that the attributes map contains at least `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`.

#### SSL handshake errors when using a proxy

While testing in your development environment, you might hit SSL handshake errors when an HTTP debugging proxy like Charles or Proxyman is running, or when you're behind a corporate network proxy. This is expected: the SDK+ pins its SSL certificate, and a proxy intercepts HTTPS by presenting its own certificate, which fails the pin.

To let the proxy inspect SDK+ traffic, disable pinning in your development build by adding `setPinningDisabledInDevelopment(true)` to the `NetworkOptions` builder in your [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#initialize-android).

### Disable SSL pinning for proxy debugging

```kotlin
val networkOptions = NetworkOptions.builder()
  // Only takes effect in the development environment; production builds stay pinned.
  .setPinningDisabledInDevelopment(true)
  .build()
```

### Disable SSL pinning for proxy debugging

```java
NetworkOptions networkOptions = NetworkOptions.builder()
  // Only takes effect in the development environment; production builds stay pinned.
  .setPinningDisabledInDevelopment(true)
  .build();
```
