# Flutter 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/flutter/](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/)
>
> 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 Flutter 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.

Use the **Target** and **Language** selectors above to choose your deployment platform and the native code examples you want to follow.

## Target: iOS and Android

> **Note**
>
> You'll write a few lines of native code (Swift or Objective-C on iOS, Kotlin or Java on Android) when you initialize the SDK+ in [Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#initialize). Every other step uses Dart through the `mparticle_flutter_sdk` package.

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

The Flutter SDK+ runs on top of the native SDK+. The Dart-side install steps are the same for every target; the native install differs per target platform. Use the **Target** pill above to switch between iOS, Android, and Web.

### 1. Add the mparticle\_flutter\_sdk package

Add the `mparticle_flutter_sdk` package to your Flutter project.

#### Add the Flutter package

```shell
flutter pub add mparticle_flutter_sdk
```

### 2. Pin mparticle\_flutter\_sdk to 2.0 or later

After running `pub add`, your `pubspec.yaml` should pin the package to **2.0 or later** (required for Shoppable Ads).

#### pubspec.yaml

```yaml
dependencies:
  mparticle_flutter_sdk: ^2.0.0
```

### Target: iOS

#### 3. Add the Rokt SDK+ to your iOS app

Rokt SDK+ requires a minimum deployment target of **iOS 15.0**. Use CocoaPods or Swift Package Manager — whichever your project already uses.

##### Install method: CocoaPods

Add the Rokt SDK+ pod to your `ios/Podfile`:

###### ios/Podfile

```ruby
pod 'RoktSDKPlus', '~> 9.2'
```

##### Install method: Swift Package Manager

In Xcode select **File → Add Package Dependencies**, enter the URL below, set the dependency rule to **Up to Next Major Version**, and add the **`RoktSDKPlus`** product to your app target. Or pin in `Package.swift`:

| Package           | Repository URL                                  | Product       |
| ----------------- | ----------------------------------------------- | ------------- |
| Rokt SDK+ for iOS | `https://github.com/ROKT/rokt-sdk-plus-ios.git` | `RoktSDKPlus` |

###### Package.swift

```swift
dependencies: [
  .package(url: "https://github.com/ROKT/rokt-sdk-plus-ios.git", from: "9.2.0"),
]
```

#### 4. Get the SDK handle

Import the package into your Dart code and get an instance of the SDK. This `mpInstance` is the SDK handle the rest of this guide builds on — every Dart API call in later steps (identify, set user attributes, log events, show placements) goes through it.

##### Get the SDK handle

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

MparticleFlutterSdk? mpInstance = await MparticleFlutterSdk.getInstance();
```

### Target: Android

#### 3. Add the Rokt Kit Gradle dependencies

Add the Rokt Kit to your app's Gradle dependencies. Use the inner pill to switch between Kotlin DSL and Groovy.

##### Gradle DSL: Kotlin DSL

###### android/app/build.gradle.kts

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

##### Gradle DSL: Groovy

###### android/app/build.gradle

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

#### 4. Extend FlutterFragmentActivity

Ensure your root Android Activity extends `FlutterFragmentActivity`. This is required by the Rokt SDK+ for proper Activity lifecycle handling.

##### android/app/src/main/kotlin/\<your-package>/MainActivity.kt

```kotlin
import io.flutter.embedding.android.FlutterFragmentActivity

class MainActivity : FlutterFragmentActivity()
```

#### 5. Get the SDK handle

Import the package into your Dart code and get an instance of the SDK. This `mpInstance` is the SDK handle the rest of this guide builds on — every Dart API call in later steps (identify, set user attributes, log events, show placements) goes through it.

##### Get the SDK handle

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

MparticleFlutterSdk? mpInstance = await MparticleFlutterSdk.getInstance();
```

### Target: Web

For Flutter Web, the Rokt SDK+ loads as a JavaScript snippet in your `web/index.html`. The Dart-side `mparticle_flutter_sdk` package proxies calls through to the web SDK+ at runtime — so there's no separate native install step on the web side. The full initialization snippet is shown in [Step 2: Initialize the Rokt SDK+](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#initialize). The JavaScript examples in later steps go in your `web/index.html` alongside the initialization script.

## 2. Initialize the Rokt SDK+

The Flutter SDK+ initializes through the native SDK+ on your target platform. Insert the appropriate initialization snippet on the native side, then the Dart `mparticle_flutter_sdk` package will proxy through to it.

### Target: iOS and Android

When inserting the initialization snippet, you will see customizable fields for:

#### 1. Entering your Rokt key and secret

Set the Rokt key and secret to the values provided by your Rokt account manager.

#### 2. Setting your data environment

Set the SDK+ environment to development while testing to route data to the Development environment, and to production to send live customer activity to Production. (iOS: `.development` / `.production`. Android: `MParticle.Environment.Development` / `MParticle.Environment.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 the custom base URL on your network-options object to your custom subdomain. Routing the Rokt SDK+ through your own domain reduces the risk of ad blockers and browsers blocking ads or data. Omit the network options entirely 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. Once identified, use the success callback (iOS: `onIdentifyComplete`. Android: `addSuccessListener`) to set additional user attributes.

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

### Target: iOS

Insert the following initialization snippet in your `AppDelegate` file. Replace `your-key` and `your-secret` with the values provided by your Rokt team.

#### AppDelegate initialization (Swift)

```swift
import mParticle_Apple_SDK
import RoktPaymentExtension

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]?) -> Bool {
  // Initialize the SDK
  let options = MParticleOptions(key: "your-key",
  secret: "your-secret")
  // 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
  options.environment = .development

  // Enter your custom subdomain if you are using a first-party domain configuration (optional)
  let networkOptions = MPNetworkOptions()
  networkOptions.customBaseURL = URL(string: "https://rkt.example.com")
  options.networkOptions = networkOptions

  // Identify the current user:
  let identifyRequest = MPIdentityApiRequest.withEmptyUser()

  // If you're using an un-hashed email address, set it in 'email'.
  identifyRequest.email = "j.smith@example.com"

  // If you're using a hashed email address, set it in 'other' instead of email
  identifyRequest.setIdentity("sha256 hashed email goes here", identityType: .other)

  // If the user is identified with their email address, set additional user attributes.
  options.identifyRequest = identifyRequest
  options.onIdentifyComplete = {(result: MPIdentityApiResult?, error: Error?) in
      if let user = result?.user {
          user.setUserAttribute("example attribute key", value: "example attribute value")
      }
  }
  MParticle.sharedInstance().start(with: options)

  // Register after MParticle.sharedInstance().start(), before selectShoppableAds
  if let paymentExt = RoktPaymentExtension(
      applePayMerchantId: "merchant.com.yourapp.rokt", // omit if not offering Apple Pay
      urlScheme: "myapp" // omit if not offering Afterpay / Clearpay
  ) {
      MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExt)
  }
  return true
}
```

> **Note**
>
> Configure `stripePublishableKey` in your **mParticle Rokt kit** settings (mParticle dashboard). The kit forwards it to Rokt as `stripeKey` at registration time — you do not pass it in code. At least one of `applePayMerchantId` or `urlScheme` must be provided.

#### AppDelegate initialization (Objective-C)

```objectivec
#import <mParticle_Apple_SDK.h>

- (BOOL)application:(UIApplication *)application
  didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
  // Initialize the SDK
  MParticleOptions *options =
      [MParticleOptions optionsWithKey:@"your-key"
                                secret:@"your-secret"];

  // Specify the data environment with environment:
  // Set it to MPEnvironmentDevelopment if you are still testing your integration.
  // Set it to MPEnvironmentProduction if your integration is ready for production data.
  // The default is MPEnvironmentAutoDetect which attempts to detect the environment automatically
  options.environment = MPEnvironmentDevelopment;

  // Enter your custom subdomain if you are using a first-party domain configuration (optional)
  MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init];
  networkOptions.customBaseURL = [NSURL URLWithString:@"https://rkt.example.com"];
  options.networkOptions = networkOptions;

  // Identify the current user:
  MPIdentityApiRequest *identifyRequest = [MPIdentityApiRequest requestWithEmptyUser];

  // If you're using an un-hashed email address, set it in 'email'.
  identifyRequest.email = @"j.smith@example.com";

  // If you're using a hashed email address, set it in 'other' instead of email
  [identifyRequest setIdentity:@"sha256 hashed email goes here"
                  identityType:MPIdentityOther];

  options.identifyRequest = identifyRequest;

  // If the user is identified with their email address, set additional user attributes.
  options.onIdentifyComplete = ^(MPIdentityApiResult *_Nullable apiResult, NSError *_Nullable error) {
      if (apiResult) {
          [apiResult.user setUserAttribute:@"example attribute key"
                                     value:@"example attribute value"];
      }
  };

  [[MParticle sharedInstance] startWithOptions:options];

  return YES;
}
```

#### 5. Registering the payment extension

Register `RoktPaymentExtension` after `MParticle.sharedInstance().start()` and before `selectShoppableAds` to enable Shoppable Ads payments. Registration is required for all Shoppable Ads placements on iOS — pass `applePayMerchantId` for Apple Pay, `urlScheme` for Afterpay / Clearpay, or both. See [Appendix F: Configure Shoppable Ads payments](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#appendix-f-configure-shoppable-ads-payments-ios-only).

### Target: Android

Configure the Android SDK+ using an `MParticleOptions` object in the `onCreate()` of your `Application` class. The SDK+ must be initialized before any other SDK+ API calls are made.

> **Caution**
>
> Make sure to replace `your-key` and `your-secret` with the key and secret provided by your dedicated Rokt account representative.

#### Application.onCreate initialization (Kotlin)

```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()
      // If you're using an un-hashed email address, set it in 'email'.
      .email("j.smith@example.com")
      .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 representative
          "your-secret" // The secret provided by your Rokt account representative
      ).environment(MParticle.Environment.Development)  // 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
      .networkOptions(networkOptions)
      .build()

      MParticle.start(options)
  }
}
```

#### Application.onCreate initialization (Java)

```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();

      // 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 representative
          "your-secret" // The secret provided by your Rokt account representative
      ).environment(MParticle.Environment.Development) // 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
      .networkOptions(networkOptions)
      .build();
      // Identify the current user:
      IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser()
      // If you do not have the user's email address, you can pass in a null value
      .email("j.smith@example.com").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");
          }
      });

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

