# MAUI 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/maui/](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/)
>
> 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 MAUI SDK+. The SDK+ passes user and transaction data to Rokt on configured screens so Rokt can render relevant experiences, such as offers on confirmation screens.

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

Add the SDK+ packages to your project:

### Add SDK+ packages

```bash
dotnet add package mParticle.MAUI
dotnet add package mParticle.MAUI.Kits.Rokt
dotnet add package mParticle.MAUI.Kits.Rokt.Payments
```

`mParticle.MAUI.Kits.Rokt.Payments` already includes the core Rokt kit transitively.

> **Note**
>
> For Android you also need to ensure your activity extends `MauiAppCompatActivity`.

## 2. Initialize the Rokt SDK+

Insert the following initialization snippet in your application startup. The SDK+ must be initialized before any other SDK+ API calls. Replace `your-key` and `your-secret` with the key and secret provided by your Rokt team.

### SDK+ initialization

```csharp
using mParticle.MAUI;

string key = "";
string secret = "";
#if __ANDROID__
  key = "your-key";
  secret = "your-secret";
#elif __IOS__
  key = "your-key";
  secret = "your-secret";
#endif

// Initialize the SDK+
var options = new MParticleOptions()
{
  ApiKey = key,
  ApiSecret = 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 = mParticle.MAUI.Environment.Development;

// Enter your custom subdomain if you are using a first-party domain configuration (optional)
options.NetworkOptions = new NetworkOptions()
{
  CustomBaseUrl = "https://rkt.example.com"
};

// Identify the current user:
var identifyRequest = new IdentityApiRequest();
identifyRequest.UserIdentities = new Dictionary<UserIdentity, string>()
{
#if __ANDROID__
  // Preferred: pass the customer's raw, unhashed email.
  // If you can only provide a SHA-256-hashed email, use UserIdentity.Other instead — do not pass both.
  { UserIdentity.Email, "j.smith@example.com" },
  { UserIdentity.Other, "SHA-256 hashed email" },  // only if raw email unavailable
  // If you can only provide a SHA-256-hashed mobile number, use Other2 instead of MobileNumber — do not pass both.
  { UserIdentity.Other2, "SHA-256 hashed mobile number" },  // only if raw mobile unavailable
  { UserIdentity.MobileNumber, "+13125551515" },
  { UserIdentity.CustomerId, "cust_10482" }
#elif __IOS__
  // Preferred: pass the customer's raw, unhashed email.
  // If you can only provide a SHA-256-hashed email, use UserIdentity.Other instead — do not pass both.
  { UserIdentity.Email, "j.smith@example.com" },
  { UserIdentity.Other, "SHA-256 hashed email" },  // only if raw email unavailable
  // Customer phone number in E.164 format.
  { UserIdentity.MobileNumber, "+13125551515" },
  // If you can only provide a SHA-256-hashed mobile number, use Other2 instead of MobileNumber — do not pass both.
  { UserIdentity.Other2, "SHA-256 hashed mobile number" },  // only if raw mobile unavailable
  { UserIdentity.CustomerId, "cust_10482" }
#endif
};

// If the user is identified with their email address, set additional user attributes.
options.IdentifyRequest = identifyRequest;

OnUserIdentified onIdentifyComplete = newUser =>
{
  if (newUser != null)
  {
      newUser.SetUserAttribute("example attribute key", "example attribute value");
  }
};
options.IdentityStateListener = onIdentifyComplete;

// Register the Rokt kit with mParticle before initialization
RoktKit.Register();

MParticle.Instance.Initialize(options);
```

When inserting the initialization snippet into your application startup, you will see customizable fields for:

### 1. Entering your Rokt key and secret

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

### 2. Setting your data environment

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

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

Follow the instructions in [First-Party Domain Configuration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/), and set `options.NetworkOptions.CustomBaseUrl` to your custom subdomain before calling `MParticle.Instance.Initialize(options)`. Omit `options.NetworkOptions` to send traffic to Rokt's default endpoints.

### 4. Identifying your user and setting attributes

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

#### IdentityStateListener

```csharp
OnUserIdentified onIdentifyComplete = newUser =>
{
  if (newUser != null)
  {
      newUser.SetUserAttribute("example attribute key", "example attribute value");
  }
};
options.IdentityStateListener = onIdentifyComplete;
```

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

## 3. Identify the User

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

### Supported user identifiers

### Show supported user identifiers

| Field          | Type   | Description                                                                                                                                                      |
| -------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`        | string | Pass the customer's raw, unhashed email address.                                                                                                                 |
| `mobile`       | 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`.** (Android path only.)                     |
| `other2`       | string | Pass a SHA-256-hashed mobile number. **Only use when the raw mobile number cannot be provided — do not pass both `mobile` and `other2`.** (Android path only.)   |
| `emailSha256`  | string | Pass a SHA-256-hashed email. **Only use when the raw email cannot be provided — do not pass both `email` and `emailSha256`.** (iOS path only.)                   |
| `mobileSha256` | string | Pass a SHA-256-hashed mobile number. **Only use when the raw mobile number cannot be provided — do not pass both `mobile` and `mobileSha256`.** (iOS path only.) |