### Target: Web

Insert the Rokt SDK+ initialization script in your `web/index.html`. Set `ROKT_DOMAIN` to the subdomain you created during your [first-party domain configuration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/) — this is optional: if you leave the default, requests will be routed through `apps.rokt-api.com`.

> **Caution**
>
> Replace `"YOUR_API_KEY"` with the API key provided by your dedicated Rokt team before deploying. Using a first-party domain ensures the SDK+ calls Rokt's API from your own domain, providing a seamless customer experience and minimizing the risk of blocked content.

#### web/index.html SDK+ initialization

```html
<script type="text/javascript">

// Enter your Rokt API key
const API_KEY = "YOUR_API_KEY";

// Enter your custom subdomain if you are using a first-party domain configuration (optional)
const ROKT_DOMAIN = "https://apps.rokt-api.com";

window.mParticle = {
  config: {
      // Set the data environment:
      // Set isDevelopmentMode to true if you are still testing your integration.
      // Set isDevelopmentMode to false if your integration is ready for production data.
      isDevelopmentMode: true,
      // Identify the current user:
      // If you do not have the user's email address, you can pass in a null value
      identifyRequest: {
          userIdentities: {
              // If you're using an un-hashed email address, set it in 'email' (preferred).
              email: 'j.smith@example.com',
              // If you're using a hashed email address, set it in 'other' instead of 'email'.
              other: 'sha256 hashed email goes here',
              // Customer phone number in E.164 format.
              mobile_number: '+13125551515',
              // Partner's internal customer/account identifier (if the user is logged in).
              customerid: 'cust_10482'
          }
      },
      // If the user is identified with their email address, set additional user attributes.
      identityCallback: function(result) {
          if (result.getUser()) {
              result.getUser().setUserAttribute('attribute_key', 'attribute_value');
          }
      }
  }
};

// Load the SDK
(function(e) { window.mParticle = window.mParticle || {}; window.mParticle.EventType = { Unknown: 0, Navigation: 1, Location: 2, Search: 3, Transaction: 4, UserContent: 5, UserPreference: 6, Social: 7, Other: 8, Media: 9 }; window.mParticle.eCommerce = { Cart: {} }; window.mParticle.Identity = {}; window.mParticle.Rokt = {}; window.mParticle.config = window.mParticle.config || {}; window.mParticle.config.rq = []; window.mParticle.config.snippetVersion = 2.8; window.mParticle.ready = function(e) { window.mParticle.config.rq.push(e); }; ["endSession", "logError", "logBaseEvent", "logEvent", "logForm", "logLink", "logPageView", "setSessionAttribute", "setAppName", "setAppVersion", "setOptOut", "setPosition", "startNewSession", "startTrackingLocation", "stopTrackingLocation"].forEach(function(e) { window.mParticle[e] = function() { var t = Array.prototype.slice.call(arguments); t.unshift(e); window.mParticle.config.rq.push(t); }; }); ["setCurrencyCode", "logCheckout"].forEach(function(e) { window.mParticle.eCommerce[e] = function() { var t = Array.prototype.slice.call(arguments); t.unshift("eCommerce." + e); window.mParticle.config.rq.push(t); }; }); ["identify", "login", "logout", "modify"].forEach(function(e) { window.mParticle.Identity[e] = function() { var t = Array.prototype.slice.call(arguments); t.unshift("Identity." + e); window.mParticle.config.rq.push(t); }; }); ["selectPlacements","hashAttributes","hashSha256","setExtensionData","use","getVersion","terminate","onShoppableAdsReady"].forEach(function(e) { window.mParticle.Rokt[e] = function() { var t = Array.prototype.slice.call(arguments); t.unshift("Rokt." + e); window.mParticle.config.rq.push(t); }; }); var t = window.mParticle.config.isDevelopmentMode ? 1 : 0, n = "?env=" + t, a = window.mParticle.config.dataPlan; if (a) { var o = a.planId, r = a.planVersion; o && (r && (r < 1 || r > 1e3) && (r = null), n += "&plan_id=" + o + (r ? "&plan_version=" + r : "")); } var i = window.mParticle.config.versions, s = []; i && Object.keys(i).forEach(function(e) { s.push(e + "=" + i[e]); }); var c = document.createElement("script"); c.type = "text/javascript"; c.async = !0; window.ROKT_DOMAIN = ROKT_DOMAIN || 'https://apps.rokt-api.com'; mParticle.config.domain = ROKT_DOMAIN.split('//')[1]; c.src = ROKT_DOMAIN + "/js/v2/" + e + "/app.js" + n + "&" + s.join("&"); c.onerror = function() { var u = ["https://apps.","rokt","ecommerce",".com"].join(""); window.ROKT_DOMAIN = u; mParticle.config.domain = u.split("//")[1]; var d = document.createElement("script"); d.type = "text/javascript"; d.async = !0; d.src = u + "/js/v2/" + e + "/app.js" + n + "&" + s.join("&"); var f = document.getElementsByTagName("script")[0]; f.parentNode.insertBefore(d, f); }; var l = document.getElementsByTagName("script")[0]; l.parentNode.insertBefore(c, l); })(API_KEY);
</script>
```

The initialization script includes the following configuration settings:

#### 1. isDevelopmentMode

Set `isDevelopmentMode` to `true` while testing your integration to collect "development" data. When you are ready to go live, set it to `false` to collect and send production data to Rokt.

#### 2. identifyRequest

The SDK+ allows you to identify the current user by including their email address at the time the SDK+ is initialized. Rokt recommends using your customer's raw (unhashed) email address. Include `mobile_number` and `customerid` when available — more signals improve identity resolution.

#### 3. identityCallback

Use the `identityCallback` to set additional user attributes once the user is identified. If you don't have the user's email at initialization, set `email` to `null` — the SDK+ will still initialize, and you can identify the user later via [Step 3: Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#identify).

## 3. Identify the User

The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#initialize) 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

| Identifier      | Type   | Description                                                                                                                                      |
| --------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `email`         | string | Pass the customer's raw, unhashed email address.                                                                                                 |
| `mobile_number` | string | Pass the customer's phone number in E.164 format.                                                                                                |
| `customerId`    | string | Pass your internal customer/account identifier. Send on every screen for logged-in users.                                                        |
| `other`         | string | Pass a SHA-256-hashed email. **Only use when the raw email cannot be provided — do not pass both `email` and `other`.**                          |
| `other2`        | string | Pass a SHA-256-hashed mobile number. **Only use when the raw mobile number cannot be provided — do not pass both `mobile_number` and `other2`.** |

To identify the user:

### 1. Create an identityRequest object

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

### 2. Use the success handler for additional attributes

To set additional user attributes, use the `then` success handler on the identify call (web: `identityCallback`). If the `identityRequest` succeeds, any user attributes you set inside the handler are assigned to the identified user.

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

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

- **`login`:** call when the user logs in or creates an account.
- **`identify`:** call when you obtain the user's email mid-session without a login transition (for example, a guest enters their email at checkout).
- **`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`:

#### Target: iOS

##### Identify Jane Smith (Dart)

```dart
import 'package:mparticle_flutter_sdk/identity/identity_type.dart';
import 'package:mparticle_flutter_sdk/identity/identity_api_result.dart';
import 'package:mparticle_flutter_sdk/identity/identity_api_error_response.dart';

// 1. Create the identityRequest object
var identityRequest = MparticleFlutterSdk.identityRequest;
// Preferred: pass the customer's raw, unhashed email.
// If you can only provide a SHA-256-hashed email, remove the Email line and use IdentityType.Other instead — do not pass both.
identityRequest.setIdentity(identityType: IdentityType.Email, value: 'j.smith@example.com');
identityRequest.setIdentity(identityType: IdentityType.Other, value: 'SHA-256 hashed email');  // only if raw email unavailable
// If you can only provide a SHA-256-hashed mobile number, use IdentityType.Other2 instead of MobileNumber — do not pass both.
identityRequest.setIdentity(identityType: IdentityType.Other2, value: 'SHA-256 hashed mobile number');  // only if raw mobile unavailable
identityRequest.setIdentity(identityType: IdentityType.MobileNumber, value: '+13125551515');
identityRequest.setIdentity(identityType: IdentityType.CustomerId, value: 'cust_10482');

// 2. Optionally set user attributes in the success handler.
void Function(IdentityApiResult) identityCallback = (IdentityApiResult successResponse) {
  successResponse.user.setUserAttribute('firstname', 'Jane');
  successResponse.user.setUserAttribute('lastname', 'Smith');
};

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

#### Target: Android

##### Identify Jane Smith (Dart)

```dart
import 'package:mparticle_flutter_sdk/identity/identity_type.dart';
import 'package:mparticle_flutter_sdk/identity/identity_api_result.dart';
import 'package:mparticle_flutter_sdk/identity/identity_api_error_response.dart';

// 1. Create the identityRequest object
var identityRequest = MparticleFlutterSdk.identityRequest;
// Preferred: pass the customer's raw, unhashed email.
// If you can only provide a SHA-256-hashed email, remove the Email line and use IdentityType.Other instead — do not pass both.
identityRequest.setIdentity(identityType: IdentityType.Email, value: 'j.smith@example.com');
identityRequest.setIdentity(identityType: IdentityType.Other, value: 'SHA-256 hashed email');  // only if raw email unavailable
// If you can only provide a SHA-256-hashed mobile number, use IdentityType.Other2 instead of MobileNumber — do not pass both.
identityRequest.setIdentity(identityType: IdentityType.Other2, value: 'SHA-256 hashed mobile number');  // only if raw mobile unavailable
identityRequest.setIdentity(identityType: IdentityType.MobileNumber, value: '+13125551515');
identityRequest.setIdentity(identityType: IdentityType.CustomerId, value: 'cust_10482');

// 2. Optionally set user attributes in the success handler.
void Function(IdentityApiResult) identityCallback = (IdentityApiResult successResponse) {
  successResponse.user.setUserAttribute('firstname', 'Jane');
  successResponse.user.setUserAttribute('lastname', 'Smith');
};

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

#### Target: Web

##### Identify Jane Smith (web/index.html)

```javascript
// 1. Create the identifyRequest object
const identifyRequest = {
  userIdentities: {
      email: 'j.smith@example.com',
      // If you are passing a hashed email address, set it inside the 'other' field
      other: 'SHA-256 hashed email address',
      mobile_number: '+13125551515',
      customerid: 'cust_10482'
  }
};
// 2. User attributes are set using identityCallback
const identityCallback = function(result) {
  if (result.getUser()) {
      result.getUser().setUserAttribute('firstname', 'Jane');
      result.getUser().setUserAttribute('lastname', 'Smith');
  }
};
// 3. Call one of the following methods that best matches the user's action:
mParticle.Identity.login(identifyRequest, identityCallback); // Call when the user logs in or creates an account
mParticle.Identity.identify(identifyRequest, identityCallback); // Call when you obtain the user's email mid-session, but not during a login
mParticle.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.

### Target: iOS

#### Set user attributes (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

// Retrieve the current user. This will only succeed if you have identified the user during SDK initialization or by calling the identify method.
var currentUser = await mpInstance?.getCurrentUser();

// Once you have successfully set the current user to `currentUser`, you can set user attributes with:
currentUser?.setUserAttribute(key: 'custom-attribute-name', value: '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(key: 'firstname', value: 'John');
currentUser?.setUserAttribute(key: 'lastname', value: 'Doe');
// Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901'
currentUser?.setUserAttribute(key: 'mobile', value: '3125551515');
currentUser?.setUserAttribute(key: 'age', value: '33');
currentUser?.setUserAttribute(key: 'gender', value: 'M');
currentUser?.setUserAttribute(key: 'city', value: 'Brooklyn');
currentUser?.setUserAttribute(key: 'state', value: 'NY');
currentUser?.setUserAttribute(key: 'zip', value: '123456');
currentUser?.setUserAttribute(key: 'dob', value: 'yyyymmdd');
currentUser?.setUserAttribute(key: 'title', value: 'Mr');
currentUser?.setUserAttribute(key: 'language', value: 'en');
currentUser?.setUserAttribute(key: 'lifetime_value', value: '52.25');
currentUser?.setUserAttribute(key: 'predictedltv', value: '136.23');

// You can create a user attribute to contain a list of values
var attributeList = <String>[];
attributeList.add('documentary');
attributeList.add('comedy');
attributeList.add('romance');
attributeList.add('drama');
currentUser?.setUserAttributeArray(key: 'favorite-genres', value: attributeList);

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

### Target: Android

#### Set user attributes (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

// Retrieve the current user. This will only succeed if you have identified the user during SDK initialization or by calling the identify method.
var currentUser = await mpInstance?.getCurrentUser();

// Once you have successfully set the current user to `currentUser`, you can set user attributes with:
currentUser?.setUserAttribute(key: 'custom-attribute-name', value: '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(key: 'firstname', value: 'John');
currentUser?.setUserAttribute(key: 'lastname', value: 'Doe');
// Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901'
currentUser?.setUserAttribute(key: 'mobile', value: '3125551515');
currentUser?.setUserAttribute(key: 'age', value: '33');
currentUser?.setUserAttribute(key: 'gender', value: 'M');
currentUser?.setUserAttribute(key: 'city', value: 'Brooklyn');
currentUser?.setUserAttribute(key: 'state', value: 'NY');
currentUser?.setUserAttribute(key: 'zip', value: '123456');
currentUser?.setUserAttribute(key: 'dob', value: 'yyyymmdd');
currentUser?.setUserAttribute(key: 'title', value: 'Mr');
currentUser?.setUserAttribute(key: 'language', value: 'en');
currentUser?.setUserAttribute(key: 'lifetime_value', value: '52.25');
currentUser?.setUserAttribute(key: 'predictedltv', value: '136.23');

// You can create a user attribute to contain a list of values
var attributeList = <String>[];
attributeList.add('documentary');
attributeList.add('comedy');
attributeList.add('romance');
attributeList.add('drama');
currentUser?.setUserAttributeArray(key: 'favorite-genres', value: attributeList);

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

### Target: Web

#### Set user attributes (web)

```javascript
// To retrieve the current user, call getCurrentUser.
const currentUser = mParticle.Identity.getCurrentUser();

// Once you have successfully set the current user to a const called `currentUser`, you can set user attributes with:
currentUser.setUserAttribute("firstname", "Jane");
currentUser.setUserAttribute("lastname", "Smith");
currentUser.setUserAttribute("mobile", "+13125551515");
currentUser.setUserAttribute("birthyear", 1990);
currentUser.setUserAttribute("gender", "F");

// Address attributes (collect the billing address at checkout)
currentUser.setUserAttribute("billingaddress1", "123 Main St");
currentUser.setUserAttribute("billingaddress2", "Apt 4B");
currentUser.setUserAttribute("billingcity", "Brooklyn");
currentUser.setUserAttribute("billingstate", "NY");
currentUser.setUserAttribute("billingzipcode", "11201");
currentUser.setUserAttribute("country", "US");

// Lifecycle and loyalty attributes
currentUser.setUserAttribute("customertype", "logged_in");
currentUser.setUserAttribute("newcustomer", false);
currentUser.setUserAttribute("loyaltytier", "gold");
currentUser.setUserAttribute("loyaltyid", "LOY-7781");
currentUser.setUserAttribute("lifetime_value", 2340.00);
currentUser.setUserAttribute("customersegment", "vip");

// Marketing attribution
currentUser.setUserAttribute("utmsource", "google");
currentUser.setUserAttribute("utmmedium", "cpc");
currentUser.setUserAttribute("utmcampaign", "spring_sale");

// To set a list attribute, set the value of the attribute to an array of strings. For example:
currentUser.setUserAttribute("favorite_genres", ["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");
```

### User attributes

Set as many of the following as you can collect:

### Show all user attributes

| Attribute            | 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.             |
| `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.                                              |
| `city`               | string  | Billing city. Used for relevance.                                                                                      |
| `state`              | string  | Billing state / province / region. Used for relevance and eligibility.                                                 |
| `zip`                | 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.                                                               |
| `lifetime_value`     | decimal | Customer's cumulative purchase value, as a string (e.g. `"52.25"`). Used for relevance.                                |
| `predictedltv`       | decimal | Predicted total lifetime value, typically from a partner ML model. Distinct from `lifetime_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.                       |
| `utmsource`          | string  | Marketing attribution source. Used for relevance.                                                                      |
| `utmmedium`          | string  | Marketing attribution medium. Used for relevance.                                                                      |
| `utmcampaign`        | string  | Marketing attribution campaign. 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 `mpInstance?.logScreenEvent()` with the name of the screen (e.g. `'homepage'`, `'product_detail_page'`). Include any additional custom attributes in the event's `customAttributes` map.

#### Target: iOS

##### Log a screen view (Dart)

```dart
import 'package:mparticle_flutter_sdk/events/screen_event.dart';

ScreenEvent screenEvent = ScreenEvent(eventName: 'homepage')
..customAttributes = {'custom-attribute': 'custom-value'};
mpInstance?.logScreenEvent(screenEvent);
```

#### Target: Android

##### Log a screen view (Dart)

```dart
import 'package:mparticle_flutter_sdk/events/screen_event.dart';

ScreenEvent screenEvent = ScreenEvent(eventName: 'homepage')
..customAttributes = {'custom-attribute': 'custom-value'};
mpInstance?.logScreenEvent(screenEvent);
```

#### Target: Web

##### Log a page view (web)

```javascript
// Wait for the SDK to be fully loaded before logging the event
window.mParticle.ready(function() {
  mParticle.logPageView(
      "page_view",
      {
          "screenname": location.pathname.split("/").filter(Boolean).pop() || "home",
          "pagetype": "PDP",
          "url": window.location.toString(),
          "title": document.title,
          "sitesection": "mens",
          "referringpage": document.referrer
      }
  );
});
```

### 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 `ProductActionType` 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 | `ProductActionType.ViewDetail`         |
| Product clicked            | `ProductActionType.Click`              |
| Item added to cart         | `ProductActionType.AddToCart`          |
| Item removed from cart     | `ProductActionType.RemoveFromCart`     |
| Item added to wishlist     | `ProductActionType.AddToWishList`      |
| Item removed from wishlist | `ProductActionType.RemoveFromWishlist` |
| Checkout flow initiated    | `ProductActionType.Checkout`           |
| Checkout option selected   | `ProductActionType.CheckoutOption`     |
| Order confirmed            | `ProductActionType.Purchase`           |
| Order refunded             | `ProductActionType.Refund`             |

Tracking a commerce event takes three phases:

#### 1. Define the product

Build a product with name, SKU, and price. Set additional fields like `quantity`, `category`, `brand`, and `variant` directly on the instance. On the Web target, use `mParticle.eCommerce.createProduct` instead — Flutter Web routes through the mParticle Web SDK.

#### Target: iOS

##### Define a product (Dart)

```dart
Product product = Product(
  name: 'Double Room - Econ Rate',
  sku: 'econ-1',
  price: 100.00,
);
product.quantity = 4;
product.category = 'room';
product.brand = 'lodge-o-rama';
product.variant = 'standard';
```

#### Target: Android

##### Define a product (Dart)

```dart
Product product = Product(
  name: 'Double Room - Econ Rate',
  sku: 'econ-1',
  price: 100.00,
);
product.quantity = 4;
product.category = 'room';
product.brand = 'lodge-o-rama';
product.variant = 'standard';
```

#### Target: Web

##### Define a product (web)

```javascript
var product = mParticle.eCommerce.createProduct(
  'Double Room - Econ Rate', // Name
  'econ-1',                  // SKU
  100.00,                    // Price
  4,                         // Quantity
  'standard',                // Variant
  'room',                    // Category
  'lodge-o-rama'             // Brand
);
```

#### 2. Summarize the transaction

Build a `TransactionAttributes` for `Purchase`, `Checkout`, and `CheckoutOption` events. On the Web target, use a plain `transactionAttributes` object literal with PascalCase keys. Order-level coupons belong here, not on individual products.

#### Target: iOS

##### Summarize the transaction (Dart)

```dart
final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionId: 'ORDER-12345',
  revenue: 149.99,
  tax: 12.50,
  shipping: 5.99,
  couponCode: 'SUMMER20',
);
```

#### Target: Android

##### Summarize the transaction (Dart)

```dart
final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionId: 'ORDER-12345',
  revenue: 149.99,
  tax: 12.50,
  shipping: 5.99,
  couponCode: 'SUMMER20',
);
```

#### Target: Web

##### Summarize the transaction (web)

```javascript
const transactionAttributes = {
  Id:       'ORDER-12345',
  Revenue:  149.99,
  Tax:      12.50,
  Shipping: 5.99,
  Coupon:   'SUMMER20'
};
```

#### 3. Log the commerce event

Build a `CommerceEvent` with the product action type and your product(s), attach `transactionAttributes` when applicable, then call `mpInstance?.logCommerceEvent`. On Web, call `mParticle.eCommerce.logProductAction` (or `logImpression` for PLP impressions) instead. Pick the customer action you want to log:

##### Commerce event: PLP impression

Log a product listing (or category) page view as a product impression. Pass every visible product in a single call, and set the impression's name to the list / category name (Rokt uses this as `listname`).

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

###### Target: iOS

**Example PLP impression (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;
product.position = 1; // 1-indexed rank in the list

CommerceEvent event = CommerceEvent.withImpression(
  impressionListName: 'Mens Running Shoes',
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example PLP impression (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;
product.position = 1; // 1-indexed rank in the list

CommerceEvent event = CommerceEvent.withImpression(
  impressionListName: 'Mens Running Shoes',
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example PLP impression (web)**

```javascript
var product = mParticle.eCommerce.createProduct('Product A', 'SKU-001', 29.99, null, null, 'Shoes', 'BrandX', 1);