To identify the user:

### 1. Create an identifyRequest object

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

### 2. Set additional user attributes via AddSuccessListener

To set additional user attributes, use the `AddSuccessListener` callback on the identify result. If the `identifyRequest` succeeds, any user attributes you set inside the listener are assigned to the identified user.

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

Pass the `identifyRequest` to the method that matches the user's action:

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

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

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

#### Identify the user

```csharp
// 1. Create the identifyRequest object
var identifyRequest = new IdentityApiRequest();
identifyRequest.UserIdentities = new Dictionary<UserIdentity, string>()
{
#if __ANDROID__
  // Preferred: pass the customer's raw, unhashed email.
  // If you can only provide a SHA-256-hashed email, use UserIdentity.Other instead — do not pass both.
  { UserIdentity.Email, "j.smith@example.com" },
  { UserIdentity.Other, "SHA-256 hashed email" },  // only if raw email unavailable
  // If you can only provide a SHA-256-hashed mobile number, use Other2 instead of MobileNumber — do not pass both.
  { UserIdentity.Other2, "SHA-256 hashed mobile number" },  // only if raw mobile unavailable
  { UserIdentity.MobileNumber, "+13125551515" },
  { UserIdentity.CustomerId, "cust_10482" }
#elif __IOS__
  // Preferred: pass the customer's raw, unhashed email.
  // If you can only provide a SHA-256-hashed email, use UserIdentity.Other instead — do not pass both.
  { UserIdentity.Email, "j.smith@example.com" },
  { UserIdentity.Other, "SHA-256 hashed email" },  // only if raw email unavailable
  // Customer phone number in E.164 format.
  { UserIdentity.MobileNumber, "+13125551515" },
  // If you can only provide a SHA-256-hashed mobile number, use Other2 instead of MobileNumber — do not pass both.
  { UserIdentity.Other2, "SHA-256 hashed mobile number" },  // only if raw mobile unavailable
  { UserIdentity.CustomerId, "cust_10482" }
#endif
};

// 2. User attributes are set using the AddSuccessListener callback
// 3. Call one of the following methods that best matches the user's action:
MParticle.Instance.Identity.Login(identifyRequest)
  .AddSuccessListener(result =>
  {
      result.User.SetUserAttribute("firstname", "Jane");
      result.User.SetUserAttribute("lastname", "Smith");
  }); // Call when the user logs in or creates an account
MParticle.Instance.Identity.Identify(identifyRequest)
  .AddSuccessListener(result =>
  {
      result.User.SetUserAttribute("firstname", "Jane");
      result.User.SetUserAttribute("lastname", "Smith");
  }); // Call when you obtain the user's email mid-session, but not during a login
MParticle.Instance.Identity.Logout(); // Call when the user logs out
```

## 4. Set User Attributes

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

### Set user attributes

```csharp
using mParticle.MAUI;

// 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 = MParticle.Instance.Identity.CurrentUser;

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

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

// You can create a user attribute to contain a list of values
currentUser.SetUserAttribute("favorite-genres", string.Join(", ", new string[] { "documentary", "comedy", "romance", "drama" }));

// To remove a user attribute, call RemoveUserAttribute and pass in the attribute name.
currentUser.RemoveUserAttribute("attribute-to-remove");
```

### User attributes

Set as many of the following as you can collect:

### Show all user attributes