mParticle.eCommerce.logImpression(
  { Name: 'Mens Running Shoes', Product: [product] },
  { currency: 'USD' }
);
```

##### 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.             |
| `listname`    | string  | no       | Set if the user arrived from a PLP. |

###### Target: iOS

**Example ViewDetail event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.ViewDetail,
  product: product,
);
event.customAttributes = {'currency': 'USD', 'listname': 'PLP-Running'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example ViewDetail event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.ViewDetail,
  product: product,
);
event.customAttributes = {'currency': 'USD', 'listname': 'PLP-Running'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example ViewDetail event (web)**

```javascript
const product = mParticle.eCommerce.createProduct(
'Trail Runner v3',
'SKU-001',
129.95,
1
);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.ViewDetail,
[product],
{ currency: 'USD', listname: 'PLP-Running' },
null,
null
);
```

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

###### Target: iOS

**Example AddToCart event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.AddToCart,
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example AddToCart event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.AddToCart,
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example AddToCart event (web)**

```javascript
const product = mParticle.eCommerce.createProduct(
'Trail Runner v3',
'SKU-001',
129.95,
1
);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.AddToCart,
[product],
{ currency: 'USD' },
null,
null
);
```

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

###### Target: iOS

**Example RemoveFromCart event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1; // units removed

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.RemoveFromCart,
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example RemoveFromCart event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
product.quantity = 1; // units removed

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.RemoveFromCart,
  product: product,
);
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example RemoveFromCart event (web)**

```javascript
const product = mParticle.eCommerce.createProduct(
'Trail Runner v3',
'SKU-001',
129.95,
1
);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.RemoveFromCart,
[product],
{ currency: 'USD' },
null,
null
);
```

##### Commerce event: Cart page view

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

| Field           | Type      | Required | Description                                                |
| --------------- | --------- | -------- | ---------------------------------------------------------- |
| `event_name`    | string    | yes      | Always `"view_cart"`.                                      |
| `event_type`    | EventType | yes      | Use `EventType.Other`.                                     |
| `cartitems`     | array     | yes      | Full cart contents as a real JSON array (don't stringify). |
| `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.                                                                                                                                   |

###### Target: iOS

**Example cart page view event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/event_type.dart';
import 'package:mparticle_flutter_sdk/events/mp_event.dart';

MPEvent event = MPEvent(
  eventName: 'view_cart',
  eventType: EventType.Other)
  ..customAttributes = {
      'cartitemcount': 3,
      'totalprice': 169.85,
      'currency': 'USD',
      'couponcode': 'SUMMER20',
      '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},
      ],
  };
mpInstance?.logEvent(event);
```

###### Target: Android

**Example cart page view event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/event_type.dart';
import 'package:mparticle_flutter_sdk/events/mp_event.dart';

MPEvent event = MPEvent(
  eventName: 'view_cart',
  eventType: EventType.Other)
  ..customAttributes = {
      'cartitemcount': 3,
      'totalprice': 169.85,
      'currency': 'USD',
      'couponcode': 'SUMMER20',
      '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},
      ],
  };
mpInstance?.logEvent(event);
```

###### Target: Web

**Example cart page view event (web)**

```javascript
mParticle.logEvent(
  "view_cart",
  mParticle.EventType.Other,
  {
      cartitemcount: 2,
      totalprice: 149.99,
      currency: "USD",
      couponcode: "SUMMER20",
      cartitems: [
          { cartitemid: "SKU-001", productsku: "SKU-001", productname: "Product A", itemprice: 99.99, quantity: 1 }
      ]
  }
);
```

##### Commerce event: Checkout

Log when the customer enters the checkout flow. Send all cart products and a transaction summary covering the cart total and any order-level coupon.

| Field           | Type    | Required | Description                     |
| --------------- | ------- | -------- | ------------------------------- |
| `cartitems`     | array   | yes      | Full cart contents.             |
| `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.  |

###### Target: iOS

**Example Checkout event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

final TransactionAttributes transactionAttributes = TransactionAttributes(
  revenue: 169.85,
  couponCode: 'SUMMER20',
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Checkout,
  product: product1,
);
event.addProduct(product2);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD', 'cartitemcount': 3};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example Checkout event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

final TransactionAttributes transactionAttributes = TransactionAttributes(
  revenue: 169.85,
  couponCode: 'SUMMER20',
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Checkout,
  product: product1,
);
event.addProduct(product2);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD', 'cartitemcount': 3};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example Checkout event (web)**

```javascript
const product1 = mParticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1);
const product2 = mParticle.eCommerce.createProduct('Cushion Insole',  'SKU-002', 19.95, 2);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.Checkout,
[product1, product2],
{
  currency: 'USD',
  cartitemcount: 3,
  totalprice: 169.85,
  couponCode: 'SUMMER20'
},
null,
null
);
```

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

Log when the customer completes the shipping step. Pass `option: 'shipping'` along with the shipping selections as custom attributes.

| Field            | Type    | Required | Description                          |
| ---------------- | ------- | -------- | ------------------------------------ |
| `cartitems`      | array   | yes      | Full cart contents.                  |
| `option`         | string  | yes      | Always `"shipping"` for this event.  |
| `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.              |

###### Target: iOS

**Example CheckoutOption (shipping) event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.CheckoutOption,
  product: product1,
);
event.addProduct(product2);
event.customAttributes = {
  'option': 'shipping',
  'shippingmethod': 'express',
  'zipcode': '94103',
  'country': 'US',
  'totalprice': 169.85,
  'currency': 'USD',
};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example CheckoutOption (shipping) event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.CheckoutOption,
  product: product1,
);
event.addProduct(product2);
event.customAttributes = {
  'option': 'shipping',
  'shippingmethod': 'express',
  'zipcode': '94103',
  'country': 'US',
  'totalprice': 169.85,
  'currency': 'USD',
};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example CheckoutOption (shipping) event (web)**

```javascript
const product1 = mParticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1);
const product2 = mParticle.eCommerce.createProduct('Cushion Insole',  'SKU-002', 19.95, 2);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.CheckoutOption,
[product1, product2],
{
  option: 'shipping',
  shippingmethod: 'express',
  zipcode: '94103',
  country: 'US',
  totalprice: 169.85,
  currency: 'USD'
},
null,
null
);
```

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

Log when the customer completes the payment step. Pass `option: 'payment'` along with the payment method selected as custom attributes.

| Field                    | Type    | Required | Description                                        |
| ------------------------ | ------- | -------- | -------------------------------------------------- |
| `cartitems`              | array   | yes      | Full cart contents.                                |
| `option`                 | string  | yes      | Always `"payment"` for this event.                 |
| `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.                            |

###### Target: iOS

**Example CheckoutOption (payment) event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.CheckoutOption,
  product: product1,
);
event.addProduct(product2);
event.customAttributes = {
  'option': 'payment',
  'paymenttype': 'credit_card',
  'payment_method': 'visa',
  'paymentServiceProvider': 'stripe',
  'ccbin': '424242',
  'totalprice': 169.85,
  'currency': 'USD',
};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example CheckoutOption (payment) event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.CheckoutOption,
  product: product1,
);
event.addProduct(product2);
event.customAttributes = {
  'option': 'payment',
  'paymenttype': 'credit_card',
  'payment_method': 'visa',
  'paymentServiceProvider': 'stripe',
  'ccbin': '424242',
  'totalprice': 169.85,
  'currency': 'USD',
};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example CheckoutOption (payment) event (web)**

```javascript
const product1 = mParticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1);
const product2 = mParticle.eCommerce.createProduct('Cushion Insole',  'SKU-002', 19.95, 2);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.CheckoutOption,
[product1, product2],
{
  option: 'payment',
  paymenttype: 'credit_card',
  payment_method: 'visa',
  paymentServiceProvider: 'stripe',
  ccbin: '424242',
  totalprice: 169.85,
  currency: 'USD'
},
null,
null
);
```

##### Commerce event: Purchase

Log when an order is confirmed. Send the full cart and a transaction summary identifying the order, revenue, tax, shipping, and any order-level coupon.

| Field           | Type    | Required | Description                          |
| --------------- | ------- | -------- | ------------------------------------ |
| `cartitems`     | array   | yes      | Full cart contents at time of order. |
| `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.                |

###### Target: iOS

**Example Purchase event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionID: 'ORDER-10482',
  revenue: 169.85,
  tax: 14.20,
  shipping: 5.99,
  couponCode: 'SUMMER20',
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Purchase,
  product: product1,
);
event.addProduct(product2);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD', 'cartitemcount': 3};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example Purchase event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product product1 = Product(name: 'Trail Runner v3', sku: 'SKU-001', price: 129.95);
product1.quantity = 1;
Product product2 = Product(name: 'Cushion Insole', sku: 'SKU-002', price: 19.95);
product2.quantity = 2;

final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionID: 'ORDER-10482',
  revenue: 169.85,
  tax: 14.20,
  shipping: 5.99,
  couponCode: 'SUMMER20',
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Purchase,
  product: product1,
);
event.addProduct(product2);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD', 'cartitemcount': 3};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example Purchase event (web)**

```javascript
const product1 = mParticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1);
const product2 = mParticle.eCommerce.createProduct('Cushion Insole',  'SKU-002', 19.95, 2);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.Purchase,
[product1, product2],
{ cartitemcount: 3, currency: 'USD' },
null,
{
  Id:       'ORDER-10482',
  Revenue:  169.85,
  Tax:      14.20,
  Shipping: 5.99,
  Coupon:   'SUMMER20'
}
);
```

##### Commerce event: Refund

Log when an order (or a line within it) is refunded. Send only the products being refunded plus a transaction summary referencing the original order ID.

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

###### Target: iOS

**Example Refund event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product refundedProduct = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
refundedProduct.quantity = 1; // units refunded

final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionID: 'ORDER-10482', // original order id
  revenue: 129.95,               // refunded amount
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Refund,
  product: refundedProduct,
);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Android

**Example Refund event (Dart)**

```dart
import 'package:mparticle_flutter_sdk/events/product.dart';
import 'package:mparticle_flutter_sdk/events/commerce_event.dart';
import 'package:mparticle_flutter_sdk/events/product_action_type.dart';
import 'package:mparticle_flutter_sdk/events/transaction_attributes.dart';

Product refundedProduct = Product(
  name: 'Trail Runner v3',
  sku: 'SKU-001',
  price: 129.95,
);
refundedProduct.quantity = 1; // units refunded

final TransactionAttributes transactionAttributes = TransactionAttributes(
  transactionID: 'ORDER-10482', // original order id
  revenue: 129.95,               // refunded amount
);

CommerceEvent event = CommerceEvent.withProduct(
  productActionType: ProductActionType.Refund,
  product: refundedProduct,
);
event.transactionAttributes = transactionAttributes;
event.customAttributes = {'currency': 'USD'};
mpInstance?.logCommerceEvent(event);
```

###### Target: Web

**Example Refund event (web)**

```javascript
const refundedProduct = mParticle.eCommerce.createProduct(
'Trail Runner v3',
'SKU-001',
129.95,
1 // units refunded
);

mParticle.eCommerce.logProductAction(
mParticle.ProductActionType.Refund,
[refundedProduct],
{ currency: 'USD' },
null,
{
  Id:      'ORDER-10482', // original order id
  Revenue: 129.95          // refunded amount
}
);
```

##### Commerce event: Site search

Log when the customer runs a site search. Site search is a **Web-only** standard event — iOS and Android targets don't have a native equivalent.

###### Target: Web

| Field          | Type      | Required | Description                                |
| -------------- | --------- | -------- | ------------------------------------------ |
| `event_name`   | string    | yes      | Always `"search"`.                         |
| `event_type`   | EventType | yes      | Use `mParticle.EventType.Search`.          |
| `searchstring` | string    | yes      | What the customer typed in the search box. |
| `resultcount`  | integer   | yes      | Number of results returned.                |

**Example site search event (web)**

```javascript
mParticle.logEvent(
  "search",
  mParticle.EventType.Search,
  { searchstring: "blue running shoes", resultcount: 24 }
);
```

###### Target: iOS

> **Note**
>
> Site search is a Web-only standard event. For Flutter iOS targets, log a custom event with `EventType.Search` instead (see the Custom events option in this selector).

###### Target: Android

> **Note**
>
> Site search is a Web-only standard event. For Flutter Android targets, log a custom event with `EventType.Search` instead (see the Custom events option in this selector).

### Event category: Custom events

Track custom events using `MPEvent`, 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.               |

#### Target: iOS

##### Log a custom event (Dart)

```dart
import 'package:mparticle_flutter_sdk/events/event_type.dart';
import 'package:mparticle_flutter_sdk/events/mp_event.dart';

MPEvent event = MPEvent(
  eventName: 'video_watched',
  eventType: EventType.Navigation)
  ..customAttributes = {
      'category': 'Destination Intro',
      'title': 'Paris',
  };
mpInstance?.logEvent(event);
```

#### Target: Android

##### Log a custom event (Dart)

```dart
import 'package:mparticle_flutter_sdk/events/event_type.dart';
import 'package:mparticle_flutter_sdk/events/mp_event.dart';

MPEvent event = MPEvent(
  eventName: 'video_watched',
  eventType: EventType.Navigation)
  ..customAttributes = {
      'category': 'Destination Intro',
      'title': 'Paris',
  };
mpInstance?.logEvent(event);
```

#### Target: Web

##### Log a custom event (web)

```javascript
mParticle.logEvent(
  'event-name',
  mParticle.EventType.Other,
  {
      'custom-attribute-name': 'custom-attribute-value'
  }
);
```

## 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 page in a staging (or testing) environment.
- `prod.rokt.conf`: A confirmation page in a production environment.
- `stg.rokt.payments`: A payments page in a staging (or testing) environment.
- `prod.rokt.payments`: A payments page 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