| Field                | Type    | Description                                                                                                            |
| -------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
| `firstname`          | string  | Customer's first name. Used for personalization.                                                                       |
| `lastname`           | string  | Customer's last name. Used for personalization.                                                                        |
| `mobile`             | string  | Phone number formatted as `1112345678` or `+1 (222) 345-6789`. Used for identity resolution and relevance.             |
| `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.                                              |
| `billingcity`        | string  | Billing city. Used for relevance.                                                                                      |
| `billingstate`       | string  | Billing state / province / region. Used for relevance and eligibility.                                                 |
| `billingzipcode`     | string  | Full ZIP or postcode (US preference is ZIP+4). Used for identity resolution and relevance.                             |
| `billingaddress1`    | string  | Billing street address line 1. Used for identity resolution and relevance.                                             |
| `billingaddress2`    | string  | Billing street address line 2. Used for identity resolution.                                                           |
| `country`            | string  | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `AU`). Used for eligibility and relevance.                           |
| `birthyear`          | integer | Customer's birth year (e.g. `1990`). Used for eligibility and relevance.                                               |
| `newcustomer`        | boolean | Whether this is a first-time buyer. Used for relevance.                                                                |
| `customertype`       | string  | Whether the user is authenticated (`guest` / `logged_in`). Used for relevance.                                         |
| `loyaltytier`        | string  | Partner loyalty program tier. Used for relevance and eligibility.                                                      |
| `loyaltyid`          | string  | Loyalty program member ID. Used for identity resolution.                                                               |
| `predictedltv`       | decimal | Predicted total lifetime value, typically from a partner ML model. Used for relevance.                                 |
| `subscriptionstatus` | string  | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. |
| `customersegment`    | string  | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance.                       |
| `acquisitionchannel` | string  | Channel through which the customer was acquired. Used for relevance.                                                   |

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

## 5. Track Funnel Events

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

### Event category: Screen views

Call `MParticle.Instance.LogScreen` with the name of the screen (e.g. `"homepage"`, `"product_detail_page"`). Include any additional custom attributes in the info dictionary.

#### Log a screen view

```csharp
MParticle.Instance.LogScreen(
  "homepage",
  new Dictionary<string, string>() { { "custom-attribute", "custom-value" } }
);
```

### Event category: Commerce events

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

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

Commerce events are logged with `CommerceEvent`, using a product action constant that identifies the customer action (viewing a product, adding to cart, starting checkout, completing a purchase, etc.). The sections below list the supported action types and walk through assembling the event.

#### Show all product action types

| Customer action            | Product action constant            |
| -------------------------- | ---------------------------------- |
| Product detail page viewed | `ProductAction.ViewDetail`         |
| Product clicked            | `ProductAction.Click`              |
| Item added to cart         | `ProductAction.AddToCart`          |
| Item removed from cart     | `ProductAction.RemoveFromCart`     |
| Item added to wishlist     | `ProductAction.AddToWishlist`      |
| Item removed from wishlist | `ProductAction.RemoveFromWishlist` |
| Checkout flow initiated    | `ProductAction.Checkout`           |
| Checkout option selected   | `ProductAction.CheckoutOption`     |
| Order confirmed            | `ProductAction.Purchase`           |
| Order refunded             | `ProductAction.Refund`             |

Tracking a commerce event requires three steps:

#### 1. Define the product

Create a `Product` with the product's name, SKU, and price. Set additional fields using the builder.

##### Define a product

```csharp
var product = new Product(
  name: "Double Room - Econ Rate",
  sku: "econ-1",
  price: 100.00
)
{
  Quantity = 4,
  Category = "room",
  Brand = "lodge-o-rama",
  Variant = "standard"
};
```

#### 2. Summarize the transaction

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

##### Summarize the transaction

```csharp
var transactionAttributes = new TransactionAttributes("ORDER-12345")
{
  Revenue = 149.99,
  Tax = 12.50,
  Shipping = 5.99,
  CouponCode = "SUMMER20"
};
```

#### 3. Log the commerce event

Create a `CommerceEvent` with a product action constant from the table above, attach the `transactionAttributes`, and log it. Pick the customer action you want to log:

##### Commerce event: ViewDetail

Log when a customer opens a product detail page.

###### Example ViewDetail event

```csharp
var product = new Product(
  name: "Trail Runner v3",
  sku: "SKU-001",
  price: 129.95
)
{
  Quantity = 1,
  Category = "shoes"
};

var commerceEvent = new CommerceEvent(ProductAction.ViewDetail, product)
{
  Currency = "USD"
};

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

##### Commerce event: AddToCart

Log when a customer adds an item to the cart.

###### Example AddToCart event

```csharp
var product = new Product(
  name: "Trail Runner v3",
  sku: "SKU-001",
  price: 129.95
)
{
  Quantity = 1
};

var commerceEvent = new CommerceEvent(ProductAction.AddToCart, product)
{
  Currency = "USD"
};

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

##### Commerce event: RemoveFromCart

Log when a customer removes an item from the cart.

###### Example RemoveFromCart event

```csharp
var product = new Product(
  name: "Trail Runner v3",
  sku: "SKU-001",
  price: 129.95
)
{
  Quantity = 1 // units removed
};

var commerceEvent = new CommerceEvent(ProductAction.RemoveFromCart, product)
{
  Currency = "USD"
};

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

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

###### Example Checkout event

```csharp
var product1 = new Product(name: "Trail Runner v3", sku: "SKU-001", price: 129.95) { Quantity = 1 };
var product2 = new Product(name: "Cushion Insole",  sku: "SKU-002", price: 19.95)  { Quantity = 2 };

var transactionAttributes = new TransactionAttributes("ORDER-12345")
{
  Revenue = 169.85,
  CouponCode = "SUMMER20"
};

var commerceEvent = new CommerceEvent(ProductAction.Checkout, product1)
{
  Currency = "USD",
  TransactionAttributes = transactionAttributes
};
commerceEvent.AddProduct(product2);

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

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

Log when the customer completes the shipping step. Set `CheckoutOption` to `"shipping"` and pass the shipping selection as custom attributes.

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

```csharp
var product1 = new Product(name: "Trail Runner v3", sku: "SKU-001", price: 129.95) { Quantity = 1 };
var product2 = new Product(name: "Cushion Insole",  sku: "SKU-002", price: 19.95)  { Quantity = 2 };