| Attribute                | 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`                 | string  | Cart subtotal before tax and shipping. Distinct from `totalprice`. Used for relevance and Shoppable Ads.                                         |
| `cartitemcount`          | integer | Number of items in the cart. Used for relevance.                                                                                                 |
| `cartItems`              | array   | Structured array of cart-line objects (Flutter Web only). See Cart items under Commerce Events. 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.                                                                                                      |
| `lifetime_value`         | decimal | Customer's cumulative purchase value (e.g. `"2340.00"`). 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 and Shoppable Ads payment method prioritization. |
| `paymentServiceProvider` | string  | Payment services offered on the page (`apple_pay`, `paypal`, `card`). 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.                                                                              |
| `shippingmethod`         | string  | Shipping method selected (`standard`, `express`, `next_day`). Used for relevance.                                                                |
| `shippingaddress1`       | string  | Shipping street address. Used for relevance and Shoppable Ads order fulfillment.                                                                 |
| `shippingcity`           | string  | Shipping city. Used for relevance and Shoppable Ads order fulfillment.                                                                           |
| `shippingstate`          | string  | Shipping state or province. Used for relevance and Shoppable Ads order fulfillment.                                                              |
| `shippingzipcode`        | string  | Shipping ZIP or postcode. Used for relevance and Shoppable Ads order fulfillment.                                                                |
| `shippingcountry`        | string  | Shipping country (ISO 3166-1 alpha-2). Used for relevance and Shoppable Ads order fulfillment.                                                   |
| `adsexperience`          | string  | Pass `"shoppable"` when deliberately selecting 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:

#### Target: iOS

##### Overlay placement (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

final attributes = {
  // Identity
  'email': 'j.smith@example.com',
  'firstname': 'Jenny',
  'lastname': 'Smith',
  'mobile': '+13125551515',

  // Transaction
  'confirmationref': '54321',
  'currency': 'USD',
  'country': 'US',
  'language': 'en',
  'totalprice': '149.99',
  'cartitemcount': '2',
  'couponcode': 'SUMMER20',

  // Customer context
  'newcustomer': 'false',
  'customertype': 'logged_in',
  'lifetime_value': '2340.00',
  'subscriptionstatus': 'active',
  'customersegment': 'vip',

  // Payment (include paymenttype and paymentServiceProvider for Pay+)
  'paymenttype': 'credit_card',
  'paymentServiceProvider': 'card',
  'ccbin': '411112',

  // Billing address
  'billingaddress1': '123 Main St',
  'billingcity': 'Brooklyn',
  'billingstate': 'NY',
  'billingzipcode': '11201',

  // Shipping
  'shippingmethod': 'express',
  'shippingaddress1': '175 Varick St',
  'shippingcity': 'New York',
  'shippingstate': 'NY',
  'shippingzipcode': '10014',
  'shippingcountry': 'US',
};

final roktConfig = RoktConfig(
  colorMode: ColorMode.light,
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
  roktConfig: roktConfig,
);
```

#### Target: Android

##### Overlay placement (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

final attributes = {
  // Identity
  'email': 'j.smith@example.com',
  'firstname': 'Jenny',
  'lastname': 'Smith',
  'mobile': '+13125551515',

  // Transaction
  'confirmationref': '54321',
  'currency': 'USD',
  'country': 'US',
  'language': 'en',
  'totalprice': '149.99',
  'cartitemcount': '2',
  'couponcode': 'SUMMER20',

  // Customer context
  'newcustomer': 'false',
  'customertype': 'logged_in',
  'lifetime_value': '2340.00',
  'subscriptionstatus': 'active',
  'customersegment': 'vip',

  // Payment (include paymenttype and paymentServiceProvider for Pay+)
  'paymenttype': 'credit_card',
  'paymentServiceProvider': 'card',
  'ccbin': '411112',

  // Billing address
  'billingaddress1': '123 Main St',
  'billingcity': 'Brooklyn',
  'billingstate': 'NY',
  'billingzipcode': '11201',

  // Shipping
  'shippingmethod': 'express',
  'shippingaddress1': '175 Varick St',
  'shippingcity': 'New York',
  'shippingstate': 'NY',
  'shippingzipcode': '10014',
  'shippingcountry': 'US',
};

final roktConfig = RoktConfig(
  colorMode: ColorMode.light,
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
  roktConfig: roktConfig,
);
```

#### Target: Web

##### Overlay placement (web)

```javascript
window.mParticle.ready(async function () {
  const selection = await window.mParticle.Rokt.selectPlacements({
      identifier: "yourPageIdentifier",
      attributes: {
          // Identity
          "email": "j.smith@example.com",
          "firstname": "Jenny",
          "lastname": "Smith",
          "mobile": "+13125551515",

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

          // Customer context
          "newcustomer": false,
          "customertype": "logged_in",
          "lifetime_value": 2340.00,
          "subscriptionstatus": "active",
          "customersegment": "vip",

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

          // Billing address
          "billingaddress1": "123 Main St",
          "billingaddress2": "Apt 4B",
          "billingcity": "Brooklyn",
          "billingstate": "NY",
          "billingzipcode": "11201",

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

          // Cart contents — send a real JSON array, not a stringified blob.
          "cartItems": [
              {
                  "cartitemid": "SKU-001",
                  "productsku": "SKU-001",
                  "productname": "Product A",
                  "productcategory": "Electronics",
                  "productbrand": "BrandX",
                  "itemprice": 99.99,
                  "quantity": 1
              },
              {
                  "cartitemid": "SKU-002",
                  "productsku": "SKU-002",
                  "productname": "Product B",
                  "productcategory": "Accessories",
                  "itemprice": 24.99,
                  "quantity": 2
              }
          ]
      }
  });
});
```

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

Use the `RoktLayout` widget to embed a placement in your Flutter UI. The `onLayoutCreated` callback fires when the widget is created.

#### Target: iOS

##### Embedded placement with RoktLayout (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

final attributes = {
  'email': 'j.smith@example.com',
  'firstname': 'Jenny',
  'lastname': 'Smith',
  'billingzipcode': '90210',
  'confirmationref': '54321',
};

const RoktLayout(
  placeholderName: 'RoktEmbedded1',
  onLayoutCreated: () {
      // Layout created
  }
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
);
```

#### Target: Android

##### Embedded placement with RoktLayout (Dart)

```dart
import 'package:mparticle_flutter_sdk/mparticle_flutter_sdk.dart';

final attributes = {
  'email': 'j.smith@example.com',
  'firstname': 'Jenny',
  'lastname': 'Smith',
  'billingzipcode': '90210',
  'confirmationref': '54321',
};

const RoktLayout(
  placeholderName: 'RoktEmbedded1',
  onLayoutCreated: () {
      // Layout created
  }
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
);
```

#### Target: Web

##### Embedded placeholder div (web/index.html)

```html
<div id="rokt-{container-name}"></div>
```

#### Target: Web

##### Embedded selectPlacements call (web)

```javascript
window.mParticle.ready(async function () {
  const selection = await window.mParticle.Rokt.selectPlacements({
      identifier: "prod.rokt.{page}",
      attributes: {
          // Pass the same attribute set described in Placement Attributes above
      }
  });

  // For SPA navigations, close the placement when the customer leaves the page.
  if (selection) {
      selection.close();
  }
});
```

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

### Placement position: Interstitial (iOS only)

Interstitial placements are rendered between the payment and confirmation screens, allowing customers to purchase additional products. Interstitial placements are used by Shoppable Ads.

> **Note**
>
> Interstitial placements (Shoppable Ads) are supported on **iOS only** in the Flutter SDK+. The Android path does not support interstitial placements. On Web, interstitial placements use the `<rokt-thank-you>` wrapper described below.