var commerceEvent = new CommerceEvent(ProductAction.CheckoutOption, product1)
{
  Currency = "USD",
  CheckoutOption = "shipping",
  CustomAttributes = new Dictionary<string, string>
  {
      { "shippingmethod", "express" },
      { "zipcode", "94103" },
      { "country", "US" },
      { "totalprice", "169.85" }
  }
};
commerceEvent.AddProduct(product2);

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

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

Log when the customer completes the payment step. Set `CheckoutOption` to `"payment"` and pass the payment method selected as custom attributes.

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

```csharp
var product1 = new Product(name: "Trail Runner v3", sku: "SKU-001", price: 129.95) { Quantity = 1 };
var product2 = new Product(name: "Cushion Insole",  sku: "SKU-002", price: 19.95)  { Quantity = 2 };

var commerceEvent = new CommerceEvent(ProductAction.CheckoutOption, product1)
{
  Currency = "USD",
  CheckoutOption = "payment",
  CustomAttributes = new Dictionary<string, string>
  {
      { "paymenttype", "credit_card" },
      { "payment_method", "visa" },
      { "paymentServiceProvider", "stripe" },
      { "ccbin", "424242" },
      { "totalprice", "169.85" }
  }
};
commerceEvent.AddProduct(product2);

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

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

###### Example Purchase event

```csharp
var product1 = new Product(name: "Trail Runner v3", sku: "SKU-001", price: 129.95) { Quantity = 1 };
var product2 = new Product(name: "Cushion Insole",  sku: "SKU-002", price: 19.95)  { Quantity = 2 };

var transactionAttributes = new TransactionAttributes("ORDER-10482")
{
  Revenue = 169.85,
  Tax = 14.20,
  Shipping = 5.99,
  CouponCode = "SUMMER20"
};

var commerceEvent = new CommerceEvent(ProductAction.Purchase, product1)
{
  Currency = "USD",
  TransactionAttributes = transactionAttributes
};
commerceEvent.AddProduct(product2);

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

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

###### Example Refund event

```csharp
var refundedProduct = new Product(
  name: "Trail Runner v3",
  sku: "SKU-001",
  price: 129.95
)
{
  Quantity = 1 // units refunded
};

var transactionAttributes = new TransactionAttributes("ORDER-10482") // original order id
{
  Revenue = 129.95 // refunded amount
};

var commerceEvent = new CommerceEvent(ProductAction.Refund, refundedProduct)
{
  Currency = "USD",
  TransactionAttributes = transactionAttributes
};

MParticle.Instance.LogCommerceEvent(commerceEvent);
```

The same structure applies to every product action constant — swap `ProductAction.Purchase` for the action that matches the customer behavior you're logging. See the product action types table above for the full list.

### Event category: Custom events

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

#### Show custom event types

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

#### Log a custom event

```csharp
MParticle.Instance.LogEvent(
  "video_watched",
  EventType.Navigation,
  new Dictionary<string, string>()
  {
      { "category", "Destination Intro" },
      { "title", "Paris" }
  }
);
```

## 6. Show a Placement

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

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