> **Caution: Requires SDK+ 2.0**
>
> Shoppable Ads require `mparticle_flutter_sdk` **2.0.0 or later** and **`RoktSDKPlus` \~> 9.2** from [`rokt-sdk-plus-ios`](https://github.com/ROKT/rokt-sdk-plus-ios) on iOS. If you are still on 1.x, follow the [SDK+ 2.0 migration guide](https://docs.rokt.com/developer-reference/sdks/sdk-migration-guide#flutter-sdk-plus-20) before proceeding.

#### Target: iOS

The `RoktPaymentExtension` used below ships with `RoktSDKPlus` (added to your `ios/Podfile` in [Step 1](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#install)) — no separate pod is required.

##### 1. Register the payment extension in AppDelegate.swift

In `ios/Runner/AppDelegate.swift`, register the payment extension after SDK+ initialization:

###### ios/Runner/AppDelegate.swift (Shoppable Ads)

```swift
import mParticle_Apple_SDK
import RoktPaymentExtension

// In application(_:didFinishLaunchingWithOptions:), after MParticle.sharedInstance().start(with: options)
if let paymentExt = RoktPaymentExtension(
  applePayMerchantId: "merchant.com.yourapp.rokt", // omit if not offering Apple Pay
  urlScheme: "myapp" // omit if not offering Afterpay / Clearpay
) {
  MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExt)
}
```

> **Note**
>
> Configure `stripePublishableKey` in your **mParticle Rokt kit** settings; the kit forwards it to Rokt automatically. In code, provide only the Apple Pay merchant ID and/or `urlScheme`. At least one of `applePayMerchantId` or `urlScheme` must be provided. Apple Pay is optional — Shoppable Ads also supports built-in PayPal and card forwarding without it.

> **Caution**
>
> You must call `registerPaymentExtension` **after** SDK+ initialization and **before** calling `selectShoppableAds` from your Dart code. If no payment extension is registered, `selectShoppableAds` will fire a `PlacementFailure` event.

##### 2. Forward redirect URLs (Afterpay, Clearpay, PayPal)

If you offer Afterpay, Clearpay, or PayPal, those methods redirect back to your app after authentication. Forward incoming URLs to Rokt from your **native iOS** `SceneDelegate` (or `AppDelegate`), in addition to any existing mParticle URL handling. Skip this step if you only offer Apple Pay or card forwarding.

###### ios/Runner/SceneDelegate.swift (Shoppable Ads)

```swift
func scene(_ scene: UIScene, openURLContexts URLContexts: Set<UIOpenURLContext>) {
  for urlContext in URLContexts {
      if MParticle.sharedInstance().rokt.handleURLCallback(with: urlContext.url) {
          return
      }
      MParticle.sharedInstance().handleURLContext(urlContext)
  }
}
```

Afterpay / Clearpay also require the matching URL scheme registered under `CFBundleURLTypes` in `Info.plist` and passed as `urlScheme` when creating `RoktPaymentExtension` (see the previous step).

##### 3. Call selectShoppableAds from your Dart code

Call `selectShoppableAds` once all required attributes are available. Shoppable Ads always display as an overlay — no embedded views are needed.

###### Interstitial placement — selectShoppableAds (Dart, iOS only)

```dart
final attributes = {
  'email': 'j.smith@example.com',
  'firstname': 'Jenny',
  'lastname': 'Smith',
  'confirmationref': 'ORD-12345',
  'amount': '52.25',
  'currency': 'USD',
  'paymenttype': 'visa',
  'shippingaddress1': '123 Main St',
  'shippingcity': 'New York',
  'shippingstate': 'NY',
  'shippingzipcode': '10001',
  'shippingcountry': 'US',
};

mpInstance?.rokt.selectShoppableAds(
  identifier: 'ConfirmationPage',
  attributes: attributes,
);
```

Shoppable Ads events are delivered via the `MPRoktEvents` EventChannel — see the [Events API](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#events-api) section below.

#### Target: Web

##### Interstitial wrapper (web/index.html)

```html
<body>
  <!-- Your header -->
  <rokt-thank-you id="rokt-thank-you">
      <!-- Your confirmation page content -->
  </rokt-thank-you>
  <!-- Your footer -->
</body>
```

#### Target: Web

##### Interstitial selectPlacements call (web)

```javascript
window.mParticle.ready(async function () {
  await window.mParticle.Rokt.selectPlacements({
      identifier: "prod.rokt.conf",
      attributes: {
          // Pass the same attribute set described in Placement Attributes above
      }
  });
});
```

Some features require extensions. Enable them by calling `mParticle.Rokt.use()` before `selectPlacements()`. For example, to display upsell placements on a Thank You page, enable the `ThankYouPageJourney` extension first:

##### Enable the ThankYouPageJourney extension

```javascript
window.mParticle.ready(async function() {
// Enable the necessary extension prior to selecting placements
await window.mParticle.Rokt.use("ThankYouPageJourney");

const selection = await mParticle.Rokt.selectPlacements({
  identifier: "yourPageIdentifier",
  attributes: {
    "email": "j.smith@example.com"
    // Any additional user attributes you want to pass to Rokt
  },
});
})
```

### 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 file paths can also be supplied as a map of PostScript names to asset paths.

### Target: iOS

#### selectPlacements with RoktConfig and font typefaces (Dart)

```dart
// If you want to use custom fonts for your placement, create a fontTypefaces map
final fontTypefaces = {'Arial-Bold': 'fonts/Arial-Bold.ttf'};

final roktConfig = RoktConfig(
  colorMode: ColorMode.light,
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
  fontFilePathMap: fontTypefaces,
  roktConfig: roktConfig,
);
```

### Target: Android

#### selectPlacements with RoktConfig and font typefaces (Dart)

```dart
// If you want to use custom fonts for your placement, create a fontTypefaces map
final fontTypefaces = {'Arial-Bold': 'fonts/Arial-Bold.ttf'};

final roktConfig = RoktConfig(
  colorMode: ColorMode.light,
);

mpInstance?.rokt.selectPlacements(
  identifier: 'RoktExperience',
  attributes: attributes,
  fontFilePathMap: fontTypefaces,
  roktConfig: 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

On iOS and Android, the SDK+ provides placement lifecycle events as a stream through the `MPRoktEvents` EventChannel. On Web, subscribe to events directly on the selection object returned by `selectPlacements`.

### Target: iOS

#### Subscribe to placement events (Dart)

```dart
final EventChannel roktEventChannel = EventChannel('MPRoktEvents');
roktEventChannel.receiveBroadcastStream().listen((dynamic event) {
  debugPrint('rokt_event: $event');
});
```

### Target: Android

#### Subscribe to placement events (Dart)

```dart
final EventChannel roktEventChannel = EventChannel('MPRoktEvents');
roktEventChannel.receiveBroadcastStream().listen((dynamic event) {
  debugPrint('rokt_event: $event');
});
```

### Target: Web

#### Subscribe to placement events (web)

```javascript
window.mParticle.ready(async function () {
  const selection = await window.mParticle.Rokt.selectPlacements({
      // add attributes
  });

  // Listen for when the placement becomes interactive/ready to display
  selection.on('PLACEMENT_INTERACTIVE').subscribe(() => {
      // Logic to run after Placement has become interactive
  });

  // Listen for when user engages positively or negatively with an offer
  selection.on('OFFER_ENGAGEMENT').subscribe(function () {
      // Logic to run after offer is engaged with
  });
});
```

#### 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.                                                                                                                                                         | identifier: String                                                                                  |
| PlacementReady                   | Triggered when a placement is ready to display but has not rendered content yet.                                                                                                                                          | identifier: String                                                                                  |
| OfferEngagement                  | Triggered when the user engages with the offer.                                                                                                                                                                           | identifier: String                                                                                  |
| PositiveEngagement               | Triggered when the user positively engages with the offer.                                                                                                                                                                | identifier: String                                                                                  |
| FirstPositiveEngagement          | Triggered when the user positively engages with the offer for the first time.                                                                                                                                             | identifier: String, fulfillmentAttributes: FulfillmentAttributes                                    |
| OpenUrl                          | Triggered when the user presses a URL that is configured to be sent to the partner app.                                                                                                                                   | identifier: String, url: String                                                                     |
| PlacementClosed                  | Triggered when a placement is closed by the user.                                                                                                                                                                         | identifier: 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. | identifier: String                                                                                  |
| PlacementFailure                 | Triggered when a placement could not be displayed due to some failure or when no placements are available to show.                                                                                                        | identifier: String (optional)                                                                       |
| EmbeddedSizeChanged              | Triggered when an embedded placement's height changes.                                                                                                                                                                    | identifier: String, selectedHeight: Double                                                          |
| CartItemInstantPurchase          | Triggered when the catalog item purchase is initiated by the user.                                                                                                                                                        | identifier: String, catalogItemId: String, cartItemId: String, totalPrice: String, currency: String |
| CartItemInstantPurchaseInitiated | Purchase flow started — user tapped "Buy" (Shoppable Ads, iOS only).                                                                                                                                                      | identifier: String, catalogItemId: String, cartItemId: String                                       |
| CartItemInstantPurchaseFailure   | Purchase failed (Shoppable Ads, iOS only).                                                                                                                                                                                | identifier: String, catalogItemId: String, cartItemId: String, error: String                        |
| CartItemDevicePay                | Apple Pay / device payment triggered (Shoppable Ads, iOS only).                                                                                                                                                           | identifier: String, catalogItemId: String, cartItemId: String, paymentProvider: String              |
| InstantPurchaseDismissal         | User dismissed the purchase overlay (Shoppable Ads, iOS only).                                                                                                                                                            | identifier: String                                                                                  |

## 7. Appendix

### Appendix A: App configuration

Applications can pass configuration settings through `RoktConfig` so the 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 |

```dart title="RoktConfig with ColorMode"
final roktConfig = RoktConfig(
    colorMode: ColorMode.light,
);

mpInstance?.rokt.selectPlacements(
    identifier: 'RoktExperience',
    attributes: attributes,
    roktConfig: roktConfig,
);
```

#### EdgeToEdgeDisplay (Android only)

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

When building the native `RoktConfig` on Android, call `edgeToEdgeDisplay(true)` on the `RoktConfig.Builder` to enable edge-to-edge mode:

```kotlin title="RoktConfig with EdgeToEdgeDisplay (Android native)"
import com.mparticle.MParticle
import com.mparticle.rokt.RoktConfig

val roktConfig = RoktConfig.Builder()
    .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.                                           |

```dart title="Cache for 1200 seconds"
// Cache the experience for 1200 seconds, using email and orderNumber as the cache key.
final roktConfig = RoktConfig(
    cacheConfig: CacheConfig(
        cacheDurationInSeconds: 1200,
        cacheAttributes: {'email': 'j.smith@example.com', 'orderNumber': '123'},
    ),
);

mpInstance?.rokt.selectPlacements(
    identifier: 'RoktExperience',
    attributes: attributes,
    roktConfig: roktConfig,
);
```

### Appendix B: SwiftUI support with MPRoktLayout (iOS only)

If your app is primarily written in SwiftUI, the `MPRoktLayout` component provides a more modern, declarative approach to integrating Rokt placements in your iOS app.

The `MPRoktLayout` class provides a SwiftUI-compatible way to display Rokt placements without manually calling `selectPlacements`, supporting both overlay and embedded placement types.

```swift title="SwiftUI placement with MPRoktLayout"
import SwiftUI
import mParticle_Apple_SDK
import mParticle_Rokt_Swift

struct OrderConfirmationView: View {
    let attributes = [
        "email": "test@gmail.com",
        "firstname": "Jenny",
        "lastname": "Smith",
        "billingzipcode": "07762",
        "confirmationref": "54321"
    ]

    @State private var sdkTriggered = true

    var body: some View {
        VStack(alignment: .leading) {
            // Other UI components
            Text("Order Confirmation")
                .font(.title)

            // Rokt placement using SwiftUI
            MPRoktLayout(
                sdkTriggered: $sdkTriggered,
                identifier: "RoktExperience",
                locationName: "RoktEmbedded1", // For embedded placements
                attributes: attributes,
                config: roktConfig, // Optional RoktConfig
                onEvent: { roktEvent in
                    // Optional: Handle different event types see above
                }
            ).roktLayout
        }
        .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
    }
}
```

| Parameter      | Type                   | Description                                                               |
| -------------- | ---------------------- | ------------------------------------------------------------------------- |
| `sdkTriggered` | Bool                   | Controls when the placement should be triggered.                          |
| `identifier`   | String                 | The Rokt placement identifier (e.g., `"RoktExperience"`).                 |
| `locationName` | String?                | Optional location name for embedded placements (e.g., `"RoktEmbedded1"`). |
| `attributes`   | \[String: String]      | Dictionary of attributes to pass to the placement.                        |
| `config`       | RoktConfig?            | Optional configuration object for color mode, caching, etc.               |
| `onEvent`      | ((RoktEvent) -> Void)? | Optional callback to handle all placement events.                         |

### Appendix C: Jetpack Compose support with RoktLayout (Android only)

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",
            "mobile" to "(323) 867-5309",
            "postcode" to "90210",
            "country" to "US"
        )
        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 = "Location1",
            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. `"Location1"`).        |
| `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 D: 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.

```dart title="IDSync error handling"
import 'package:mparticle_flutter_sdk/identity/identity_api_result.dart';
import 'package:mparticle_flutter_sdk/identity/identity_api_error_response.dart';

mpInstance?.identity
    .identify(identityRequest: identityRequest)
    .then(
        (IdentityApiResult successResponse) {
            // Proceed with the identified user
        },
        onError: (error) {
            var failureResponse = error as IdentityAPIErrorResponse;
            // Inspect failureResponse.statusCode to determine the error type:
            // - Check for network errors (device offline) and retry the request
            // - Check for throttle errors (429) and retry with backoff
            print('Identity error: $failureResponse');
        }
    );
```

#### Client-side error codes (iOS)

The `MPIdentityErrorResponseCode` enum defines the following client-side codes:

| MPIdentityErrorResponseCode                     | Description                                                                                       |
| ----------------------------------------------- | ------------------------------------------------------------------------------------------------- |
| `MPIdentityErrorResponseCodeRequestInProgress`  | The IDSync HTTP request was not performed as there is already an IDSync HTTP request in progress. |
| `MPIdentityErrorResponseCodeClientSideTimeout`  | The IDSync HTTP request failed due to a TCP connection timeout.                                   |
| `MPIdentityErrorResponseCodeClientNoConnection` | The IDSync HTTP request failed due to lack of network coverage.                                   |
| `MPIdentityErrorResponseCodeSSLError`           | The IDSync HTTP request failed due to an SSL configuration issue.                                 |
| `MPIdentityErrorResponseCodeOptOut`             | The IDSync HTTP request was not performed due to the SDK+ being disabled due to opt-out.          |
| `MPIdentityErrorResponseCodeUnknown`            | The IDSync HTTP request failed due to an unknown error.                                           |

#### Android error codes

The Android SDK+ returns `IdentityApi.UNKNOWN_ERROR` for client-side issues including device out of coverage, client-side timeout, or invalid identity requests. Check for `THROTTLE_ERROR` (HTTP 429) and retry with backoff when encountered.

#### HTTP status codes

| Value | Description                                                                                                                  |
| ----- | ---------------------------------------------------------------------------------------------------------------------------- |
| 400   | The IDSync HTTP call failed due to an invalid request body.                                                                  |
| 401   | The IDSync HTTP call failed due to an authentication error. Verify that your API key is correct.                             |
| 429   | The IDSync HTTP call was throttled and should be retried.                                                                    |
| 5xx   | The IDSync HTTP call failed due to a Rokt server-side issue. Contact your account representative for additional information. |

### Appendix E: 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 Flutter 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 on iOS

Extract the session ID from the deep link and pass it to the SDK+ before calling `selectPlacements`. Add this to your `AppDelegate.swift`:

```swift title="Handle deep link and set sessionId (iOS)"
func handleDeepLink(url: URL) {
    let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
    if let sessionId = components?.queryItems?.first(where: { $0.name == "sessionId" })?.value {
        MParticle.sharedInstance().rokt.setSessionId(sessionId: sessionId)
    }
    // Proceed with your confirmation flow
}
```

#### Setting the session ID on Android

Extract the session ID from the deep link and pass it to the SDK+ before calling `selectPlacements`. Add this to your `MainActivity`:

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

    intent.data?.getQueryParameter("sessionId")?.let { sessionId ->
        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.

### Target: iOS

### Appendix F: Configure Shoppable Ads payments (iOS only)

If you are not using Shoppable Ads, skip this step.

Shoppable Ads on iOS require a registered `RoktPaymentExtension` (native iOS) and support multiple payment methods. Registering the extension is mandatory for every Shoppable Ads placement, even if you offer only redirect-based methods. The registration and redirect-forwarding snippets are in the [Show a Placement](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#placements) step's Shoppable Ads (interstitial) target.

| Method                                                                                                                                            | iOS setup                                                                                                     |
| ------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------- |
| [Apple Pay](https://docs.rokt.com/developer-reference/product-deep-dives/shoppable-ads/payment-methods/apple-pay#ios-setup)                       | Apple Pay merchant ID passed as `applePayMerchantId` on `RoktPaymentExtension`. Optional.                     |
| PayPal                                                                                                                                            | Built into the Rokt SDK+ — no extra extension config. Requires redirect-URL forwarding.                       |
| Afterpay / Clearpay                                                                                                                               | Custom URL scheme in `Info.plist` + matching `urlScheme` on `RoktPaymentExtension` + redirect-URL forwarding. |
| [Card Forwarding](https://docs.rokt.com/developer-reference/product-deep-dives/shoppable-ads/payment-methods/card-forwarding#payment-sharing-api) | Partner Payment Sharing API + `partnerpaymentreference` / `last4digits` attributes on `selectShoppableAds`.   |

> **Note**
>
> Apple Pay is **optional** — Shoppable Ads also supports built-in PayPal and card forwarding without an Apple Pay merchant ID. At least one of `applePayMerchantId` or `urlScheme` must be provided when creating the extension. Configure `stripePublishableKey` in your **mParticle Rokt kit** settings; the kit forwards it to Rokt automatically.

To offer Apple Pay, create an Apple Pay merchant ID, configure your Xcode project, and generate a Payment Processing Certificate by following [Apple Pay — iOS setup](https://docs.rokt.com/developer-reference/product-deep-dives/shoppable-ads/payment-methods/apple-pay#ios-setup), then pass the merchant ID as `applePayMerchantId`.

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

```dart title="Enable verbose SDK+ logging"
// Enable mParticle debug logging at the Dart level
MparticleFlutterSdk.setLogLevel(LogLevel.verbose);
```

### 2. Build and run against a development environment

Build and run your app with the development environment set on the native side:

- **iOS:** `environment = .development` (Swift) or `MPEnvironmentDevelopment` (Objective-C)
- **Android:** `MParticle.Environment.Development`
- **Web:** `isDevelopmentMode: true`

### 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 identify call succeeds.

- **iOS:** Check the Xcode console for Rokt SDK+ log output.
- **Android:** Check Android Studio's Logcat for Rokt SDK+ log output.
- **Web:** Open developer tools, go to the **Network** tab, filter by `experiences`, and confirm a `/experiences` request with status 200 fires.

### Troubleshooting

If the placement doesn't render or events don't appear, check your platform's debug console for Rokt SDK+ errors. Common issues:

#### Initialization errors

- Confirm the `key` and `secret` (iOS/Android) or `API_KEY` (Web) match the values from your Rokt account manager.
- Confirm native SDK+ initialization runs before any `selectPlacements` or `logEvent` call from your Dart code.
- On Android, confirm your root Activity extends `FlutterFragmentActivity`.
- For Shoppable Ads on iOS, confirm `RoktPaymentExtension` is registered after SDK+ initialization and before `selectShoppableAds`.

#### Identity errors

If the identify call's `onError` handler fires, inspect the `IdentityAPIErrorResponse` for the status code and retry the request according to your business logic. 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`.