Call `SelectPlacements` as early as the screen loads and once all relevant attributes are available. At minimum, pass `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`. See [Placement attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/#placement-attributes) for the full list.

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

Pass these attributes in the `attributes` dictionary of `SelectPlacements`. Always supply the most recent value — attributes passed here override any earlier `SetUserAttribute` calls.

### Show all placement attributes

| Field                    | Type    | Description                                                                                                                                                                                                                                                                                                                                                           |
| ------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `email`                  | string  | Customer email (unhashed). Used for identity resolution.                                                                                                                                                                                                                                                                                                              |
| `firstname`              | string  | Customer first name. Used for personalization.                                                                                                                                                                                                                                                                                                                        |
| `lastname`               | string  | Customer last name. Used for personalization.                                                                                                                                                                                                                                                                                                                         |
| `mobile`                 | string  | Customer mobile number in E.164 format. Used for identity resolution.                                                                                                                                                                                                                                                                                                 |
| `confirmationref`        | string  | Order / confirmation reference number. Used for relevance and deduplication.                                                                                                                                                                                                                                                                                          |
| `currency`               | string  | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance.                                                                                                                                                                                                                                                                                        |
| `country`                | string  | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance.                                                                                                                                                                                                                                                                                                  |
| `language`               | string  | Customer's preferred language (ISO 639-1). Used for relevance.                                                                                                                                                                                                                                                                                                        |
| `totalprice`             | decimal | Total cart value including tax and shipping. Used for relevance.                                                                                                                                                                                                                                                                                                      |
| `amount`                 | string  | Cart subtotal before tax and shipping. Distinct from `totalprice`. Used for relevance.                                                                                                                                                                                                                                                                                |
| `couponCode`             | string  | Promo code applied to the order, if any. Used for relevance.                                                                                                                                                                                                                                                                                                          |
| `newcustomer`            | boolean | Whether this is a first-time buyer. Used for relevance.                                                                                                                                                                                                                                                                                                               |
| `customertype`           | string  | `guest` or `logged_in`. Used for relevance.                                                                                                                                                                                                                                                                                                                           |
| `value`                  | decimal | Customer's cumulative purchase value (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.                                                                                                                                                                                                                                                                      |
| `paymentServiceProvider` | string  | Comma-separated list of payment methods accepted on the page (e.g. `applepay,paypal,cardpayment`). Values must be lowercase with no spaces. See [Payment Service Provider](https://docs.rokt.com/developer-reference/product-deep-dives/pay-plus/partners/data-integration#payment-service-provider) for the full list of accepted values. Used for Pay+ eligibility. |
| `ccbin`                  | string  | Credit card BIN (6-8 digits). Used for relevance.                                                                                                                                                                                                                                                                                                                     |
| `billingname`            | string  | Billing name. Used for identity resolution.                                                                                                                                                                                                                                                                                                                           |
| `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.                                                                                                                                                                                                                                                                                     |
| `shippingname`           | string  | Shipping name. Used for relevance.                                                                                                                                                                                                                                                                                                                                    |
| `shippingaddress1`       | string  | Shipping street address. Used for relevance.                                                                                                                                                                                                                                                                                                                          |
| `shippingcity`           | string  | Shipping city. Used for relevance.                                                                                                                                                                                                                                                                                                                                    |
| `shippingstate`          | string  | Shipping state or province. Used for relevance.                                                                                                                                                                                                                                                                                                                       |
| `shippingzipcode`        | string  | Shipping ZIP or postcode. Used for relevance.                                                                                                                                                                                                                                                                                                                         |
| `shippingcountry`        | string  | Shipping country (ISO 3166-1 alpha-2). Used for relevance.                                                                                                                                                                                                                                                                                                            |
| `cartItems`              | array   | Structured array of cart-line objects. Used for relevance.                                                                                                                                                                                                                                                                                                            |
| `adsexperience`          | string  | Pass `"shoppable"` when deliberately targeting a Shoppable Ads experience.                                                                                                                                                                                                                                                                                            |

### Placement position: Overlay placements

Overlay placements render on top of your confirmation screen in a Rokt-managed container, requiring no changes to your app's existing layout. To insert an overlay placement, call `SelectPlacements` once the confirmation screen loads:

#### Overlay placement

```csharp
using mParticle.MAUI;

var attributes = new Dictionary<string, string>
{
  // Identity
  ["email"] = "j.smith@example.com",
  ["firstname"] = "Jenny",
  ["lastname"] = "Smith",
  ["mobile"] = "+13125551515",

  // Transaction
  ["confirmationref"] = "54321",
  ["currency"] = "USD",
  ["country"] = "US",
  ["language"] = "en",
  ["totalprice"] = "149.99",
  ["couponCode"] = "SUMMER20",

  // Customer context
  ["newcustomer"] = "false",
  ["customertype"] = "logged_in",
  ["value"] = "2340.00",
  ["subscriptionstatus"] = "active",
  ["customersegment"] = "vip",

  // Payment (include paymenttype and paymentServiceProvider for Pay+)
  ["paymenttype"] = "credit_card",
  ["paymentServiceProvider"] = "cardpayment",
  ["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"
};

MParticle.Instance.Rokt.SelectPlacements(
  identifier: "RoktExperience",
  attributes: attributes
);
```

### Placement position: Embedded placements

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

#### 1. Register the RoktEmbeddedView handler

Configure MAUI handlers for the `RoktEmbeddedView` in your app startup class so the SDK+ can render the embedded view in your layout.

##### Register RoktEmbeddedView handler

```csharp
public static class MauiProgram
{
  public static MauiApp CreateMauiApp()
  {
      var builder = MauiApp.CreateBuilder();
      builder
          .UseMauiApp<App>()
          .ConfigureMauiHandlers(handlers =>
          {
              handlers.AddHandler(typeof(RoktEmbeddedView), typeof(RoktEmbeddedViewHandler));
          });

      return builder.Build();
  }
}
```

#### 2. Add RoktEmbeddedView to your XAML layout

Add `RoktEmbeddedView` to your XAML layout at the position where you want the placement to render. The `x:Name` (here, `Location1`) is how you'll reference it from your code-behind.

##### Add RoktEmbeddedView to layout XAML

```xml
<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
      xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
      xmlns:sdk="clr-namespace:mParticle.MAUI;assembly=mParticle.Maui.Sdk"
      x:Class="SampleApp.MainPage">

  <VerticalStackLayout>
      <sdk:RoktEmbeddedView
          x:Name="Location1"/>
  </VerticalStackLayout>
</ContentPage>
```

#### 3. Call SelectPlacements and subscribe to events

From your code-behind, subscribe to placement events with `Events` (loading state, readiness, interaction, completion, and failure), then call `SelectPlacements` and pass your `RoktEmbeddedView` by `x:Name` in the `embeddedViews` dictionary.

##### Embedded placement with event subscriptions

```csharp
using mParticle.MAUI;

var attributes = new Dictionary<string, string>
{
  ["email"] = "j.smith@example.com",
  ["firstname"] = "Jenny",
  ["lastname"] = "Smith",
  ["billingzipcode"] = "11201",
  ["confirmationref"] = "54321"
};

MParticle.Instance.Rokt.Events("RoktExperience", roktEvent =>
{
  switch (roktEvent.GetType().Name)
  {
      case "RoktShowLoadingIndicator":
          Console.WriteLine("Rokt is loading...");
          break;
      case "RoktPlacementReady":
          Console.WriteLine("Placement is ready.");
          break;
      case "RoktPlacementInteractive":
          Console.WriteLine("Placement is interactive.");
          break;
      case "RoktPlacementCompleted":
          Console.WriteLine("Placement completed.");
          break;
      case "RoktPlacementFailure":
          Console.WriteLine("Placement failed or no fill.");
          break;
      default:
          Console.WriteLine($"Unhandled event: {roktEvent.GetType().Name}");
          break;
  }
});

MParticle.Instance.Rokt.SelectPlacements(
  identifier: "RoktExperience",
  attributes: attributes,
  embeddedViews: new Dictionary<string, RoktEmbeddedView>()
  {
      { "RoktEmbedded1", Location1 }
  }
);
```

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

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 are supported on **iOS only** in the MAUI SDK+. The Android path of this SDK+ does not support interstitial placements. Scope all interstitial placement code to the `#if __IOS__` conditional compilation block.

On iOS, interstitial placements use the dedicated `SelectShoppableAds` method after registering the payment extension (see [Configure Apple Pay (iOS only)](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/#apple-pay-maui)). The `CartItemInstantPurchase` and related events in the [Events API](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/#events-api) below fire during Shoppable Ads purchase flows. Pass the complete attribute set described in [Placement attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/#placement-attributes) — in particular, include shipping address details to support Shoppable Ads order fulfillment.

#### Interstitial placement (iOS only)

```csharp
#if __IOS__
using mParticle.MAUI;

var attributes = new Dictionary<string, string>
{
  // Identity
  ["email"] = "j.smith@example.com",
  ["firstname"] = "Jenny",
  ["lastname"] = "Smith",
  ["mobile"] = "+13125551515",

  // Transaction
  ["confirmationref"] = "54321",
  ["currency"] = "USD",
  ["country"] = "US",
  ["language"] = "en",
  ["totalprice"] = "149.99",
  ["amount"] = "137.50",
  ["couponCode"] = "SUMMER20",

  // Customer context
  ["newcustomer"] = "false",
  ["customertype"] = "logged_in",
  ["value"] = "2340.00",

  // Payment
  ["paymenttype"] = "credit_card",
  ["paymentServiceProvider"] = "cardpayment",
  ["ccbin"] = "411112",

  // Billing address
  ["billingaddress1"] = "123 Main St",
  ["billingcity"] = "Brooklyn",
  ["billingstate"] = "NY",
  ["billingzipcode"] = "11201",

  // Shipping address (required for Shoppable Ads order fulfillment)
  ["shippingmethod"] = "express",
  ["shippingaddress1"] = "175 Varick St",
  ["shippingcity"] = "New York",
  ["shippingstate"] = "NY",
  ["shippingzipcode"] = "10014",
  ["shippingcountry"] = "US"
};

MParticle.Instance.Rokt.SelectShoppableAds(
  identifier: "RoktExperience",
  attributes: attributes,
  config: null
);
#endif
```

> **Note**
>
> If your app does not have shipping address details (for example, for ticket or digital goods purchases), pass billing address details instead. Rokt will provide a UI for the customer to confirm or edit their shipping address before completing the purchase.

### Optional functions

| Function                          | Purpose                        |
| --------------------------------- | ------------------------------ |
| `MParticle.Instance.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).

### SelectPlacements with RoktConfig

```csharp
using mParticle.MAUI;

var roktConfig = new RoktConfig()
{
  ColorMode = RoktConfig.RoktColorMode.Light
};

MParticle.Instance.Rokt.SelectPlacements(
  identifier: "RoktExperience",
  attributes: attributes,
  config: roktConfig
);
```

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

### Events API

The SDK+ provides placement lifecycle events through the `MParticle.Instance.Rokt` API.
Use `Events` to subscribe per placement identifier and respond to loading state, readiness, interaction, completion, and failures.

### Subscribe to placement events

```csharp
void HandleRoktEvent(object roktEvent)
{
  switch (roktEvent.GetType().Name)
  {
      case "RoktShowLoadingIndicator":
          Console.WriteLine("Rokt is loading...");
          break;
      case "RoktHideLoadingIndicator":
          Console.WriteLine("Rokt finished loading.");
          break;
      case "RoktPlacementReady":
          Console.WriteLine("Placement is ready.");
          break;
      case "RoktPlacementInteractive":
          Console.WriteLine("Placement is interactive.");
          break;
      case "RoktPositiveEngagement":
      case "RoktFirstPositiveEngagement":
          Console.WriteLine("User positively engaged.");
          break;
      case "RoktPlacementCompleted":
          Console.WriteLine("Placement completed.");
          break;
      case "RoktPlacementFailure":
          Console.WriteLine("Placement failed or no fill.");
          break;
      default:
          Console.WriteLine($"Unhandled event: {roktEvent.GetType().Name}");
          break;
  }
}

MParticle.Instance.Rokt.Events("RoktExperience", roktEvent =>
{
  HandleRoktEvent(roktEvent);
});
```

#### Standard events

### Show all standard events

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

## 7. Configure Apple Pay (iOS only)

Apple Pay is required for Shoppable Ads on iOS. If you are not using Shoppable Ads, skip this step.

Before registering the payment extension, create an Apple Pay merchant ID, configure your Xcode project, and generate a Payment Processing Certificate.

Follow the steps in [Apple Pay — iOS setup](https://docs.rokt.com/developer-reference/product-deep-dives/shoppable-ads/payment-methods/apple-pay#ios-setup), then register `RoktPaymentExtension` in your iOS-specific platform code. In a MAUI project, place this in your iOS platform `AppDelegate.cs` (or in `MauiProgram.cs` inside a `#if __IOS__` block), and call it after `MParticle.Instance.Initialize(options)` and before any `SelectPlacements` or `SelectShoppableAds` call:

### Register RoktPaymentExtension (iOS only)

```csharp
#if __IOS__
// iOS only: register after MParticle.Instance.Initialize(options),
// before SelectPlacements/SelectShoppableAds.
RoktPaymentExtension.Register("merchant.com.yourapp.rokt");
#endif
```

> **Caution**
>
> `RoktPaymentExtension` must be registered **after** `MParticle.Instance.Initialize(options)` and **before** any `SelectPlacements` or `SelectShoppableAds` call. Registering out of order will prevent Apple Pay from functioning correctly.

> **Note**
>
> The exact C# API for `RoktPaymentExtension` registration may vary by MAUI binding version. If the method signature above does not match your NuGet package, check your NuGet release notes for the equivalent call, or contact Rokt support for binding-specific guidance.

## 8. Appendix

### Appendix A: App configuration

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

```csharp title="RoktConfig with ColorMode"
var roktConfig = new RoktConfig()
{
    ColorMode = RoktConfig.RoktColorMode.Light
};

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

```csharp title="Cache for 1200 seconds"
var roktConfig = new RoktConfig()
{
    CacheConfig = new CacheConfig()
    {
        CacheDurationInSeconds = 1200,
        CacheAttributes = new Dictionary<string, string>()
        {
            { "email", "j.smith@example.com" },
            { "orderNumber", "123" }
        }
    }
};

MParticle.Instance.Rokt.SelectPlacements(
    identifier: "RoktExperience",
    attributes: attributes,
    config: 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 |

Use the `#if __ANDROID__` block to scope this setting to Android only:

```csharp title="EdgeToEdgeDisplay (Android only)"
#if __ANDROID__
var roktConfig = new RoktConfig()
{
    EdgeToEdgeDisplay = true
};

MParticle.Instance.Rokt.SelectPlacements(
    identifier: "RoktExperience",
    attributes: attributes,
    config: roktConfig
);
#endif
```

### Appendix B: MAUI declarative UI support

The MAUI SDK+ supports both XML-based layout (`RoktEmbeddedView` in XAML) and code-behind placement integration. For embedded placements in XAML, register the `RoktEmbeddedViewHandler` in `MauiProgram.CreateMauiApp()` (see [Embedded placements](https://docs.rokt.com/integration-guides/ecommerce/sdk/maui/#placement-maui)) and reference the view by its `x:Name` in your code-behind.

There is no equivalent of Jetpack Compose (`RoktLayout`) or SwiftUI (`MPRoktLayout`) for MAUI at this time. Use the XML + code-behind pattern for embedded placements.

### Appendix C: Error handling

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

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

The failure listener receives an `errorResponse` object. Use the `HttpCode` property to determine the cause and decide whether to retry.

#### Android error handling

On Android, `IdentityApi.UNKNOWN_ERROR` indicates a client-side failure (device offline or client-side timeout) — retry the request. HTTP 429 (`IdentityApi.THROTTLE_ERROR`) means the request was rate-limited — retry with exponential backoff. Other HTTP error codes indicate implementation or server issues that should be logged and investigated.

```csharp title="IDSync error handling (Android)"
#if __ANDROID__
MParticle.Instance.Identity.Identify(identifyRequest)
    .AddFailureListener(errorResponse =>
    {
        if (errorResponse.HttpCode == IdentityApi.UnknownError)
        {
            // Device is likely offline or client-side timeout — retry the request
        }
        else if (errorResponse.HttpCode == 429)
        {
            // Throttled — retry with exponential backoff
        }
        else
        {
            // Log errorResponse.HttpCode and investigate — likely an implementation issue
        }
    })
    .AddSuccessListener(result =>
    {
        // Proceed with the identified user
    });
#endif
```

#### iOS error handling

On iOS, the failure listener's `HttpCode` maps to `MPIdentityErrorResponseCode` values from the native iOS SDK+. Network failures (`clientNoConnection`, `clientSideTimeout`) should be retried immediately. Throttle errors (HTTP 429, corresponding to `retry`) should be retried with backoff. `requestInProgress` means another IDSync call is in flight — inspect your implementation if this occurs frequently, then retry. All other codes typically indicate an implementation issue; inspect `errorResponse` details to diagnose.

```csharp title="IDSync error handling (iOS)"
#if __IOS__
MParticle.Instance.Identity.Identify(identifyRequest)
    .AddFailureListener(errorResponse =>
    {
        if (errorResponse.HttpCode == IdentityApi.UnknownError)
        {
            // clientNoConnection or clientSideTimeout — device is offline or timed out, retry the request
        }
        else if (errorResponse.HttpCode == 429)
        {
            // Throttled (MPIdentityErrorResponseCodeRetry) — retry with exponential backoff
        }
        else if (errorResponse.HttpCode == (int)IdentityApi.RequestInProgress)
        {
            // Another IDSync request is already in progress — inspect implementation frequency, then retry
        }
        else
        {
            // Log errorResponse details and investigate — likely an implementation issue
        }
    })
    .AddSuccessListener(result =>
    {
        // Proceed with the identified user
    });
#endif
```

> **Note**
>
> The C# constant names above (`IdentityApi.UnknownError`, `IdentityApi.RequestInProgress`) reflect the MAUI binding layer. If your NuGet version exposes different constant names, they map to the underlying iOS `MPIdentityErrorResponseCode` values:
>
> | MAUI C# constant                | iOS `MPIdentityErrorResponseCode`                       |
> | ------------------------------- | ------------------------------------------------------- |
> | `IdentityApi.UnknownError`      | `clientNoConnection`, `clientSideTimeout`, or `unknown` |
> | `IdentityApi.RequestInProgress` | `requestInProgress`                                     |
> | HTTP 429                        | `retry` (throttle)                                      |

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

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

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

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

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

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

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

#### Passing to native app via deep link

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

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

#### Setting the session ID

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

```csharp title="Handle deep link and set sessionId"
// Extract sessionId from the incoming deep link URI
// and set it on the Rokt SDK+ before calling SelectPlacements
var uri = new Uri(deepLinkUrl);
var query = System.Web.HttpUtility.ParseQueryString(uri.Query);
var sessionId = query["sessionId"];

if (!string.IsNullOrEmpty(sessionId))
{
    MParticle.Instance.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.

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

#### Enable verbose SDK+ logging

```csharp
#if __ANDROID__
  MParticle.Instance.SetLogLevel(LogLevel.Verbose);
#elif __IOS__
  MParticle.Instance.SetLogLevel(LogLevel.Verbose);
#endif
```

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

Build and run your app with `options.Environment = mParticle.MAUI.Environment.Development`.

### 3. Trigger SelectPlacements

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

### 4. Verify events

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

### Troubleshooting

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

#### Initialization errors

- Confirm the key and secret inside the platform-specific blocks match the values from your Rokt account manager.
- Confirm `MParticle.Instance.Initialize(options)` runs before any `SelectPlacements` or `LogEvent` call.
- Confirm `RoktKit.Register()` is called before `MParticle.Instance.Initialize(options)`.

#### Identity errors

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

#### Placement not rendering

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