# Rokt Ecommerce SDK+ integration guides > Complete machine-readable SDK+ integration guidance for Rokt Ecommerce partners integrating Rokt into transaction experiences they own. ## Product and audience boundary 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/). ## How to use this bundle - Determine the ecommerce partner’s implementation platform before selecting a document. - Read the selected platform document in full. Every document is complete, not a summary or quick start. - Do not combine code or requirements from different platform documents unless performing an explicit cross-platform comparison. - Preserve the target and language context shown inside wrapper-platform documents. - Do not ingest this bundle together with the individual platform files; it contains the same seven complete guides. ## Document manifest | ID | Product | Intended implementer | Platform | Targets | Languages | Machine-readable source | | --- | --- | --- | --- | --- | --- | --- | | `web` | Rokt Ecommerce | Ecommerce partner | Web | Web | JavaScript | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/web/index.html.md) | | `ios` | Rokt Ecommerce | Ecommerce partner | iOS | iOS | Swift, Objective-C | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/ios/index.html.md) | | `android` | Rokt Ecommerce | Ecommerce partner | Android | Android | Kotlin, Java | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/android/index.html.md) | | `flutter` | Rokt Ecommerce | Ecommerce partner | Flutter | iOS, Android, Web | Dart, Swift, Objective-C, Kotlin, Java, JavaScript | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/flutter/index.html.md) | | `react-native` | Rokt Ecommerce | Ecommerce partner | React Native | iOS, Android | JavaScript, Swift, Objective-C, Kotlin, Java | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/react-native/index.html.md) | | `maui` | Rokt Ecommerce | Ecommerce partner | MAUI | iOS, Android | C# | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/maui/index.html.md) | | `cordova` | Rokt Ecommerce | Ecommerce partner | Cordova | iOS, Android | JavaScript, Objective-C, Java | [Complete guide](https://main--docs-rokt.netlify.app/integration-guides/ecommerce/sdk/cordova/index.html.md) | ## Complete platform documents # Web 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/web/](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/) > > 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 Web SDK+. The SDK+ passes user and transaction data to Rokt on configured pages so Rokt can render relevant experiences, such as offers on confirmation pages. ## 1. Initialize the Rokt SDK+ Include the SDK+ initialization script on every page of your site. Browser caching means the SDK+ loads from cache on subsequent pages rather than re-fetching. **For single-page apps:** Insert the script into the head of your main `index.html`, or wherever your content is rendered. **For multi-page apps:** Place the script in your primary shared layout file. If you don't use a template-based rendering system, add it to each HTML file. > **Note: First-Party Domain Configuration** > > Using a first-party domain when integrating the Web SDK+ into your site ensures that the SDK+ uses your own domain when making calls to Rokt's API, providing your customers with a seamless experience and minimizing the risk of blocked content. To learn how to configure a first-party domain for your SDK+ integration, see [First Party Domain Integration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/). ### Rokt Web SDK+ initialization ```html ``` When inserting the initialization script into your site, you will see customizable fields for: ### 1. Entering your Rokt API key Set `API_KEY` to the Rokt API key provided by your Rokt account manager. ### 2. 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 `ROKT_DOMAIN` to your custom subdomain. Routing the Rokt SDK+ through your own domain reduces the risk of ad blockers and browsers from blocking ads or data. ### 3. Setting your data environment Set `isDevelopmentMode` to `true` while testing to route data to the Development environment, and `false` to send live customer activity to Production. ### 4. Identifying your user and setting attributes In `identifyRequest`, pass the user's raw, unhashed email in the `email` field. Include `mobile_number` and `customerid` when available — more signals improve identity resolution. Once identified, use the `identityCallback` to set additional user attributes. For a list of recommended attributes, see [User attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#user-attributes). #### identityCallback ```javascript // The identityCallback determines if the identifyRequest was successful. identityCallback: function(result) { if (result.getUser()) { // If the user was identified, set additional user attributes with setUserAttribute. result.getUser().setUserAttribute('attribute_key', 'attribute_value'); } } ``` > **Note** > > Always include `identifyRequest` in the initialization script. 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 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#identify). ## 2. Identify the User The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#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 | Field | Type | Description | | --------------- | ------ | ---------------------------------------------------------------------------------------------------- | | `email` | string | Raw, unhashed email address. | | `email_sha256` | string | SHA-256 hashed email. Use instead of `email` when only the hashed form is available. | | `mobile_sha256` | string | SHA-256 hashed mobile number. Use instead of `mobile_number` when only the hashed form is available. | | `mobile_number` | string | Phone number in E.164 format (e.g. `+13125551515`). | | `customerid` | string | Internal customer/account identifier. Send on every page for logged-in users. | To identify the user: ### 1. Create an identifyRequest object Create an `identifyRequest` object to contain the user's identifiers. You should integrate the user's raw, unhashed email address into the `email` field. ### 2. Create an identityCallback To set additional user attributes, create an `identityCallback`. If the `identifyRequest` succeeds, then any user attributes you set inside the callback are assigned to the identified user. ### 3. Send the request using the method that matches the user's action Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action: - **`mParticle.Identity.login`:** call when the user logs in or creates an account. - **`mParticle.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.Identity.logout`:** call when the user logs out. Calling these methods transitions the SDK's record of the current user's state. The `login` and `logout` methods also automatically log a corresponding event to improve Rokt's attribution. For example, to identify a user named Jane Smith with the email address `j.smith@example.com`, mobile number `+13125551515`, and customer ID `cust_10482`: #### Example user identification ```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 'email_sha256' field instead of 'email'. email_sha256: 'SHA-256 hashed email address', mobile_number: '+13125551515', // If you are passing a hashed mobile number, set it inside the 'mobile_sha256' field instead of 'mobile_number'. mobile_sha256: 'SHA-256 hashed mobile number', 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 ``` ## 3. Set User Attributes Set user attributes progressively throughout the complete customer journey, not just at checkout. The more attributes you set, the better Rokt can resolve the customer's identity and deliver relevant offers. > **Tip: Set attributes as early as possible** > > Set new user attributes as they become available. Earlier attribute collection gives Rokt more signals with which to improve the speed and relevance of placements rendered on the confirmation page. To get confirmation-page offers ready before the confirmation page opens, see [Set confirmation-page attributes during checkout](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#set-confirmation-page-attributes-during-checkout). ### Set User Attributes ```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_number", "+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("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 attributes 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_number` | string | Customer phone number in E.164 format (e.g. `+13125551515`). Used for identity resolution and relevance. | | `birthyear` | integer | Customer's birth year (e.g. `1990`). Preferred date-of-birth field. Alternates: `dob` (`yyyymmdd`), `age`. Used for eligibility and relevance. | | `dob` | string | Date of birth, `yyyymmdd`. Alternate to `birthyear`. Used for eligibility and relevance. | | `age` | integer | Customer's age. Alternate to `birthyear`. Used for eligibility and relevance. | | `gender` | string | Customer's gender. For example, `M`, `F`, `Male`, or `Female`. Used for relevance. | | `title` | string | Honorific. For example, `Mr`, `Mrs`, `Ms`. Used for personalization. | | `language` | string | ISO 639-1 language code associated with the purchase. Used for relevance. | | `billingaddress1` | string | Street address (e.g. `123 Main St`). Used for identity resolution and relevance. | | `billingaddress2` | string | Apartment/unit (e.g. `Apt 4B`). Used for identity resolution. | | `billingcity` | string | Billing city. Used for relevance. | | `billingstate` | string | Billing state / province / region. Used for relevance and eligibility. | | `billingzipcode` | string | Full ZIP or postcode (US preference is ZIP+4). Used for identity resolution and relevance. | | `country` | string | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `AU`). Used for eligibility and relevance. | | `newcustomer` | boolean | Whether this is a first-time buyer (`true` / `false`). 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 produced by a partner machine-learning model. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance. | | `acquisitionchannel` | string | How the customer was originally acquired. Used for relevance. | | `utmsource` | string | Marketing attribution source. Used for relevance. | | `utmmedium` | string | Marketing attribution medium. Used for relevance. | | `utmcampaign` | string | Marketing attribution campaign. Used for relevance. | | `utmcontent` | string | Marketing attribution content variant. Used for relevance. | | `utmterm` | string | Marketing attribution term / keyword. Used for relevance. | | `referrer` | string | Referring URL — intent signal. Used for relevance. | > **Note** > > All user attributes (including list attributes and tags) must have distinct names. ### Set confirmation-page attributes during checkout When Rokt loads offers early for your account, it requests your confirmation-page offers before the customer places the order, using the identity and attributes you've set so far, so the offers are ready as soon as the confirmation page opens. When your confirmation page calls `selectPlacements`, Rokt checks the attributes it relies on for your account against the ones the early request used. If one of them doesn't match, for example because it was only set on the confirmation page or its value changed, Rokt requests new offers and the confirmation page loads them the usual way. To let Rokt use offers loaded early: - **Set confirmation-page attributes during checkout.** Apart from order-only details (see below), set every attribute your confirmation page passes to `selectPlacements` as soon as you know it, and no later than the final checkout review page. This includes the customer's identity: when a guest enters their email at checkout, call `mParticle.Identity.identify` right away, as described in [Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#identify). - **Use the same names and values on both pages.** Rokt compares attribute values exactly, so keep attribute names, value formats, and capitalization identical on your checkout and confirmation pages. For example, `Gold` on the checkout page and `gold` on the confirmation page count as different values. Email addresses are the exception: Rokt ignores their capitalization. - **Keep order-only details on the confirmation page.** Details that only exist once the order is placed, such as the order number, don't need to be set during checkout. - **Load the SDK+ in the page itself on every checkout step.** Include the [initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#initialize) directly in each checkout page, including the final review page, the same way you include it on your confirmation page. If a tag manager loads the SDK+ inside its own iframe, Rokt can't tell which checkout page the customer is on, and your confirmation page can't use offers loaded there. ### Set confirmation-page attributes during checkout ```javascript // On the final checkout review page, once the SDK+ has loaded. // Use the same attribute names and values your confirmation page passes to selectPlacements. window.mParticle.ready(function() { const currentUser = mParticle.Identity.getCurrentUser(); currentUser.setUserAttribute("firstname", "Jane"); currentUser.setUserAttribute("lastname", "Smith"); currentUser.setUserAttribute("customertype", "guest"); currentUser.setUserAttribute("loyaltytier", "gold"); currentUser.setUserAttribute("billingzipcode", "11201"); currentUser.setUserAttribute("country", "US"); }); ``` ## 4. Log Events Page views, commerce events, and custom events provide the behavioral and transaction signals Rokt uses to select relevant experiences and measure their impact across the customer journey. Choose an event category below for specific implementation instructions and code examples. ### Event category: Page view Page views tell Rokt which page the customer is on. Wrap each call in `mParticle.ready()` so it fires after the SDK+ initializes. On transactional pages (PLP, PDP, cart, checkout, confirmation), also fire the matching commerce event from **Commerce events** to send product- and order-level details. #### Page type: home Log this when the customer lands on your site's home page, including empty-path roots like `/`. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example home page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "home", pagetype: "home", url: window.location.toString(), title: document.title, sitesection: "root", referringpage: document.referrer }); }); ``` #### Page type: PLP Log this on a product listing page (PLP): a category, collection, or search-results page where the customer is browsing multiple products at once. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example PLP page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "shoes", pagetype: "PLP", url: window.location.toString(), title: document.title, sitesection: "mens", referringpage: document.referrer }); }); ``` #### Page type: PDP Log this on a product detail page (PDP) when the customer opens a single product to view its details, price, and options. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example PDP page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "trail-runner-v3", pagetype: "PDP", url: window.location.toString(), title: document.title, sitesection: "mens", referringpage: document.referrer }); }); ``` #### Page type: cart Log this when the customer opens the cart page to review the items they've added. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example cart page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "cart", pagetype: "cart", url: window.location.toString(), title: document.title, sitesection: "checkout", referringpage: document.referrer }); }); ``` #### Page type: checkout Log this when the customer enters the checkout flow to finalize their order, entering shipping, billing, or payment details. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example checkout page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "checkout", pagetype: "checkout", url: window.location.toString(), title: document.title, sitesection: "checkout", referringpage: document.referrer }); }); ``` #### Page type: confirmation Log this on the order confirmation page after a successful purchase. This is the primary surface where Rokt renders post-transaction offers. | Field | Type | Description | | --------------- | ------ | --------------------------------------------------------------------------------------------------------- | | `screenname` | string | Last segment of the URL path (e.g. `/products/shoes` → `shoes`). Defaults to `home` if the path is empty. | | `pagetype` | string | Type of page being viewed: `home`, `PLP`, `PDP`, `cart`, `checkout`, `confirmation`. | | `url` | string | Full URL of the current page. | | `title` | string | Page title (from `document.title`). | | `sitesection` | string | Top-level site section (e.g. `mens`, `womens`, `support`). | | `referringpage` | string | Referring URL captured from `document.referrer`. | ##### Example confirmation page view ```javascript window.mParticle.ready(function() { mParticle.logPageView("page_view", { screenname: "order-10482", pagetype: "confirmation", url: window.location.toString(), title: document.title, sitesection: "checkout", referringpage: document.referrer }); }); ``` ### Event category: Commerce events Commerce events carry product-level details for each step of the customer journey. You should log a separate commerce event for each action the customer takes, from viewing product detail pages through to their final purchase. > **Note** > > Commerce events track both product-specific data and event-level data. Products are represented as objects created with `createProduct`. Note that event attributes for commerce events are typed in flatcase, such as `currency` and `listname`, but product-specific attributes and attributes in `transactionAttributes` objects use PascalCase, such as `Name`, `Sku`, and `Id`. There are three steps in the process of logging a commerce event: #### 1. Define the product Create one or more products using `mParticle.eCommerce.createProduct`. The positional arguments cover name, SKU, price, quantity, variant, category, brand, and position. ##### Define a product ```javascript const 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 If you are logging a `Purchase`, `Checkout`, or `CheckoutOption` event, create a `transactionAttributes` object. Use PascalCase for the transaction-specific fields (`Id`, `Revenue`, `Tax`, `Shipping`, `CouponCode`). If you want to include an order-level coupon, insert it here, not in an individual product object. ##### Summarize the transaction ```javascript const transactionAttributes = { Id: 'ORDER-12345', Revenue: 149.99, Tax: 12.50, Shipping: 5.99, CouponCode: 'SUMMER20' }; ``` #### 3. Log the commerce event To log a commerce event, call `mParticle.eCommerce.logProductAction`, passing the product action type, the product(s) you created in step 1, event-level attributes, any optional custom flags, and (when applicable) the `transactionAttributes` object. For product impressions, call `mParticle.eCommerce.logImpression` instead. To see examples and requirements for each commerce event type, select one of the following: ##### Commerce event: PLP impression Log a product listing page (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`. | | `Product` | object or array | yes | A product object from `createProduct`, or an array of them. The name is singular even when the value is an array (array values require Web SDK 1.14.4+). Set `Position` to each item's 1-indexed rank. | | `currency` | string | yes | ISO 4217 currency code (passed as event-level customAttribute). | ###### Example PLP impression ```javascript // Position (8th positional arg) is the 1-indexed rank in the list. const product1 = mParticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price null, null, // Quantity, Variant 'Shoes', // Category 'BrandX', // Brand 1 // Position in list ); const product2 = mParticle.eCommerce.createProduct( 'Cushion Insole', // Name 'SKU-002', // SKU 19.95, // Price null, null, // Quantity, Variant 'Accessories', // Category 'BrandX', // Brand 2 // Position in list ); mParticle.eCommerce.logImpression( { Name: 'Mens Running Shoes', Product: [product1, product2] }, { currency: 'USD' } // event-level attrs ); ``` ##### Commerce event: ViewDetail Log when a customer opens a product detail page. | Field | Type | Required | Description | | ---------- | ------- | -------- | ------------------------------------------------- | | `Name` | string | yes | Product name supplied to `createProduct`. | | `Sku` | string | yes | Product SKU supplied to `createProduct`. | | `Price` | decimal | yes | Per-item price supplied to `createProduct`. | | `Quantity` | integer | no | Quantity supplied to `createProduct`, when known. | | `currency` | string | yes | ISO 4217 currency code. | | `listname` | string | no | Set if the customer arrived from a PLP. | ###### Example ViewDetail event ```javascript const product = mParticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); mParticle.eCommerce.logProductAction( mParticle.ProductActionType.ViewDetail, [product], { currency: 'USD', listname: 'PLP-Running' }, // event-level attrs null, // custom flags null // no transactionAttributes ); ``` ##### Commerce event: AddToCart Log when a customer adds an item to the cart. | Field | Type | Required | Description | | ---------- | ------- | -------- | ------------------------------------------- | | `Name` | string | yes | Product name supplied to `createProduct`. | | `Sku` | string | yes | Product SKU supplied to `createProduct`. | | `Price` | decimal | yes | Per-item price supplied to `createProduct`. | | `Quantity` | integer | yes | Number of items added. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example AddToCart event ```javascript const product = mParticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); 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 | | ---------- | ------- | -------- | ------------------------------------------- | | `Name` | string | yes | Product name supplied to `createProduct`. | | `Sku` | string | yes | Product SKU supplied to `createProduct`. | | `Price` | decimal | yes | Per-item price supplied to `createProduct`. | | `Quantity` | integer | yes | Number of items removed. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example RemoveFromCart event ```javascript const product = mParticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity removed ); 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. Create a separate product for each item in the customer's cart, then pass the complete product array to `logProductAction` using `ProductActionType.ViewCart`. | Field | Type | Required | Description | | --------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------ | | `products` | Product\[] | yes | Full cart contents as objects from `createProduct`. Pass as the second argument to `logProductAction`. | | `cartitemcount` | integer | yes | Total number of items in the cart. | | `totalprice` | decimal | yes | Cart total. | | `currency` | string | yes | ISO 4217 currency code. | | `couponcode` | string | no | Order-level promo, if applied. | ###### Example cart page view event ```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.ViewCart, [product1, product2], { cartitemcount: 3, totalprice: 169.85, currency: "USD", couponcode: "SUMMER20" }, null, null ); ``` ##### Commerce event: Checkout Log when the customer enters the checkout flow. Create a product object for each item in the customer's cart, and pass the complete product array along with any cart-level totals. | Field | Type | Required | Description | | ----------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------ | | `products` | Product\[] | yes | Full cart contents as objects from `createProduct`. Pass as the second argument to `logProductAction`. | | `totalprice` | decimal | yes | Cart total before tax/shipping. | | `cartitemcount` | integer | yes | Total number of items in the cart. | | `currency` | string | yes | ISO 4217 currency code. | | `transactionAttributes` | object | yes | Transaction details, including the checkout reference and current totals. | ###### Example Checkout event ```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); const transactionAttributes = { Id: 'CHECKOUT-10482', // stable checkout reference Revenue: 169.85, // current cart total CouponCode: 'SUMMER20' // order-level promo }; mParticle.eCommerce.logProductAction( mParticle.ProductActionType.Checkout, [product1, product2], { currency: 'USD', cartitemcount: 3, totalprice: 169.85 }, null, transactionAttributes ); ``` ##### Commerce event: CheckoutOption (shipping) Log when the customer completes the shipping step. Pass `option: 'shipping'` along with the shipping selections. | Field | Type | Required | Description | | ----------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------ | | `products` | Product\[] | yes | Full cart contents as objects from `createProduct`. Pass as the second argument to `logProductAction`. | | `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. | | `transactionAttributes` | object | yes | Transaction details, including the checkout reference and current totals. | ###### Example CheckoutOption (shipping) event ```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); const transactionAttributes = { Id: 'CHECKOUT-10482', // stable checkout reference Revenue: 175.84, // subtotal plus shipping Shipping: 5.99, // selected shipping cost CouponCode: 'SUMMER20' // order-level promo }; mParticle.eCommerce.logProductAction( mParticle.ProductActionType.CheckoutOption, [product1, product2], { option: 'shipping', shippingmethod: 'express', zipcode: '94103', country: 'US', totalprice: 175.84, currency: 'USD' }, null, transactionAttributes ); ``` ##### Commerce event: CheckoutOption (payment) Log when the customer completes the payment step. Pass `option: 'payment'` along with the payment method selected. | Field | Type | Required | Description | | ------------------------ | ---------- | -------- | ------------------------------------------------------------------------------------------------------ | | `products` | Product\[] | yes | Full cart contents as objects from `createProduct`. Pass as the second argument to `logProductAction`. | | `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. | | `transactionAttributes` | object | yes | Transaction details, including the checkout reference and final totals. | ###### Example CheckoutOption (payment) event ```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); const transactionAttributes = { Id: 'CHECKOUT-10482', // stable checkout reference Revenue: 190.04, // subtotal plus tax and shipping Tax: 14.20, // current tax total Shipping: 5.99, // selected shipping cost CouponCode: 'SUMMER20' // order-level promo }; mParticle.eCommerce.logProductAction( mParticle.ProductActionType.CheckoutOption, [product1, product2], { option: 'payment', paymenttype: 'credit_card', payment_method: 'visa', paymentServiceProvider: 'stripe', ccbin: '424242', totalprice: 190.04, currency: 'USD' }, null, transactionAttributes ); ``` ##### Commerce event: Purchase Log when a purchase is confirmed. Create a product for each item, pass the complete product array to `logProductAction`, and include a `transactionAttributes` object summarizing the order. | Field | Type | Required | Description | | ---------------------------------- | ---------- | -------- | -------------------------------------------------------------------------------------------------------- | | `products` | Product\[] | yes | An array of objects representing each purchased item. Pass as the second argument to `logProductAction`. | | `transactionAttributes.Id` | string | yes | Order or transaction identifier. | | `transactionAttributes.Revenue` | decimal | yes | Order total. | | `transactionAttributes.Tax` | decimal | yes | Total tax on the order. | | `transactionAttributes.Shipping` | decimal | yes | Shipping cost. | | `transactionAttributes.CouponCode` | string | no | Order-level promo, if applied. | | `currency` | string | yes | ISO 4217 currency code. | | `cartitemcount` | integer | no | Total number of items in the order. | > **Note** > > Order-level coupons belong on `transactionAttributes.CouponCode`. When a single order has multiple SKUs with different promos, pass each product's coupon as the ninth argument to `createProduct`. ###### Example Purchase event ```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); const transactionAttributes = { Id: 'ORDER-10482', // order or transaction identifier Revenue: 190.04, // order total Tax: 14.20, // total tax Shipping: 5.99, // shipping cost CouponCode: 'SUMMER20' // order-level promo }; mParticle.eCommerce.logProductAction( mParticle.ProductActionType.Purchase, [product1, product2], { currency: 'USD', cartitemcount: 3 }, null, transactionAttributes ); ``` ##### Commerce event: Refund Log when an order (or an item within it) is refunded. Send only the products being refunded, plus a `transactionAttributes` object referencing the original order. | Field | Type | Required | Description | | ------------------------------- | ------- | -------- | -------------------------------------------------- | | `Name` | string | yes | Refunded product name supplied to `createProduct`. | | `Sku` | string | yes | Refunded product SKU supplied to `createProduct`. | | `Price` | decimal | yes | Per-item refund amount. | | `Quantity` | integer | yes | Number of items refunded. | | `transactionAttributes.Id` | string | yes | Original order ID being refunded against. | | `transactionAttributes.Revenue` | decimal | yes | Total refunded amount. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example Refund event ```javascript const refundedProduct = mParticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity refunded ); const transactionAttributes = { Id: 'ORDER-10482', // original order ID Revenue: 129.95 // total refunded amount }; mParticle.eCommerce.logProductAction( mParticle.ProductActionType.Refund, [refundedProduct], { currency: 'USD' }, null, transactionAttributes ); ``` ##### Commerce event: Site search Log when the customer runs a site search. Since site searches do not have a native `ProductActionType`, use `mParticle.logEvent` with the event name `"search"` and `EventType.Search`. Include the search string and the number of results returned. | 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 ```javascript mParticle.logEvent( "search", mParticle.EventType.Search, { searchstring: "blue running shoes", resultcount: 24 } ); ``` ### Event category: Custom event Track anything else with `mParticle.logEvent`. Pass an event name, an `EventType` that categorizes it, and a free-form attributes object describing what happened. | Field | Type | Required | Description | | ------------ | --------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `event_name` | string | yes | A name identifying the event (e.g. `"video_play"`, `"newsletter_subscribed"`). | | `event_type` | EventType | yes | Categorizes the event. Supported: `Navigation`, `Location`, `Search`, `Transaction`, `UserContent`, `UserPreference`, `Social`, `Other`. Defaults to `Unknown` if omitted. | | `attributes` | object | no | Key-value pairs describing the event. Values can be strings, numbers, booleans, or null. | One custom event Rokt recommends firing is **Ready to Checkout** — log this at the point in the checkout flow where the customer has entered their payment details and is about to confirm the order, but before they click **Place Order** or **Buy Now**. This helps Rokt optimize the selection process so that offers are displayed as quickly as possible on the following page. Trigger the event once all of the following conditions are met: - The cart is finalized (items and quantities confirmed). - A shipping method has been selected. - A payment method has been entered or selected. - The user is on the final review/payment screen, prior to order submission. Include any final attributes that were not available earlier in the session (for example, `payment_method_type`, which is typically only known at this stage). #### Example custom event: Ready to Checkout ```javascript window.mParticle.ready(function() { mParticle.logEvent( 'Ready to Checkout', mParticle.EventType.Transaction, { checkout_ready: 'true', payment_method_type: 'credit_card', shipping_address_verified: 'true', shippingmethod: 'express', cartitemcount: '3', cart_total: '430.00', currency: 'USD', }, ); }); ``` ## 5. Show a Placement On every payment and confirmation page where you want Rokt to render content, call `selectPlacements` with one of the supported page identifiers and the customer + transaction attributes. Rokt uses these signals to choose and render the most relevant offer. Attributes passed in `selectPlacements` override any earlier values set via `setUserAttribute`. Always supply the most recent value. If Rokt loads offers early for your account, also set these attributes during checkout with the same values, apart from order-only details such as the order number. See [Set confirmation-page attributes during checkout](https://docs.rokt.com/integration-guides/ecommerce/sdk/web/#set-confirmation-page-attributes-during-checkout). **Page identifiers** - `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. Provide the attributes relevant to the page and Rokt product you are implementing. The full attribute reference is below. ### Show all placement attributes | Field | Type | Description | | ------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `email` | string | Customer email address (unhashed). Used for identity resolution and Shoppable Ads order confirmation. | | `firstname` | string | Customer first name. Used for personalization and Shoppable Ads order fulfillment. | | `lastname` | string | Customer last name. Used for personalization and Shoppable Ads order fulfillment. | | `mobile_number` | string | Customer mobile number (E.164 format, e.g. `+13125551515`). Used for identity resolution and Shoppable Ads. | | `confirmationref` | string | Order / confirmation reference number. Used for relevance, deduplication, and Shoppable Ads order reconciliation. | | `currency` | string | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`, `JPY`). Used for relevance and Shoppable Ads. | | `country` | string | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance. | | `language` | string | Customer's preferred language (ISO 639-1, e.g. `en`, `de`, `fr`). Used for relevance. | | `totalprice` | decimal | Total cart value including tax and shipping. Used for relevance. | | `amount` | decimal | Cart subtotal before tax and shipping. Distinct from `totalprice`. Required for Pay+ on the payments page; also used by Shoppable Ads. | | `cartItems` | array | Structured array of cart-line objects. Used for relevance. | | `couponcode` | string | Promo code applied, if any. Used for 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. | | `value` | decimal | Customer cumulative purchase value. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (`vip`, `at_risk`, `new`, `reactivated`). Used for relevance. | | `paymenttype` | string | Payment method selected (`credit_card`, `paypal`, `apple_pay`, `gift_card`). Used for Pay+ eligibility and Shoppable Ads payment method prioritization. | | `paymentServiceProvider` | string | Comma-separated list of payment methods accepted on the page (e.g. `"cardpayment,paypal"`). Accepted values: `applepay`, `googlepay`, `paypal`, `venmo`, `affirm`, `afterpay`, `klarna`, `alipay`, `amazonpay`, `cardpayment`, `rakutenpay`. Must be camelCase. 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 / region. Used for relevance. | | `billingzipcode` | string | Billing ZIP / postcode. Used for identity resolution, relevance, and Shoppable Ads. | | `billingname` | string | Full cardholder name on the billing address. Used for identity resolution and payment validation. | | `shippingmethod` | string | Shipping method selected (`standard`, `express`, `next_day`). Used for relevance. | | `shippingname` | string | Full recipient name on the shipping address. Used for Shoppable Ads order fulfillment. | | `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 / region. Used for relevance and Shoppable Ads order fulfillment. | | `shippingzipcode` | string | Shipping ZIP / 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. | | `partnerpaymentreference` | string | Non-guessable identifier used to look up the customer's vaulted payment method. Required for Shoppable Ads card forwarding; if absent, card forwarding is unavailable. | | `last4digits` | string | Last 4 digits of the card used on the primary transaction. Displayed to the customer for confirmation during Shoppable Ads. | | `plcc` | string | `"yes"` or `"no"` — whether the customer has a private-label credit card with the partner. Used for Pay+ relevance. | | `discountamount` | decimal | Order-level discount applied (e.g. `10.00`). Used for Pay+ relevance. | | `prescreen` | string | `"yes"` or `"no"` — whether the customer has pre-qualified for a credit offer. Used for Pay+ relevance. | > **Note** > > Do not send products as comma-separated strings or as a stringified JSON blob. Send a real JSON array so the fields stay typed. Expand the product fields below for the expected per-line shape. ### Show product fields | 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 the top-level `couponcode` placement attribute. | | `productposition` | integer | 1-indexed rank of the product within a list or search results. | ### Placement position: Overlay Overlay placements render on top of your confirmation page in a Rokt-managed container, requiring no changes to your page's DOM. Used for standard Thanks Ads and Shoppable Ads. To insert an overlay placement, call `selectPlacements` once the confirmation page loads: #### Example overlay selectPlacements call ```javascript window.mParticle.ready(async function () { const selection = await window.mParticle.Rokt.selectPlacements({ identifier: "prod.rokt.conf", attributes: { // Identity email: "j.smith@example.com", firstname: "Jenny", lastname: "Smith", mobile_number: "+13125551515", // Transaction confirmationref: "ORDER-10482", 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 + paymentServiceProvider for Pay+) paymenttype: "credit_card", paymentServiceProvider: "cardpayment", ccbin: "411112", // Cart contents — see the Placement Attributes reference for the full // attribute set and the Product reference for the per-line shape. cartItems: [ { cartitemid: "SKU-001", productsku: "SKU-001", productname: "Trail Runner v3", itemprice: 129.95, quantity: 1 } ] } }); }); ``` ### Placement position: Embedded Embedded placements render inline at a fixed position you control on the page (for example, above the payment options on a cart page). Both Thanks and Pay+ use embedded placements; Pay+ must use embedded placements. #### 1. Add a placeholder div Add a `
` with the container ID provided by your Rokt account manager at the position where you want the placement to render. Coordinate with your Rokt account manager to choose the container name. ##### Embedded placeholder div ```html
``` #### 2. Call selectPlacements Call `selectPlacements` with the corresponding page identifier. For SPA navigations, close the placement when the customer leaves the page so it doesn't linger on back-navigation. ##### Example embedded selectPlacements call ```javascript window.mParticle.ready(async function () { const selection = await window.mParticle.Rokt.selectPlacements({ identifier: "prod.rokt.payments", attributes: { // Identity email: "j.smith@example.com", firstname: "Jenny", lastname: "Smith", mobile_number: "+13125551515", // Transaction currency: "USD", country: "US", language: "en", totalprice: 149.99, amount: 129.95, couponcode: "SUMMER20", // Customer context newcustomer: false, customertype: "logged_in", value: 2340.00, subscriptionstatus: "active", customersegment: "vip", // Payment (paymenttype + paymentServiceProvider required for Pay+) paymenttype: "credit_card", paymentServiceProvider: "cardpayment", ccbin: "411112", // Cart contents — see the Placement Attributes reference for the full // attribute set and the Product reference for the per-line shape. cartItems: [ { cartitemid: "SKU-001", productsku: "SKU-001", productname: "Trail Runner v3", itemprice: 129.95, quantity: 1 } ] } }); // Call selection.close() only when the customer leaves this page, // such as from your SPA's route-change or component-unmount handler. if (selection) { selection.close(); } }); ``` > **Caution: Pay+** > > If you are using Pay+, you must close your placement after the user navigates away to prevent it from lingering on back-navigation. > **Note** > > Pay+ requires that your existing confirmation-page `selectPlacements` call already passes `paymenttype` along with the rest of the placement attributes above. Without `paymenttype` on the confirmation page, Rokt cannot attribute share-of-wallet correctly. Validate your confirmation-page integration with your Rokt account manager before enabling Pay+ in production. ### Placement position: Interstitial Interstitial placements render between the payment and confirmation pages, allowing customers to purchase additional products. Used by Shoppable Ads. #### 1. Wrap your page content Wrap your confirmation page content in the `` tag. Rokt strips the wrapped content when an offer is available and renders the full-screen experience in its place. If no offer is eligible, the wrapped content renders normally. ##### Interstitial wrapper ```html ``` #### 2. Call selectPlacements Call `selectPlacements` with the confirmation-page identifier and include all available placement attributes so Rokt can evaluate eligibility and render the offer. ##### Example interstitial selectPlacements call ```javascript window.mParticle.ready(async function () { await window.mParticle.Rokt.selectPlacements({ identifier: "prod.rokt.conf", // use stg.rokt.conf in test environments attributes: { // Identity email: "j.smith@example.com", firstname: "Jenny", lastname: "Smith", mobile_number: "+13125551515", // Transaction confirmationref: "ORDER-10482", 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 paymenttype: "credit_card", paymentServiceProvider: "cardpayment", ccbin: "411112", // Cart contents — see the Placement Attributes reference for the full // attribute set and the Product reference for the per-line shape. cartItems: [ { cartitemid: "SKU-001", productsku: "SKU-001", productname: "Trail Runner v3", itemprice: 129.95, quantity: 1 } ] } }); }); ``` The `` wrapper also emits lifecycle events you can subscribe to for custom behavior (covered in the next step). > **Note** > > If your site embeds Rokt inside a cross-origin iframe, add `allow="payment"` to the iframe element so payment APIs work inside the Interstitial experience. **`` attributes** The `` element accepts the following optional attributes to customize loading behavior: | Attribute | Description | Default | | ------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- | | `loader` | Optional URL to a loading-indicator GIF displayed while Rokt selects a placement for the confirmation page. Supply your own asset to override the default. | Rokt loading indicator | | `fallback-timeout` | Duration in milliseconds before placement selection times out and the native confirmation page renders. | `5000` | | `partner-opt-in` | Force-enable the Interstitial experience. Usage: ``. | — | | `partner-opt-out` | Force-skip the Interstitial experience. Usage: ``. | — | ## 6. Subscribe to Placement Events The SDK+ emits events throughout the placement lifecycle. Subscribe to these events to run custom logic when a placement is ready, when a customer engages with a placement, or when an interstitial placement wrapper transitions between states. ### Lifecycle event source: Placement lifecycle By subscribing to a [placement event](https://docs.rokt.com/developer-reference/sdks/web-sdk/event-based-integration) you can receive notifications that are triggered when a placement is ready or when a customer engages with an offer. #### Subscribe to Placement Events ```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 }); }); ``` ### Lifecycle event source: Interstitial lifecycle The `` element used by the interstitial format emits lifecycle events you can subscribe to for custom behavior. Register listeners with `window.mParticle.Rokt.onShoppableAdsReady()` — this callback fires as soon as the element is ready, so listeners registered before `mParticle.ready()` are never missed. #### Show rokt-thank-you lifecycle events | Event | Emitted when | | ------------------------------------- | ------------------------------------------------------------------------ | | `THANK_YOU_ELEMENT_LOADING_INITIATED` | The `` element enters its loading state. | | `THANK_YOU_ELEMENT_COMPLETE` | The `` element renders the partner confirmation content. | #### Subscribe to rokt-thank-you events ```javascript // Register listeners before mParticle.ready() so events are never missed. window.mParticle.Rokt.onShoppableAdsReady(() => { window.RoktThankYouElement.on('THANK_YOU_ELEMENT_LOADING_INITIATED').subscribe(() => { // Triggered when the Thank You element enters its loading state }); window.RoktThankYouElement.on('THANK_YOU_ELEMENT_COMPLETE').subscribe(() => { // Triggered when the Thank You element renders the partner confirmation }); }); window.mParticle.ready(() => { // Standard mParticle initialization continues here }); ``` ## 7. Appendix Reference for closing placements in single-page apps, enabling extensions for advanced features, and configuring optional launcher behavior. ### Close a placement In single-page apps, call `.close()` on the placement object after the user navigates away so the placement doesn't linger on back-navigation. ### Close a placement ```javascript // Closes the placement called 'selection' selection.close(); ``` > **Caution: Pay+** > > If you are using Pay+, you must close your placement after the user navigates away. ### Additional functionality via extensions 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 }, }); }) ``` ### Passing additional integration launcher options Configure optional behavior by setting `window.mParticle.config.launcherOptions` in the initialization script: ### launcherOptions config ```javascript // Include this as part of the initialization script in step 1 above window.mParticle.config.launcherOptions = { noFunctional: true, noTargeting: true, // See all optional launcher options that can be set below }; ``` #### Manage customer cookie preferences Use these options to respect customer opt-out preferences by disabling functional or targeting cookies. | Parameter | Type | Default | Description | | -------------- | ------- | ------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `noFunctional` | boolean | `false` | Set to `true` to prevent Rokt from using first-party tracking IDs when the customer has opted out of functional cookies. Functional cookies power first-party personalization and advanced checkout features like Upsells. | | `noTargeting` | boolean | `false` | Set to `true` to prevent Rokt from using cross-site tracking IDs for the session when the customer has opted out of targeting cookies. Functional identifiers remain active unless `noFunctional` is also set to `true`. | For a more detailed discussion, see [Cookie Consent Flags](https://docs.rokt.com/developer-reference/sdks/web-sdk/cookie-consent-flags). #### Measure page load performance in single-page apps Provide the timestamp of when a virtual page loads so Rokt can accurately measure performance in SPAs and detect anomalies that could impact the customer experience. | Parameter | Type | Default | | ------------------- | ---- | --------------------------------------------------------------------------------------------------------------------------- | | `pageInitTimestamp` | Date | [`PerformanceNavigationTiming.responseStart`](https://developer.mozilla.org/en-US/docs/Web/API/PerformanceNavigationTiming) | When the launcher initializes on a virtual page in an SPA, pass the timestamp of when that page was initialized so Rokt can measure load performance relative to the page that triggered it. #### Link customer activity with a Rokt session ID Pass a previously generated Rokt session ID to ensure activity across different parts of the experience is tied together correctly. If your backend already holds the session JWT for that visit, pass it as `sessionToken` alongside `sessionId` so the front-end session continues the same visit. | Parameter | Type | Default | | -------------- | ------ | ------- | | `sessionId` | string | — | | `sessionToken` | string | — | `sessionId` and `sessionToken` are separate values. The session ID identifies the visit, while the session token is the JWT that authenticates requests for that visit. From a [Session API offers](https://docs.rokt.com/integration-guides/ecommerce/server-to-server/session-api-offers) response (`POST /v2/sessions/offers`), pass `session_id` as `sessionId` and `session_token.token` as `sessionToken`. Rokt sends the token as a bearer on the first offers and events requests. Use the `sessionToken` minted by the Session API for the current visit. The API returns `expires_at` as Unix epoch milliseconds; expired tokens are rejected on the first request, so do not persist and replay expired tokens. `sessionToken` requires `sessionId`. If you pass `sessionToken` without `sessionId`, it is ignored. The same `launcherOptions` fields seed a WebView from a native session. After a native placement, call `getSession` on Android or iOS, then set `sessionId` and `sessionToken` on `launcherOptions` **before** `mParticle.init`. If you only pass `sessionId`, Transactions mints a token for that visit. Platform examples are in each native SDK+ appendix (Sharing a session between web and native). ### Pass sessionId and sessionToken ```javascript window.mParticle.config.launcherOptions = { sessionId: "aec20024-8d23-46be-95f3-9be5d86292a9", // session_id from /v2/sessions/offers sessionToken: "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", // session_token.token from the same response }; ``` #### Customize how links open in your experience Enable this option when you want full control over how Rokt and advertiser links open (for example, inside a WebView instead of a browser). | Parameter | Type | Default | | ------------------------ | ------- | ------- | | `overrideLinkNavigation` | boolean | `false` | When set to `true`, Rokt stops handling link openings directly and instead emits a `LINK_NAVIGATION_REQUEST` partner event. To distinguish Rokt and advertiser links from your own, check whether the URL contains `"rokt.com"`. Subscribe to the event on the selection returned by `selectPlacements`. Each event carries a single `url` string — the link the customer activated. Open it immediately, in whichever context you need (a browser tab, a WebView, and so on), to avoid degrading the customer experience. ### Handle LINK\_NAVIGATION\_REQUEST ```javascript const selection = await window.mParticle.Rokt.selectPlacements({ // identifier and attributes as in the examples above }); selection.on("LINK_NAVIGATION_REQUEST").subscribe((event) => { // event.url is the link the customer activated — open it immediately window.open(event.url); }); ``` If a `LINK_NAVIGATION_REQUEST` event is not consumed within 3 seconds, an error is raised — make sure an active subscription exists to process the event. ## 8. Test Your Integration To confirm the SDK+ initializes, identifies the user, logs events, and requests offers correctly: ### 1. Open a new browser window Open a new browser window so you start with a clean state. ### 2. Open developer tools Open your browser's developer tools panel. For most browsers, you can do this by right-clicking on your screen and clicking **Inspect**. Enable the option that keeps network requests across page loads (**Preserve log** in Chrome, Edge, and Safari, or **Persist Logs** in Firefox). ### 3. Filter network requests From the developer tools panel, go to the **Network** tab and filter for `rokt-api.com`. If you use a [custom first-party domain](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/), filter for your custom subdomain instead. To find a specific request, you can also filter by its endpoint name: `/identity`, `/events`, `/experiences`, or `/offers`. ### 4. Run the test journey With the **Network** tab recording, complete a test journey through the pages where you integrated the SDK+. > **Note** > > Open the developer tools panel before navigating to your site so the browser records all SDK+ requests. ### 5. Verify the identity request Filter for `/identity`, then select the request that matches the identity action you performed, such as `identify` or `login`. Confirm that it has a successful status, then check the **Payload** or **Request** tab for the test identifiers you expected to send. ### 6. Verify page-view and commerce events Filter for `/events`, then select the request generated during your test journey. Confirm that it has a successful status. Check the **Payload** or **Request** tab to verify that the request contains the page-view or commerce event and the attributes you expected to send. Event requests can contain multiple events, so inspect the complete request payload when checking for a specific action. ### 7. Verify the selection request Filter for `/experiences`, then for `/offers`. Depending on your SDK+ routing, a successful selection uses one of these endpoints: - `/v1/experiences` - `/v2/sessions/offers` You only need to see one of these requests. Select the request with a 200 status, then check the **Payload** or **Request** tab to verify the data being shared with Rokt. > **Note** > > During testing, you might also see a `/v1/experiences` request with a 204 status. Use the request with a 200 status when checking the payload. ### Troubleshooting If your integration isn't working, check the **Console** tab in your browser's developer tools for Rokt SDK+ errors. Common issues include: #### Initialization errors - Make sure the SDK+ initialization script has been placed on the correct page. - If you integrated using a tag manager, make sure you configured your tag triggers so that initialization loads on the right pages, and that your `selectPlacements` and conversion logging tags are firing after the SDK+ has initialized. #### Syntax errors Make sure you are not missing any commas in your integration code. To check for syntax errors: ### 1. Open the Console tab Go to your browser's developer tools panel and select the **Console** tab. ### 2. Find the error If the file where you placed the Web SDK+ has an error, it should be logged in the console. Click the file to see the code and reported error. ### 3. Verify commas Any error is indicated in the file. In particular, check that all attributes are separated by commas as shown below. #### Missing comma (incorrect) ```javascript email: '' mobile_number: '', ``` #### Correct ```javascript email: '', mobile_number: '', ``` # iOS 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/ios/](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/) > > 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 iOS 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 iOS App Rokt SDK+ requires a minimum deployment target of **iOS 15.0**. Use Swift Package Manager or CocoaPods — whichever your project already uses. ### 1. Add the Rokt SDK+ to your iOS app #### Install method: Swift Package Manager > **Note: Bazel builds** > > If you build with Bazel, use this Swift Package Manager path and generate Bazel targets from your `Package.swift` and `Package.resolved` with [`rules_swift_package_manager`](https://github.com/cgrindel/rules_swift_package_manager). Rokt SDK+ and its dependencies are plain Swift source packages — none declare a SwiftPM `binaryTarget` — so they generate cleanly. Importing prebuilt `.xcframework` artifacts instead is not recommended: module names differ from the SwiftPM product names, and not every transitive dependency is vendored in the artifact. In Xcode, select **File → Add Package Dependencies**, enter **`https://github.com/ROKT/rokt-sdk-plus-ios.git`**, 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"), ] ``` > **Note** > > When resolving dependencies through a package registry instead of GitHub, configure SwiftPM’s client-side dependency mirroring and restart Xcode after any configuration changes, since Xcode reads and caches the settings when the project opens. #### Install method: CocoaPods Add the Rokt SDK+ pod to your `Podfile`: ##### Podfile ```ruby pod 'RoktSDKPlus', '~> 9.2' ``` ## 2. Initialize the Rokt SDK+ Insert the following initialization snippet in your AppDelegate file. Replace `your-key` and `your-secret` with the key and secret provided by your Rokt team. ### AppDelegate initialization ```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) // Customer phone number in E.164 format. identifyRequest.setIdentity("+13125551515", identityType: .phoneNumber) // If you can only provide a SHA-256-hashed mobile number, set it in 'other4' instead of 'phoneNumber' — do not pass both. identifyRequest.setIdentity("sha256 hashed mobile goes here", identityType: .other2) // 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.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. In your app, provide only the Apple Pay merchant ID and/or `urlScheme` when creating `RoktPaymentExtension`. At least one of `applePayMerchantId` or `urlScheme` must be provided; the initializer returns `nil` if both are omitted. ### AppDelegate initialization ```objectivec #import - (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: // If you do not have the user's email address, you can pass in a null value 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]; // Customer phone number in E.164 format. [identifyRequest setIdentity:@"+13125551515" identityType:MPIdentityPhoneNumber]; // If you can only provide a SHA-256-hashed mobile number, use MPIdentityOther2 instead — do not pass both. [identifyRequest setIdentity:@"sha256 hashed mobile goes here" identityType:MPIdentityOther2]; 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; } ``` When inserting the initialization snippet into your AppDelegate, you will see customizable fields for: ### 1. Entering your Rokt key and secret Set `key` and `secret` to the values provided by your Rokt account manager. ### 2. Setting your data environment Set `environment` to `.development` (Swift) or `MPEnvironmentDevelopment` (Objective-C) while testing to route data to the Development environment, and `.production` or `MPEnvironmentProduction` to send live customer activity to Production. ### 3. Entering a custom first-party domain Follow the instructions in [First-Party Domain Configuration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/), and set `customBaseURL` on `MPNetworkOptions` to your custom subdomain. Routing the Rokt SDK+ through your own domain reduces the risk of ad blockers and browsers from blocking ads or data. Omit `options.networkOptions` to send traffic to Rokt's default endpoints. ### 4. Identifying your user and setting attributes In `identifyRequest`, pass the user's raw, un-hashed email in the `email` property. For hashed emails and other identifiers, see [Supported User Identifiers](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#supported-user-identifiers). Once identified, use the `onIdentifyComplete` callback to set additional user attributes — see [User Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#user-attributes) for the recommended list. #### onIdentifyComplete ```swift options.onIdentifyComplete = {(result: MPIdentityApiResult?, error: Error?) in if let user = result?.user { user.setUserAttribute("example attribute key", value: "example attribute value") } } ``` #### onIdentifyComplete ```objectivec options.onIdentifyComplete = ^(MPIdentityApiResult *_Nullable apiResult, NSError *_Nullable error) { if (apiResult) { [apiResult.user setUserAttribute:@"example attribute key" value:@"example attribute value"]; } }; ``` > **Note** > > Always include `identifyRequest` in the initialization snippet. If you don't have the user's email at initialization, omit the assignment — the SDK+ will still initialize, and you can identify the user later via [3. Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#identify-ios). See [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#error-handling-ios) for how to inspect the `error` argument — without error handling you may see data consistency issues at scale. ### 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 — pass `applePayMerchantId` for Apple Pay, `urlScheme` for Afterpay / Clearpay, or both. See [Appendix E: Configure Shoppable Ads payments](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#appendix-e-configure-shoppable-ads-payments). The extension is created and registered in Swift; in an Objective-C app, do this from a small Swift file. ## 3. Identify the User The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#initialize-ios) 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 | Assign the customer's raw, unhashed email address to `identifyRequest.email`. | | `emailSha256` | string | SHA-256 hashed email (iOS path). Pass via `identifyRequest.setIdentity(hashedEmail, identityType: .other)`. Use instead of `email` when only the hashed form is available. | | `mobileSha256` | string | SHA-256 hashed mobile number (iOS path). Pass via `identifyRequest.setIdentity(hashedMobile, identityType: .other4)`. | | `mobile` | string | Phone number in E.164 format. Pass via `identifyRequest.setIdentity(mobileNumber, identityType: .phoneNumber)`. | | `customerid` | string | Assign your internal customer/account identifier to `identifyRequest.customerId`. | To identify the user: ### 1. Create an identifyRequest object Create an `identifyRequest` object containing the user's identifiers. ### 2. Create an identityCallback Create an `identityCallback` to set additional user attributes once identify succeeds. ### 3. Send the request using the method that matches the user's action Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action: - **`MParticle.sharedInstance().identity.login`:** call when the user logs in or creates an account. - **`MParticle.sharedInstance().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.sharedInstance().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 Jane Smith ```swift // 1. Create the identifyRequest object let identifyRequest = MPIdentityApiRequest.withEmptyUser() identifyRequest.email = "j.smith@example.com" // Customer phone number in E.164 format. identifyRequest.setIdentity("+13125551515", identityType: .phoneNumber) // If you can only provide a SHA-256-hashed mobile number, use .other2 instead of .phoneNumber — do not pass both. identifyRequest.setIdentity("SHA-256 hashed mobile number", identityType: .other2) // 2. User attributes are set using identityCallback let identityCallback = {(result: MPIdentityApiResult?) in if let user = result?.user { user.setUserAttribute("firstname", value: "Jane") user.setUserAttribute("lastname", value: "Smith") } } // 3. Call one of the following methods that best matches the user's action: MParticle.sharedInstance().identity.login(identifyRequest, completion: identityCallback) // Call when the user logs in or creates an account MParticle.sharedInstance().identity.identify(identifyRequest, completion: identityCallback) // Call when you obtain the user's email mid-session, but not during a login MParticle.sharedInstance().identity.logout() // Call when the user logs out ``` #### Identify Jane Smith ```objectivec // 1. Create the identifyRequest object MPIdentityApiRequest *identifyRequest = [MPIdentityApiRequest requestWithEmptyUser]; identifyRequest.email = @"j.smith@example.com"; // Customer phone number in E.164 format. [identifyRequest setIdentity:@"+13125551515" identityType:MPIdentityPhoneNumber]; // If you can only provide a SHA-256-hashed mobile number, use MPIdentityOther2 instead — do not pass both. [identifyRequest setIdentity:@"SHA-256 hashed mobile number" identityType:MPIdentityOther2]; // 2. User attributes are set using identityCallback id identityCallback = ^(MPIdentityApiResult *_Nullable apiResult) { if (apiResult) { [apiResult.user setUserAttribute:@"firstname" value:@"Jane"]; [apiResult.user setUserAttribute:@"lastname" value:@"Smith"]; } }; // 3. Call one of the following methods that best matches the user's action: [[[MParticle sharedInstance] identity] login:identifyRequest completion:identityCallback]; // Call when the user logs in or creates an account [[[MParticle sharedInstance] identity] identify:identifyRequest completion:identityCallback]; // Call when you obtain the user's email mid-session, but not during a login [[[MParticle sharedInstance] 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 ```swift import mParticle_Apple_SDK // Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method. let currentUser = MParticle.sharedInstance().identity.currentUser // Once you have successfully set the current user to `currentUser`, you can set user attributes with: currentUser?.setUserAttribute("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("firstname", value: "John") currentUser?.setUserAttribute("lastname", value: "Doe") // Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901' currentUser?.setUserAttribute("mobile", value: "3125551515") currentUser?.setUserAttribute("age", value: "33") currentUser?.setUserAttribute("gender", value: "M") currentUser?.setUserAttribute("billingcity", value: "Brooklyn") currentUser?.setUserAttribute("billingstate", value: "NY") currentUser?.setUserAttribute("billingzipcode", value: "123456") currentUser?.setUserAttribute("dob", value: "yyyymmdd") currentUser?.setUserAttribute("title", value: "Mr") currentUser?.setUserAttribute("language", value: "en") currentUser?.setUserAttribute("predictedltv", value: "136.23") // You can create a user attribute to contain a list of values currentUser?.setUserAttributeList("favorite-genres", values: ["documentary", "comedy", "romance", "drama"]) // To remove a user attribute, call removeUserAttribute and pass in the attribute name. All user attributes share the same key space. currentUser?.removeUserAttribute("attribute-to-remove") ``` ### Set User Attributes ```objectivec #import // Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method. MParticleUser *currentUser = [[[MParticle sharedInstance] identity] currentUser]; // Once you have successfully set the current user to `currentUser`, you can set user attributes with: [currentUser setUserAttribute:@"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:@"firstname" value:@"John"]; [currentUser setUserAttribute:@"lastname" value:@"Doe"]; // Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901' [currentUser setUserAttribute:@"mobile" value:@"3125551515"]; [currentUser setUserAttribute:@"age" value:@"33"]; [currentUser setUserAttribute:@"gender" value:@"M"]; [currentUser setUserAttribute:@"billingcity" value:@"Brooklyn"]; [currentUser setUserAttribute:@"billingstate" value:@"NY"]; [currentUser setUserAttribute:@"billingzipcode" value:@"123456"]; [currentUser setUserAttribute:@"dob" value:@"yyyymmdd"]; [currentUser setUserAttribute:@"title" value:@"Mr"]; [currentUser setUserAttribute:@"language" value:@"en"]; [currentUser setUserAttribute:@"predictedltv" value:@"136.23"]; // You can create a user attribute to contain a list of values [currentUser setUserAttribute:@"favorite-genres" values:@[@"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 | Field | Type | Description | | -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `firstname` | string | Customer's first name. Used for personalization. | | `lastname` | string | Customer's last name. Used for personalization. | | `mobile` | string | Phone number formatted as `1112345678` or `+1 (222) 345-6789`. Used for identity resolution and relevance. | | `birthyear` | integer | Customer's birth year (e.g. `1990`). Preferred date-of-birth field. Alternates: `dob`, `age`. Used for eligibility and relevance. | | `age` | integer | Customer's age. Alternate to `dob`. Used for eligibility and relevance. | | `dob` | string | Date of birth, `yyyymmdd`. Alternate to `age`. Used for eligibility and relevance. | | `gender` | string | Customer's gender. For example, `M`, `F`, `Male`, or `Female`. Used for relevance. | | `title` | string | Honorific. For example, `Mr`, `Mrs`, `Ms`. Used for personalization. | | `language` | string | ISO 639-1 language code associated with the purchase. Used for relevance. | | `billingaddress1` | string | Street address (e.g. `123 Main St`). Used for identity resolution and relevance. | | `billingaddress2` | string | Apartment/unit (e.g. `Apt 4B`). Used for identity resolution. | | `billingcity` | string | Billing city. Used for relevance. | | `billingstate` | string | Billing state / province / region. Used for relevance and eligibility. | | `billingzipcode` | string | Full ZIP or postcode (US preference is ZIP+4). Used for identity resolution and relevance. | | `country` | string | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `AU`). Used for eligibility and relevance. | | `newcustomer` | boolean | Whether this is a first-time buyer. Used for relevance. | | `customertype` | string | Whether the user is authenticated (`guest` / `logged_in`). Used for relevance. | | `loyaltytier` | string | Partner loyalty program tier. Used for relevance and eligibility. | | `loyaltyid` | string | Loyalty program member ID. Used for identity resolution. | | `predictedltv` | decimal | Predicted total lifetime value, typically from a partner ML model. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance. | | `acquisitionchannel` | string | How the customer was originally acquired. Used for relevance. | All user attributes (including list attributes) must have distinct names. ## 5. Log Events Track screen views, commerce events, and custom events so Rokt can understand where each customer is in their journey. ### Event category: Screen views Call `logScreen` with the name of the screen (e.g. `"homepage"`, `"product_detail_page"`). Include any additional custom attributes in `eventInfo`. #### Log a screen view ```swift MParticle.sharedInstance().logScreen( "homepage", eventInfo: ["custom-attribute": "custom-value"] ) ``` #### Log a screen view ```objectivec [[MParticle sharedInstance] logScreen:@"homepage" eventInfo:@{@"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 `MPCommerceEvent`, using an `MPCommerceEventAction` that identifies the customer action (viewing a product, adding to cart, starting checkout, completing a purchase, etc.). #### Show all product action types | Customer action | Swift action type | Objective-C action type | | -------------------------- | --------------------- | ----------------------------------------- | | Product detail page viewed | `.viewDetail` | `MPCommerceEventActionViewDetail` | | Product clicked | `.click` | `MPCommerceEventActionClick` | | Item added to cart | `.addToCart` | `MPCommerceEventActionAddToCart` | | Item removed from cart | `.removeFromCart` | `MPCommerceEventActionRemoveFromCart` | | Item added to wishlist | `.addToWishlist` | `MPCommerceEventActionAddToWishlist` | | Item removed from wishlist | `.removeFromWishlist` | `MPCommerceEventActionRemoveFromWishlist` | | Checkout flow initiated | `.checkout` | `MPCommerceEventActionCheckout` | | Checkout option selected | `.checkoutOption` | `MPCommerceEventActionCheckoutOption` | | Order confirmed | `.purchase` | `MPCommerceEventActionPurchase` | | Order refunded | `.refund` | `MPCommerceEventActionRefund` | Tracking a commerce event takes three phases: #### 1. Define the product Create an `MPProduct` with the product's name, SKU, quantity, and price. Set additional fields like `category`, `brand`, `variant`, and `position` directly on the instance. ##### Define a product ```swift let product = MPProduct( name: "Double Room - Econ Rate", sku: "econ-1", quantity: 4, price: 100.00 ) product.category = "room" product.brand = "lodge-o-rama" product.variant = "standard" ``` ##### Define a product ```objectivec MPProduct *product = [[MPProduct alloc] initWithName:@"Double Room - Econ Rate" sku:@"econ-1" quantity:@4 price:@100.00]; product.category = @"room"; product.brand = @"lodge-o-rama"; product.variant = @"standard"; ``` #### 2. Summarize the transaction Create an `MPTransactionAttributes` for `Purchase`, `Checkout`, and `CheckoutOption` events. Include shipping and order-level coupons when applicable — order-level coupons belong here, not on individual products. ##### Summarize the transaction ```swift let attributes = MPTransactionAttributes() attributes.transactionId = "ORDER-12345" attributes.revenue = 149.99 attributes.tax = 12.50 attributes.shipping = 5.99 attributes.couponCode = "SUMMER20" ``` ##### Summarize the transaction ```objectivec MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; attributes.transactionId = @"ORDER-12345"; attributes.revenue = @149.99; attributes.tax = @12.50; attributes.shipping = @5.99; attributes.couponCode = @"SUMMER20"; ``` #### 3. Log the commerce event Build an `MPCommerceEvent` with a `MPCommerceEventAction` from the table above, attach the `transactionAttributes` when applicable, and pass it to `MParticle.sharedInstance().logEvent`. 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 `list_name`). | Field | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------------------------- | | `Name` | string | yes | List or category name (e.g. `"Mens Running Shoes"`). Becomes `list_name`. | | `Products` | array | yes | Product objects from `createProduct`. Set `Position` to each item's 1-indexed rank. | | `currency` | string | yes | ISO 4217 currency code (passed as event-level customAttribute). | ###### Example PLP impression ```swift let product = MPProduct( name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95 ) product.position = 1 // 1-indexed rank in the list let event = MPCommerceEvent(impressionName: "Mens Running Shoes", product: product) event.currency = "USD" MParticle.sharedInstance().logEvent(event) ``` ###### Example PLP impression ```objectivec MPProduct *product = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; product.position = 1; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithImpressionName:@"Mens Running Shoes" product:product]; event.currency = @"USD"; [[MParticle sharedInstance] logEvent:event]; ``` ##### Commerce event: ViewDetail Log when a customer opens a product detail page. | Field | Type | Required | Description | | ------------- | ------- | -------- | ----------------------------------- | | `productsku` | string | yes | Product SKU. | | `productname` | string | yes | Display name. | | `itemprice` | decimal | yes | Per-unit price at the time of view. | | `currency` | string | yes | ISO 4217 currency code. | | `list_name` | string | no | Set if the user arrived from a PLP. | ###### Example ViewDetail event ```swift let product = MPProduct( name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95 ) let event = MPCommerceEvent(action: .viewDetail, product: product) event.currency = "USD" event.customAttributes = ["list_name": "PLP-Running"] MParticle.sharedInstance().logEvent(event) ``` ###### Example ViewDetail event ```objectivec MPProduct *product = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionViewDetail product:product]; event.currency = @"USD"; event.customAttributes = @{@"list_name": @"PLP-Running"}; [[MParticle sharedInstance] logEvent:event]; ``` ##### Commerce event: AddToCart Log when a customer adds an item to the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units added. | | `itemprice` | decimal | yes | Per-unit price at time of add. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level coupon, if applied at add-time. | ###### Example AddToCart event ```swift let product = MPProduct( name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95 ) let event = MPCommerceEvent(action: .addToCart, product: product) event.currency = "USD" MParticle.sharedInstance().logEvent(event) ``` ###### Example AddToCart event ```objectivec MPProduct *product = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionAddToCart product:product]; event.currency = @"USD"; [[MParticle sharedInstance] logEvent:event]; ``` ##### Commerce event: RemoveFromCart Log when a customer removes an item from the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ----------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units removed. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example RemoveFromCart event ```swift let product = MPProduct( name: "Trail Runner v3", sku: "SKU-001", quantity: 1, // units removed price: 129.95 ) let event = MPCommerceEvent(action: .removeFromCart, product: product) event.currency = "USD" MParticle.sharedInstance().logEvent(event) ``` ###### Example RemoveFromCart event ```objectivec MPProduct *product = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionRemoveFromCart product:product]; event.currency = @"USD"; [[MParticle sharedInstance] logEvent:event]; ``` ##### Commerce event: Cart page view Log when the customer arrives on the cart page. Since cart page views do not have a native `MPCommerceEventAction`, use `MPEvent` with the event name `"view_cart"` and `.other` event type. Pass the full cart contents as custom attributes. | Field | Type | Required | Description | | --------------- | --------- | -------- | ---------------------------------------------------------- | | `event_name` | string | yes | Always `"view_cart"`. | | `event_type` | EventType | yes | Use `MPEventType.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 the top-level `couponcode` placement attribute. | | `productposition` | integer | 1-indexed rank of the product within a list or search results. | ###### Example cart page view event ```swift if let event = MPEvent(name: "view_cart", type: .other) { event.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] ] ] MParticle.sharedInstance().logEvent(event) } ``` ###### Example cart page view event ```objectivec MPEvent *event = [[MPEvent alloc] initWithName:@"view_cart" type:MPEventTypeOther]; event.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} ] }; [[MParticle sharedInstance] logEvent:event]; ``` ##### 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. | ###### Example Checkout event ```swift let product1 = MPProduct(name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95) let product2 = MPProduct(name: "Cushion Insole", sku: "SKU-002", quantity: 2, price: 19.95) let attributes = MPTransactionAttributes() attributes.revenue = 169.85 attributes.couponCode = "SUMMER20" let event = MPCommerceEvent(action: .checkout, product: product1) event.addProduct(product2) event.transactionAttributes = attributes event.currency = "USD" event.customAttributes = ["cartitemcount": 3] MParticle.sharedInstance().logEvent(event) ``` ###### Example Checkout event ```objectivec MPProduct *product1 = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPProduct *product2 = [[MPProduct alloc] initWithName:@"Cushion Insole" sku:@"SKU-002" quantity:@2 price:@19.95]; MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; attributes.revenue = @169.85; attributes.couponCode = @"SUMMER20"; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionCheckout product:product1]; [event addProduct:product2]; event.transactionAttributes = attributes; event.currency = @"USD"; event.customAttributes = @{@"cartitemcount": @3}; [[MParticle sharedInstance] logEvent:event]; ``` ##### Commerce event: CheckoutOption (shipping) Log when the customer completes the shipping step. Set `checkoutOption` to `"shipping"` and pass the shipping selection 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. | ###### Example CheckoutOption (shipping) event ```swift let product1 = MPProduct(name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95) let product2 = MPProduct(name: "Cushion Insole", sku: "SKU-002", quantity: 2, price: 19.95) let event = MPCommerceEvent(action: .checkoutOption, product: product1) event.addProduct(product2) event.checkoutOption = "shipping" event.currency = "USD" event.customAttributes = [ "shippingmethod": "express", "zipcode": "94103", "country": "US", "totalprice": 169.85 ] MParticle.sharedInstance().logEvent(event) ``` ###### Example CheckoutOption (shipping) event ```objectivec MPProduct *product1 = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPProduct *product2 = [[MPProduct alloc] initWithName:@"Cushion Insole" sku:@"SKU-002" quantity:@2 price:@19.95]; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionCheckoutOption product:product1]; [event addProduct:product2]; event.checkoutOption = @"shipping"; event.currency = @"USD"; event.customAttributes = @{ @"shippingmethod": @"express", @"zipcode": @"94103", @"country": @"US", @"totalprice": @169.85 }; [[MParticle sharedInstance] logEvent:event]; ``` ##### 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. | 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. | ###### Example CheckoutOption (payment) event ```swift let product1 = MPProduct(name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95) let product2 = MPProduct(name: "Cushion Insole", sku: "SKU-002", quantity: 2, price: 19.95) let event = MPCommerceEvent(action: .checkoutOption, product: product1) event.addProduct(product2) event.checkoutOption = "payment" event.currency = "USD" event.customAttributes = [ "paymenttype": "credit_card", "payment_method": "visa", "paymentServiceProvider": "stripe", "ccbin": "424242", "totalprice": 169.85 ] MParticle.sharedInstance().logEvent(event) ``` ###### Example CheckoutOption (payment) event ```objectivec MPProduct *product1 = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPProduct *product2 = [[MPProduct alloc] initWithName:@"Cushion Insole" sku:@"SKU-002" quantity:@2 price:@19.95]; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionCheckoutOption product:product1]; [event addProduct:product2]; event.checkoutOption = @"payment"; event.currency = @"USD"; event.customAttributes = @{ @"paymenttype": @"credit_card", @"payment_method": @"visa", @"paymentServiceProvider": @"stripe", @"ccbin": @"424242", @"totalprice": @169.85 }; [[MParticle sharedInstance] logEvent:event]; ``` ##### 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. | ###### Example Purchase event ```swift let product1 = MPProduct(name: "Trail Runner v3", sku: "SKU-001", quantity: 1, price: 129.95) let product2 = MPProduct(name: "Cushion Insole", sku: "SKU-002", quantity: 2, price: 19.95) let attributes = MPTransactionAttributes() attributes.transactionId = "ORDER-10482" attributes.revenue = 169.85 attributes.tax = 14.20 attributes.shipping = 5.99 attributes.couponCode = "SUMMER20" let event = MPCommerceEvent(action: .purchase, product: product1) event.addProduct(product2) event.transactionAttributes = attributes event.currency = "USD" event.customAttributes = ["cartitemcount": 3] MParticle.sharedInstance().logEvent(event) ``` ###### Example Purchase event ```objectivec MPProduct *product1 = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPProduct *product2 = [[MPProduct alloc] initWithName:@"Cushion Insole" sku:@"SKU-002" quantity:@2 price:@19.95]; MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; attributes.transactionId = @"ORDER-10482"; attributes.revenue = @169.85; attributes.tax = @14.20; attributes.shipping = @5.99; attributes.couponCode = @"SUMMER20"; MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionPurchase product:product1]; [event addProduct:product2]; event.transactionAttributes = attributes; event.currency = @"USD"; event.customAttributes = @{@"cartitemcount": @3}; [[MParticle sharedInstance] logEvent:event]; ``` ##### 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. | ###### Example Refund event ```swift let refundedProduct = MPProduct( name: "Trail Runner v3", sku: "SKU-001", quantity: 1, // units refunded price: 129.95 ) let attributes = MPTransactionAttributes() attributes.transactionId = "ORDER-10482" // original order id attributes.revenue = 129.95 // refunded amount let event = MPCommerceEvent(action: .refund, product: refundedProduct) event.transactionAttributes = attributes event.currency = "USD" MParticle.sharedInstance().logEvent(event) ``` ###### Example Refund event ```objectivec MPProduct *refundedProduct = [[MPProduct alloc] initWithName:@"Trail Runner v3" sku:@"SKU-001" quantity:@1 price:@129.95]; MPTransactionAttributes *attributes = [[MPTransactionAttributes alloc] init]; attributes.transactionId = @"ORDER-10482"; // original order id attributes.revenue = @129.95; // refunded amount MPCommerceEvent *event = [[MPCommerceEvent alloc] initWithAction:MPCommerceEventActionRefund product:refundedProduct]; event.transactionAttributes = attributes; event.currency = @"USD"; [[MParticle sharedInstance] logEvent:event]; ``` ### Event category: Custom events Track custom events with `MPEvent`, passing an event name, event type, and optional custom attributes. #### Show custom event types **Swift** | Type | Use for | | ----------------- | ----------------------------------------------------------- | | `.navigation` | User navigation flows and page transitions within your app. | | `.location` | Location-based interactions and movements. | | `.search` | Search queries and search-related actions. | | `.transaction` | Financial transactions and purchase-related activity. | | `.userContent` | User-generated content like reviews, comments, or posts. | | `.userPreference` | User settings, preferences, and customization choices. | | `.social` | Social media interactions and sharing activities. | | `.other` | Anything that doesn't fit the categories above. | **Objective-C** | Type | Use for | | --------------------------- | ----------------------------------------------------------- | | `MPEventTypeNavigation` | User navigation flows and page transitions within your app. | | `MPEventTypeLocation` | Location-based interactions and movements. | | `MPEventTypeSearch` | Search queries and search-related actions. | | `MPEventTypeTransaction` | Financial transactions and purchase-related activity. | | `MPEventTypeUserContent` | User-generated content like reviews, comments, or posts. | | `MPEventTypeUserPreference` | User settings, preferences, and customization choices. | | `MPEventTypeSocial` | Social media interactions and sharing activities. | | `MPEventTypeOther` | Anything that doesn't fit the categories above. | #### Log a custom event ```swift if let event = MPEvent(name: "video_watched", type: .navigation) { event.customAttributes = ["category": "Destination Intro", "title": "Paris"] MParticle.sharedInstance().logEvent(event) } ``` #### Log a custom event ```objectivec MPEvent *event = [[MPEvent alloc] initWithName:@"video_watched" type:MPEventTypeNavigation]; if (event) { event.customAttributes = @{ @"category": @"Destination Intro", @"title": @"Paris" }; [[MParticle sharedInstance] logEvent:event]; } ``` ## 6. Show a Placement Call `selectPlacements` on every payment and confirmation screen you want Rokt to render content on. Include one of the following page identifiers to specify the screen type and whether it's for testing or production: - `stg.rokt.conf`: A confirmation screen in a staging (or testing) environment. - `prod.rokt.conf`: A confirmation screen in a production environment. - `stg.rokt.payments`: A payments screen in a staging (or testing) environment. - `prod.rokt.payments`: A payments screen in a production environment. Call `selectPlacements` one time for each screen instance, as early as the screen loads and as soon as all relevant attributes are available. See [Call selectPlacements once per screen](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#call-once-ios) for how to avoid repeat calls. At minimum, pass `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`. See [Placement Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#placement-attributes) for the full list. > **Caution: Pay+** > > For Pay+ placements, include `paymenttype` and `paymentServiceProvider` in the `selectPlacements` call on each page. `paymentServiceProvider` communicates what payment methods are available on the payment page; `paymenttype` communicates what method the user paid with. ### Placement position: Overlay placements #### Overlay placement ```swift import mParticle_Apple_SDK let attributes = [ "email": "test@gmail.com", "firstname": "Jenny", "lastname": "Smith", "billingzipcode": "07762", "confirmationref": "54321" ] MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes) ``` #### Overlay placement ```objectivec #import NSDictionary *attributes = @{ @"email": @"test@gmail.com", @"firstname": @"Jenny", @"lastname": @"Smith", @"billingzipcode": @"07762", @"confirmationref": @"54321" }; [[[MParticle sharedInstance] rokt] selectPlacements:@"RoktExperience" attributes:attributes]; ``` ### Placement position: Embedded placements Embedded placements share the same attribute requirements as overlay placements but render the placement view inside your own UI. Use the `onEvent` callback to respond to placement events (load, unload, loading indicator, embedded size change, etc.). Event types are subclasses of `RoktEvent` (from the `RoktContracts` package); check the event type in your callback. #### Embedded placement with onEvent ```swift import mParticle_Apple_SDK let attributes = [ "email": "test@gmail.com", "firstname": "Jenny", "lastname": "Smith", "billingzipcode": "07762", "confirmationref": "54321" ] let roktFrame = CGRect(x: 0, y: 0, width: 320, height: 50) let roktView = RoktEmbeddedView(frame: roktFrame) let embeddedViews = ["RoktEmbedded1": roktView] let roktConfig = RoktConfig.Builder().colorMode(.light).build() MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes, embeddedViews: embeddedViews, config: roktConfig) { event in switch event { case let sizeEvent as RoktEvent.EmbeddedSizeChanged: // Example event - Height changed: use sizeEvent.identifier and sizeEvent.updatedHeight // The full list of events is provided below break default: break } } ``` #### Embedded placement with onEvent ```objectivec #import @import RoktContracts; NSDictionary *attributes = @{ @"email": @"test@gmail.com", @"firstname": @"Jenny", @"lastname": @"Smith", @"billingzipcode": @"07762", @"confirmationref": @"54321" }; CGRect roktFrame = CGRectMake(0, 0, 320, 50); RoktEmbeddedView *roktView = [[RoktEmbeddedView alloc] initWithFrame:roktFrame]; NSDictionary *embeddedViews = @{@"RoktEmbedded1": roktView}; RoktConfig *roktConfig = [[[RoktConfigBuilder new] colorMode:RoktColorModeLight] build]; [[MParticle sharedInstance].rokt selectPlacements:@"RoktExperience" attributes:attributes embeddedViews:embeddedViews config:roktConfig onEvent:^(RoktEvent *_Nonnull event) { if ([event isKindOfClass:[RoktEmbeddedSizeChanged class]]) { RoktEmbeddedSizeChanged *sizeEvent = (RoktEmbeddedSizeChanged *)event; // Example event - Height changed: Use sizeEvent.identifier and sizeEvent.updatedHeight // The full list of events is provided below } }]; ``` ### Placement position: Shoppable Ads Shoppable Ads are post-purchase upsell offers with in-app catalog browsing and instant checkout, rendered as an overlay within the Rokt placement. Display them with `selectShoppableAds` rather than `selectPlacements`. A registered `RoktPaymentExtension` is **required** — if none is registered, `selectShoppableAds` fires a `PlacementFailure` event. See [Appendix E: Configure Shoppable Ads payments](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#appendix-e-configure-shoppable-ads-payments). #### Display Shoppable Ads ```swift import mParticle_Apple_SDK let attributes = [ "email": "j.smith@example.com", "firstname": "Jane", "lastname": "Smith", "confirmationref": "ORD-8829-XK2", "amount": "52.25", "currency": "USD", "paymenttype": "visa", "shippingaddress1": "123 Main St", "shippingcity": "Brooklyn", "shippingstate": "NY", "shippingzipcode": "11201", "shippingcountry": "US" ] MParticle.sharedInstance().rokt.selectShoppableAds("ConfirmationPage", attributes: attributes, config: nil) { event in switch event { case let e as RoktEvent.CartItemInstantPurchase: print("Purchase completed: \(e.catalogItemId)") case let e as RoktEvent.CartItemInstantPurchaseFailure: print("Purchase failed: \(e.error ?? "unknown")") case is RoktEvent.InstantPurchaseDismissal: print("User dismissed purchase") default: break } } ``` #### Display Shoppable Ads ```objectivec #import @import RoktContracts; NSDictionary *attributes = @{ @"email": @"j.smith@example.com", @"firstname": @"Jane", @"lastname": @"Smith", @"confirmationref": @"ORD-8829-XK2", @"amount": @"52.25", @"currency": @"USD", @"paymenttype": @"visa", @"shippingaddress1": @"123 Main St", @"shippingcity": @"Brooklyn", @"shippingstate": @"NY", @"shippingzipcode": @"11201", @"shippingcountry": @"US" }; [[MParticle sharedInstance].rokt selectShoppableAds:@"ConfirmationPage" attributes:attributes config:nil onEvent:^(RoktEvent *_Nonnull event) { // Handle Shoppable Ads events — see the Events API section below }]; ``` > **Note** > > If your platform does not have shipping address details (e.g. 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. For card forwarding, also pass `partnerpaymentreference` and `last4digits` — see [Placement Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#placement-attributes). The `CartItemInstantPurchase` and related events in the [Events API](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#events-api) below fire during Shoppable Ads purchase flows. ### Additional configuration Pass optional parameters such as `RoktConfig` to customize the placement UI (e.g. dark/light mode). Additional optional parameters including embedded views and the `onEvent` callback are shown below. ### selectPlacements with RoktConfig ```swift let roktConfig = RoktConfig.Builder().colorMode(.light).build() MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes, embeddedViews: nil, config: roktConfig) { _ in } ``` ### selectPlacements with RoktConfig ```objectivec RoktConfig *roktConfig = [[[RoktConfigBuilder new] colorMode:RoktColorModeLight] build]; [[MParticle sharedInstance].rokt selectPlacements:@"RoktExperience" attributes:attributes embeddedViews:nil config:roktConfig onEvent:^(RoktEvent *_Nonnull event) { // Handle placement events if needed }]; ``` > **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. ### Call selectPlacements once per screen Each `selectPlacements` call starts a new placement selection. If a screen calls it more than once, Rokt records every call, the placement renders again, and selection counts and the metrics that depend on them are inflated. In UIKit, `viewWillAppear` and `viewDidAppear` run every time a screen appears, not only the first time. They run again, for example, when: - A full-screen modal presented from the screen is dismissed, such as a payment sheet. - A navigation controller pops back to the screen. - The user switches back to a tab that contains the screen. `viewDidLoad` runs once for each screen instance, so call `selectPlacements` there when all the attributes you pass are already available. If they arrive later, for example after an asynchronous request or after the customer completes part of the payment flow, call `selectPlacements` one time, at the point they become available, and guard the call as described below. If you use an embedded placement, make sure the embedded view is already in the screen's view hierarchy. It doesn't need to be laid out yet. If the embedded view lives in a table or collection view cell, it doesn't exist until the cell is created, so `viewDidLoad` is too early. Request the placement after the view exists, such as in `viewDidAppear`, and guard it so it runs once for each screen instance. ### Call selectPlacements once from viewDidLoad ```swift import UIKit import mParticle_Apple_SDK final class PaymentViewController: UIViewController { @IBOutlet private weak var roktEmbeddedView: RoktEmbeddedView! @IBOutlet private weak var roktEmbeddedViewHeight: NSLayoutConstraint! override func viewDidLoad() { super.viewDidLoad() // viewDidLoad runs once for each screen instance. // Don't call selectPlacements from viewDidAppear without a guard. MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes, embeddedViews: ["RoktEmbedded1": roktEmbeddedView], config: nil) { [weak self] event in if let sizeEvent = event as? RoktEvent.EmbeddedSizeChanged { self?.roktEmbeddedViewHeight.constant = sizeEvent.updatedHeight } } } } ``` ### Call selectPlacements once from viewDidLoad ```objectivec #import @import RoktContracts; @interface PaymentViewController () @property (nonatomic, weak) IBOutlet RoktEmbeddedView *roktEmbeddedView; @property (nonatomic, weak) IBOutlet NSLayoutConstraint *roktEmbeddedViewHeight; @end @implementation PaymentViewController - (void)viewDidLoad { [super viewDidLoad]; // viewDidLoad runs once for each screen instance. // Don't call selectPlacements from viewDidAppear without a guard. __weak typeof(self) weakSelf = self; [[MParticle sharedInstance].rokt selectPlacements:@"RoktExperience" attributes:attributes embeddedViews:@{@"RoktEmbedded1": self.roktEmbeddedView} config:nil onEvent:^(RoktEvent *_Nonnull event) { if ([event isKindOfClass:[RoktEmbeddedSizeChanged class]]) { RoktEmbeddedSizeChanged *sizeEvent = (RoktEmbeddedSizeChanged *)event; weakSelf.roktEmbeddedViewHeight.constant = sizeEvent.updatedHeight; } }]; } @end ``` If you need to call `selectPlacements` from `viewDidAppear` or another callback that can run more than once, guard the call so it runs one time for each checkout or payment attempt. Don't reset the guard when the screen reappears or when a placement fails to load, or the next appearance sends another request. > **Note** > > Each screen type has its own page identifier, so the payment screen and the confirmation screen each make one call. ### Optional functions | Function | Purpose | | -------------- | ------------------------------ | | `Rokt.close()` | Auto-close overlay placements. | > **Note** > > For a full list of supported attributes, see [Placement Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#placement-attributes) below. Your Rokt team will configure your placement layouts to match your brand. ### 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 and Shoppable Ads order confirmation. | | `firstname` | string | Customer first name. Used for personalization and Shoppable Ads order fulfillment. | | `lastname` | string | Customer last name. Used for personalization and Shoppable Ads order fulfillment. | | `mobile` | string | Customer mobile number in E.164 format. Used for identity resolution. | | `confirmationref` | string | Order / confirmation reference number. Used for relevance, deduplication, and Shoppable Ads order reconciliation. | | `currency` | string | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance and Shoppable Ads. | | `country` | string | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance. | | `language` | string | Customer's preferred language (ISO 639-1). Used for relevance. | | `totalprice` | decimal | Total cart value including tax and shipping. Used for relevance. | | `amount` | decimal | Cart subtotal before tax and shipping. Distinct from `totalprice`. Used for relevance and Shoppable Ads. | | `cartItems` | array | Structured array of cart-line objects. Must be camelCase. Used for relevance. | | `couponcode` | string | Promo code applied, 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 cumulative purchase value. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (`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 | 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. | | `billingaddress1` | string | Billing street address. Used for identity resolution and relevance. | | `billingaddress2` | string | Billing apartment / unit. Used for identity resolution. | | `billingcity` | string | Billing city. Used for relevance. | | `billingstate` | string | Billing state or province. Used for relevance. | | `billingzipcode` | string | Billing ZIP / postcode. Used for identity resolution and relevance. | | `billingname` | string | Full cardholder name on the billing address. Used for identity resolution. | | `shippingmethod` | string | Shipping method selected (`standard`, `express`, `next_day`). Used for relevance. | | `shippingname` | string | Full recipient name on the shipping address. Used for Shoppable Ads order fulfillment. | | `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. | | `partnerpaymentreference` | string | Non-guessable identifier for the customer's vaulted payment method. Required for Shoppable Ads card forwarding. | | `last4digits` | string | Last 4 digits of the card used. Displayed to the customer during Shoppable Ads. | | `plcc` | string | `"yes"` or `"no"` — whether the customer has a private-label credit card. Used for Pay+ relevance. | | `discountamount` | decimal | Order-level discount applied. Used for Pay+ relevance. | | `prescreen` | string | `"yes"` or `"no"` — whether the customer has pre-qualified for a credit offer. Used for Pay+ relevance. | | `adsexperience` | string | If you are using Shoppable Ads, you must set `adsexperience` to `shoppable`. | ### Events API The SDK+ emits placement lifecycle events through the `Rokt.events` API. Subscribe to respond to load state, engagement, failures, and Shoppable Ads purchase flows. ### Subscribe to Rokt.events ```swift import mParticle_Apple_SDK MParticle.sharedInstance().rokt.events("RoktLayout", onEvent: { roktEvent in if let event = roktEvent as? RoktEvent.ShowLoadingIndicator { // Example showing handling of ShowLoadingIndicator event // The full list of events is provided below } }) ``` ### Subscribe to Rokt.events ```objectivec #import @import RoktContracts; [[MParticle sharedInstance].rokt events:@"RoktLayout" onEvent:^(RoktEvent *event) { NSLog(@"Triggered Event of type %@", [event class]); if ([event isKindOfClass:[RoktShowLoadingIndicator class]]) { // Example showing handling of ShowLoadingIndicator event // The full list of events is provided below } }]; ``` #### 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 | | OpenUrl | Triggered when the user presses a URL that is configured to be sent to the partner app | identifier: String, url: String | | PositiveEngagement | Triggered when the user positively engages with the offer | identifier: 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) | | FirstPositiveEngagement | Triggered when the user positively engages with the offer for the first time | identifier: String, setFulfillmentAttributes: func (attributes: \[String: String]) | | CartItemInstantPurchase | Triggered when a purchase is made through a placement | identifier: String, name: String?, cartItemId: String, catalogItemId: String, currency: String, description: String, linkedProductId: String?, providerData: String, quantity: NSDecimalNumber?, totalPrice: NSDecimalNumber?, unitPrice: NSDecimalNumber? | | EmbeddedSizeChanged | Triggered when the height of an embedded placement changes | identifier: String, updatedHeight: CGFloat | #### Shoppable Ads events ### Show Shoppable Ads events | Event | Description | Params | | -------------------------------- | ----------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------- | | CartItemInstantPurchaseInitiated | Purchase flow started — user tapped "Buy" | identifier, catalogItemId, cartItemId | | CartItemInstantPurchase | Purchase completed successfully | identifier, name, cartItemId, catalogItemId, currency, description, linkedProductId, providerData, quantity, totalPrice, unitPrice | | CartItemInstantPurchaseFailure | Purchase failed | identifier, catalogItemId, cartItemId, error | | CartItemDevicePay | Apple Pay / device payment triggered | identifier, catalogItemId, cartItemId, paymentProvider | | InstantPurchaseDismissal | User dismissed the purchase overlay | identifier | > **Note** > > After requesting a Rokt Shoppable Ad, any of the following events may be emitted and should be used to determine when to make a subsequent request for Rokt Thanks: > > - `PlacementClosed` > - `PlacementCompleted` > - `PlacementFailure` ## 7. Appendix ### Appendix A: App configuration Applications can send configuration settings through `RoktConfig` so the iOS 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 | ```swift title="ColorMode.light" // if application supports only Light Mode. let roktConfig = RoktConfig.Builder().colorMode(.light).build() MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes, embeddedViews: nil, config: roktConfig) { _ in } ``` ```objectivec title="ColorMode.light" // if application supports only Light Mode. RoktConfig *roktConfig = [[[RoktConfigBuilder new] colorMode:RoktColorModeLight] build]; [[MParticle sharedInstance].rokt selectPlacements:@"RoktExperience" attributes:attributes embeddedViews:nil config:roktConfig onEvent:^(RoktEvent *_Nonnull event) { // Handle placement events if needed }]; ``` #### CacheConfig object | Parameter | Description | | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | cacheDuration | Optional TimeInterval for which the Rokt SDK+ should cache the experience. Maximum allowed value is 90 minutes and the default (if value is not provided or invalid) is 90 minutes. | | cacheAttributes | Optional attributes to be used as cache key. If null, all the attributes sent in the `selectPlacements` call will be used as the cache key. | ```swift title="Cache for 1200 seconds" // to cache the experience for 1200 seconds, using email and orderNumber attributes as the cache key. let roktConfig = RoktConfig.Builder() .cacheConfig(RoktConfig.CacheConfig( cacheDuration: TimeInterval(1200), cacheAttributes: ["email": "j.smith@example.com", "orderNumber": "123"] )) .build() MParticle.sharedInstance().rokt.selectPlacements("RoktExperience", attributes: attributes, embeddedViews: nil, config: roktConfig) { _ in } ``` ```objectivec title="Cache for 1200 seconds" // to cache the experience for 1200 seconds, using email and orderNumber attributes as the cache key. NSDictionary *cacheKeyAttributes = @{ @"email": @"j.smith@example.com", @"orderNumber": @"123" }; RoktCacheConfig *cacheConfig = [[RoktCacheConfig alloc] initWithCacheDuration:1200 cacheAttributes:cacheKeyAttributes]; RoktConfig *roktConfig = [[[RoktConfigBuilder new] cacheConfig:cacheConfig] build]; [[MParticle sharedInstance].rokt selectPlacements:@"RoktExperience" attributes:attributes embeddedViews:nil config:roktConfig onEvent:^(RoktEvent *_Nonnull event) { // Handle placement events if needed }]; ``` ### Appendix B: SwiftUI support with MPRoktLayout If your app is primarily written in SwiftUI we have provided the `MPRoktLayout` component for 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. ##### Adding the SwiftUI component ```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) } } ``` ##### Parameters | 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: 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 - we intend you to treat these APIs as gating operations in order to maintain a consistent user state. The SDK+ will not retry API calls automatically, but provides callback APIs such that you can do so according to your business logic. The tolerance you have for retry and inconsistent state is up to your product requirements. If you do not wish to handle errors, you may see data consistency issues at scale. It's recommended to at least monitor for errors during your implementation. Your IDSync callback block will be invoked with one of two objects: - `MPIdentityApiResult`: A result object containing the new or updated user object. - `NSError`/`Error`: An error object containing a code and description if the IDSync call failed ```swift title="IDSync error handling" let identityCallback = {(result: MPIdentityApiResult?, error: Error?) in if (result?.user != nil) { //IDSync request succeeded, mutate attributes or query for the MPID as needed result?.user.setUserAttribute("example attribute key", value: "example attribute value") } else { NSLog(error!.localizedDescription) let resultCode = MPIdentityErrorResponseCode(rawValue: UInt((error! as NSError).code)) switch (resultCode!) { case .clientNoConnection, .clientSideTimeout: //retry the IDSync request break; case .requestInProgress, .retry: //inspect your implementation if this occurs frequency //otherwise retry the IDSync request break; default: // inspect error.localizedDescription to determine why the request failed // this typically means an implementation issue break; } } } ``` ```objectivec title="IDSync error handling" id identityCallback = ^(MPIdentityApiResult *_Nullable apiResult, NSError *_Nullable error) { if (apiResult) { // IDSync request succeeded, mutate attributes or query for the MPID as needed [apiResult.user setUserAttribute:@"example attribute key" value:@"example attribute value"]; } else { NSLog(@"%@", error.userInfo); switch (error.code) { case MPIdentityErrorResponseCodeClientNoConnection: case MPIdentityErrorResponseCodeClientSideTimeout: // Retry the IDSync request break; case MPIdentityErrorResponseCodeRequestInProgress: case MPIdentityErrorResponseCodeRetry: // Inspect your implementation if this occurs frequently; // otherwise retry the IDSync request break; default: // Inspect error.userInfo to determine why the request failed // This typically means an implementation issue break; } } }; ``` #### Status codes When an IDSync callback block in invoked with a failure, you can inspect the `code` property to determine the cause. This property is meant to describe the result of the invocation of the respective iOS SDK+ IDSync API. It may either contain a client-side generated value, or an actual HTTP status code. ##### Client-side codes The NSError code property may contain the following client-side codes, defined within the `MPIdentityErrorResponseCode` enum: | 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. The SDK+ pins the mParticle SSL certificate which requires custom initialization via the `MPNetworkOptions` API to disable. | | `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. This should be rare and could mean the app is in a bad memory state. | #### HTTP status codes The NSError code property may contain the following server generated HTTP status codes, some of which are defined within the `MPIdentityErrorResponseCode` enum for your convenience: | Value | Description | | ----- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | The IDSync HTTP call failed due to an invalid request body. Inspect the `error.userInfo` object for more information. | | 401 | The IDSync HTTP call failed due to an authentication error. Verify that your API key is correct. | | 403 | The IDSync HTTP call failed because this operation is not provisioned for your account. Contact your Rokt account manager to enable it. | | 429 | The IDSync HTTP call was throttled and should be retried. This may indicate a user "hotkey" or an incorrect implementation resulting in a higher than expected volume of IDSync requests. | | 5xx | The IDSync HTTP call failed due to a Rokt server-side issue. Contact your account representative for additional information. | ### UIApplication Delegate Proxy By default the mParticle SDK replaces your `UIApplication.delegate` with its own `NSProxy` implementation in order to facilitate and simplify the handling of remote notifications, local notifications, interactions with notification actions, and application launching. Over time we have found this to be less intrusive than other SDKs which instead perform swizzling, but it can cause complications when a client is using a 3rd party framework that does. > **Tip: Recommendation** > > We recommend new integrations disable `proxyAppDelegate` and instead use the [SceneDelegate methods](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#scenedelegate-support-ios-13) below to manually forward lifecycle events to mParticle. In a future major release, `proxyAppDelegate` will default to `false`. You can disable the proxy via the `proxyAppDelegate` flag of the `MParticleOptions` object. Doing so means you will need to audit any kits that you use individually to determine which `UIApplication` APIs they require. Any required methods should be manually invoked on mParticle, such that mParticle can forward those APIs onto each kit. ### Swift ```swift let options = MParticleOptions(key: "REPLACE WITH APP KEY", secret: "REPLACE WITH APP SECRET") options.proxyAppDelegate = false MParticle.sharedInstance().start(with: options) ``` ### Objective-C ```objectivec MParticleOptions *options = [MParticleOptions optionsWithKey:@"REPLACE WITH APP KEY" secret:@"REPLACE WITH APP SECRET"]; options.proxyAppDelegate = NO; [[MParticle sharedInstance] startWithOptions:options]; ``` #### AppDelegate Methods with Proxy Disabled When `proxyAppDelegate` is disabled, you must manually forward the following methods from your `AppDelegate` to mParticle. These methods are required for kits that have remote or local notification functionality, and for using mParticle to register for push notifications. ### Swift ```swift // MARK: - Remote Notification Registration func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { MParticle.sharedInstance().didRegisterForRemoteNotifications(withDeviceToken: deviceToken) } func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: any Error) { MParticle.sharedInstance().didFailToRegisterForRemoteNotificationsWithError(error) } // MARK: - UNUserNotificationCenterDelegate func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) { MParticle.sharedInstance().userNotificationCenter(center, willPresent: notification) if #available(iOS 14, *) { completionHandler([.list, .banner]) } else { completionHandler(.alert) } } func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { MParticle.sharedInstance().userNotificationCenter(center, didReceive: response) completionHandler() } ``` ### Objective-C ```objectivec #pragma mark - Remote Notification Registration - (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken { [[MParticle sharedInstance] didRegisterForRemoteNotificationsWithDeviceToken:deviceToken]; } - (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error { [[MParticle sharedInstance] didFailToRegisterForRemoteNotificationsWithError:error]; } #pragma mark - UNUserNotificationCenterDelegate - (void)userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler { [[MParticle sharedInstance] userNotificationCenter:center willPresentNotification:notification]; if (@available(iOS 14.0, *)) { completionHandler(UNNotificationPresentationOptionList | UNNotificationPresentationOptionBanner); } else { completionHandler(UNNotificationPresentationOptionAlert); } } - (void)userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler { [[MParticle sharedInstance] userNotificationCenter:center didReceiveNotificationResponse:response]; completionHandler(); } ``` ### SceneDelegate Support (iOS 13+) For apps using a `UISceneDelegate` (the modern lifecycle introduced in iOS 13), mParticle provides dedicated methods for handling URL contexts and user activities. These are the recommended approach for all integrations. #### Handling URL Contexts To handle deep links and custom URL schemes in your `SceneDelegate`, use the `handleURLContext:` method: ### Swift ```swift func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { for urlContext in URLContexts { MParticle.sharedInstance().handleURLContext(urlContext) } } ``` ### Objective-C ```objectivec - (void)scene:(UIScene *)scene openURLContexts:(NSSet *)URLContexts { for (UIOpenURLContext *urlContext in URLContexts) { [[MParticle sharedInstance] handleURLContext:urlContext]; } } ``` #### Handling User Activities (Universal Links) To handle Universal Links in your `SceneDelegate`, use the `handleUserActivity:` method: ### Swift ```swift func scene(_ scene: UIScene, continue userActivity: NSUserActivity) { MParticle.sharedInstance().handleUserActivity(userActivity) } ``` ### Objective-C ```objectivec - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity { [[MParticle sharedInstance] handleUserActivity:userActivity]; } ``` ### Appendix D: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — the customer acts in a WebView (for example a payment page) then returns to the app. Pass `sessionId` and `sessionToken` into iOS `setSession`. - **Native to web** — the customer starts in the app then continues in a WebView. Read the native session with `getSession` and seed Web `launcherOptions` before init. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; window.location.href = deepLink; ``` #### Setting the session Extract the session ID and session token from the deep link and pass them to the SDK+ with `setSession` before calling `selectPlacements`. ```swift title="Handle deep link and setSession" func handleDeepLink(url: URL) { let components = URLComponents(url: url, resolvingAgainstBaseURL: false) let sessionId = components?.queryItems?.first(where: { $0.name == "sessionId" })?.value let sessionToken = components?.queryItems?.first(where: { $0.name == "sessionToken" })?.value if let sessionId, !sessionId.isEmpty { let session = MPRoktSession( sessionId: sessionId, sessionToken: sessionToken, expiresAt: nil ) MParticle.sharedInstance().rokt.setSession(session) } // Proceed with your confirmation flow } ``` ```objectivec title="Handle deep link and setSession" - (void)handleDeepLink:(NSURL *)url { NSURLComponents *components = [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO]; NSString *sessionId = nil; NSString *sessionToken = nil; for (NSURLQueryItem *item in components.queryItems) { if ([item.name isEqualToString:@"sessionId"]) { sessionId = item.value; } else if ([item.name isEqualToString:@"sessionToken"]) { sessionToken = item.value; } } if (sessionId.length > 0) { MPRoktSession *session = [[MPRoktSession alloc] initWithSessionId:sessionId sessionToken:sessionToken expiresAt:nil]; [[[MParticle sharedInstance] rokt] setSession:session]; } // Proceed with your confirmation flow } ``` #### Passing from native to web After a native placement, read the current session and inject it into the WebView before Web SDK+ init. `getSession` returns `nil` when there is no session or the token has expired — show a native placement first, or pass `sessionId` alone. ```swift title="Read native session for WebView" let session = MParticle.sharedInstance().rokt.getSession() let sessionId = session?.sessionId let sessionToken = session?.sessionToken ``` ```objectivec title="Read native session for WebView" MPRoktSession *session = [[[MParticle sharedInstance] rokt] getSession]; NSString *sessionId = session.sessionId; NSString *sessionToken = session.sessionToken; ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `setSession` before `selectPlacements` so native uses the web session - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session - Empty strings are ignored and will not update the session - Always URL-encode the session ID and session token when passing as a query parameter ### Appendix E: Configure Shoppable Ads payments If you are not using Shoppable Ads, skip this step. Shoppable Ads on iOS require a registered `RoktPaymentExtension` 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 snippet is included in the [initialization code in Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#initialize-ios) — register it after `MParticle.sharedInstance().start()` and before `selectShoppableAds`. | 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 (below). | | Afterpay / Clearpay | Custom URL scheme registered in `Info.plist` + matching `urlScheme` on `RoktPaymentExtension` + redirect-URL forwarding (below). | | [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** > > Configure `stripePublishableKey` in your **mParticle Rokt kit** settings; the kit forwards it to Rokt automatically. 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. `RoktPaymentExtension` is a Swift type, so it's created and registered in Swift (see the Swift tab in [Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#initialize-ios)). In an otherwise Objective-C app, do this from a small Swift file; the rest of the flow (`selectShoppableAds`, `handleURLCallback`) is available from Objective-C. #### Apple Pay (optional) To offer Apple Pay, 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 pass the merchant ID as `applePayMerchantId` when creating `RoktPaymentExtension`. #### Afterpay / Clearpay (optional) Afterpay and Clearpay are redirect-based. To enable them: 1. Register a URL scheme in your app's `Info.plist` under `CFBundleURLTypes` (for example, `myapp`). 2. Pass the matching `urlScheme` when creating `RoktPaymentExtension` (for example, `"myapp"`). The SDK builds the return URL internally. 3. Forward redirect URLs to Rokt — see below. #### Forward redirect URLs Afterpay, Clearpay, and PayPal send customers to a web view and redirect back to your app via the registered URL scheme. Forward incoming URLs to Rokt with `handleURLCallback` **in addition to** any existing mParticle URL handling. ### Forward redirect URLs (SceneDelegate) ```swift func scene(_ scene: UIScene, openURLContexts URLContexts: Set) { for urlContext in URLContexts { if MParticle.sharedInstance().rokt.handleURLCallback(with: urlContext.url) { return } MParticle.sharedInstance().handleURLContext(urlContext) } } ``` ### Forward redirect URLs (SwiftUI) ```swift WindowGroup { ContentView() .onOpenURL { url in _ = MParticle.sharedInstance().rokt.handleURLCallback(with: url) } } ``` ## 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. ```swift title="Enable verbose SDK+ logging" Rokt.setLoggingEnabled(enable: true) ``` ### 2. Build and run against a development key Build and run your app against a development key with `environment = .development` (or `MPEnvironmentDevelopment`). ### 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 Xcode console for Rokt SDK+ errors. Common issues: #### Initialization errors - Confirm `key` and `secret` match the values from your Rokt account manager. - Confirm `MParticle.sharedInstance().start(with: options)` runs before any `selectPlacements` or `logEvent` call. - For Shoppable Ads, confirm `RoktPaymentExtension` is registered after `start()` and before `selectShoppableAds`. If using PayPal or Afterpay / Clearpay, confirm `handleURLCallback` is wired into your URL handler. #### Identity errors If the `onIdentifyComplete` or identify callback fires with an error instead of a user, see [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#error-handling-ios) for the `MPIdentityErrorResponseCode` values and retry guidance. Without error handling you may see data consistency issues at scale. #### Placement not rendering - Confirm the placement `identifier` (e.g. `RoktExperience`) matches what your Rokt account manager configured. - For embedded placements, confirm the embedded view identifier (e.g. `RoktEmbedded1`) matches the layout configuration. - Check that the attributes dictionary contains at least `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`. #### Placement requested more than once If the placement re-renders unexpectedly, or the selection count for a page identifier is higher than the number of times customers saw that screen, the screen is probably calling `selectPlacements` more than once. Compare each page identifier with its own screen views, because the payment and confirmation screens each make one call and abandoned checkouts add payment screen views without orders. - Log from `viewDidLoad`, `viewWillAppear`, and `viewDidAppear` on the screen that calls `selectPlacements`. If `viewDidAppear` runs more than once for one checkout, a call placed there runs more than once. See [Call selectPlacements once per screen](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#call-once-ios). - Check how the payment step is presented. Dismissing a full-screen modal runs `viewDidAppear` on the screen underneath again. - Search your code for every place that resets a guard flag, including placement failure handlers. A reset lets the next appearance send another request. - In a development build, set `options.logLevel = .debug` and look for repeated `MPRokt selectPlacements` lines with the same identifier. #### SSL handshake errors when using a proxy While testing in your development environment, you might hit SSL handshake errors when an HTTP debugging proxy like Charles or Proxyman is running, or when you're behind a corporate network proxy. When the SDK+ attempts to identify the current user, it reports this as `MPIdentityErrorResponseCodeSSLError` (see [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#error-handling-ios)). This is expected: the SDK+ pins its SSL certificate, and a proxy intercepts HTTPS by presenting its own certificate, which fails the pin. To let the proxy inspect SDK+ traffic, disable pinning in your development build by setting `pinningDisabledInDevelopment` on the `MPNetworkOptions` in your [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/ios/#initialize-ios). ### Disable SSL pinning for proxy debugging ```swift let networkOptions = MPNetworkOptions() // Only takes effect in the development environment; production builds stay pinned. networkOptions.pinningDisabledInDevelopment = true options.networkOptions = networkOptions ``` ### Disable SSL pinning for proxy debugging ```objectivec MPNetworkOptions *networkOptions = [[MPNetworkOptions alloc] init]; // Only takes effect in the development environment; production builds stay pinned. networkOptions.pinningDisabledInDevelopment = YES; options.networkOptions = networkOptions; ``` # Android SDK+ Integration Guide > Product: Rokt Ecommerce > > Intended implementer: Rokt Ecommerce partner > > Integration surface: The partner’s owned website, app, or transaction experience > > Not intended for: Advertisers implementing Rokt Ads > > Canonical documentation: [https://docs.rokt.com/integration-guides/ecommerce/sdk/android/](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/) > > This is a complete machine-readable guide generated from the same MDX source as the rendered documentation. Read the complete guide before implementing the integration, and do not edit this generated file directly. These SDK+ integration guides are for Rokt Ecommerce partners—ecommerce businesses integrating Rokt into transaction experiences they own, including websites, apps, carts, checkouts, payment pages, and confirmation flows. They are not implementation guides for advertisers using Rokt Ads to acquire customers across the Rokt Network. References to advertiser offers describe content rendered within an ecommerce partner’s owned experience. Advertisers should use the [Rokt Ads integration guides](https://docs.rokt.com/integration-guides/ads/). This page explains how to implement the Rokt Ecommerce Android SDK+. The SDK+ passes user and transaction data to Rokt on configured screens so Rokt can render relevant experiences, such as offers on confirmation screens. ## 1. Add the Rokt SDK+ to Your Android App Rokt SDK+ requires a minimum Android API level of **23+** (Android 6.0 Marshmallow). Update your Gradle file to include the necessary dependencies: ### build.gradle.kts dependencies ```kotlin dependencies { implementation("com.mparticle:android-rokt-kit:6.1.9") implementation("com.mparticle:android-core:6.1.9") } ``` ### build.gradle dependencies ```groovy dependencies { implementation "com.mparticle:android-rokt-kit:6.1.9" implementation "com.mparticle:android-core:6.1.9" } ``` ## 2. Initialize the Rokt SDK+ Insert the following initialization snippet in the `onCreate()` method of your `Application` class. The SDK+ must be initialized before any other SDK+ API calls. Replace `your-key` and `your-secret` with the key and secret provided by your Rokt team. ### Application.onCreate initialization ```kotlin import com.mparticle.MParticle import com.mparticle.MParticleOptions import com.mparticle.networking.NetworkOptions class YourApplicationClass : Application() { override fun onCreate() { super.onCreate() // Identify the current user: // If you do not have the user's email address, you can pass in a null value val identifyRequest = IdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email via .email(). // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both. .email("j.smith@example.com") .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email") // only if raw email unavailable // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both. .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number") // only if raw mobile unavailable // Customer phone number in E.164 format. .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515") // Partner's internal customer/account identifier (if the user is logged in). .customerId("cust_10482") .build() // If the user is identified with their email address, set additional user attributes. val identifyTask = BaseIdentityTask() .addSuccessListener { identityApiResult -> val user = identityApiResult.user user.setUserAttribute("example attribute key", "example attribute value") } // Enter your custom subdomain if you are using a first-party domain configuration (optional) val networkOptions = NetworkOptions.builder() .setCustomBaseURL("https://rkt.example.com") .build() val options: MParticleOptions = MParticleOptions.builder(this) .credentials( "your-key", // The key provided by your Rokt account manager "your-secret" // The secret provided by your Rokt account manager ) // Specify the data environment with environment: // Set it to Development if you are still testing your integration. // Set it to Production if your integration is ready for production data. // The default is AutoDetect which attempts to detect the environment automatically. .environment(MParticle.Environment.Development) .networkOptions(networkOptions) .identify(identifyRequest) .identifyTask(identifyTask) .build() MParticle.start(options) } } ``` ### Application.onCreate initialization ```java import com.mparticle.MParticle; import com.mparticle.MParticleOptions; import com.mparticle.networking.NetworkOptions; public class YourApplicationClass extends Application { @Override public void onCreate() { super.onCreate(); // Identify the current user: // If you do not have the user's email address, you can pass in a null value IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email via .email(). // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both. .email("j.smith@example.com") .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email") // only if raw email unavailable // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both. .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number") // only if raw mobile unavailable // Customer phone number in E.164 format. .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515") // Partner's internal customer/account identifier (if the user is logged in). .customerId("cust_10482") .build(); // If the user is identified with their email address, set additional user attributes. BaseIdentityTask identifyTask = new BaseIdentityTask() .addSuccessListener(new TaskSuccessListener() { @Override public void onSuccess(IdentityApiResult identityApiResult) { MParticleUser user = identityApiResult.getUser(); user.setUserAttribute("example attribute key", "example attribute value"); } }); // Enter your custom subdomain if you are using a first-party domain configuration (optional) NetworkOptions networkOptions = NetworkOptions.builder() .setCustomBaseURL("https://rkt.example.com") .build(); MParticleOptions options = MParticleOptions.builder(this) .credentials( "your-key", // The key provided by your Rokt account manager "your-secret" // The secret provided by your Rokt account manager ) // Specify the data environment with environment: // Set it to Development if you are still testing your integration. // Set it to Production if your integration is ready for production data. // The default is AutoDetect which attempts to detect the environment automatically. .environment(MParticle.Environment.Development) .networkOptions(networkOptions) .identify(identifyRequest) .identifyTask(identifyTask) .build(); MParticle.start(options); } } ``` When inserting the initialization snippet into your `Application` class, you will see customizable fields for: ### 1. Entering your Rokt key and secret Set `your-key` and `your-secret` inside `credentials` to the key and secret values provided by your Rokt account manager. ### 2. Setting your data environment Set `environment` to `MParticle.Environment.Development` while testing to route data to the Development environment, and `MParticle.Environment.Production` to send live customer activity to Production. ### 3. Entering a custom first-party domain Follow the instructions in [First-Party Domain Configuration](https://docs.rokt.com/developer-reference/sdks/web-sdk/first-party-domains/), and set `customBaseURL` on `NetworkOptions` to your custom subdomain. Routing the Rokt SDK+ through your own domain reduces the risk of ad blockers and browsers from blocking ads or data. Omit `networkOptions` to send traffic to Rokt's default endpoints. ### 4. Identifying your user and setting attributes In `identifyRequest`, pass the user's raw, un-hashed email via `.email()`. For hashed emails and other identifiers, see [Supported User Identifiers](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#supported-user-identifiers). Once identified, use the `identifyTask` success listener to set additional user attributes — see [User Attributes](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#user-attributes) for the recommended list. #### identifyTask success listener ```kotlin val identifyTask = BaseIdentityTask() .addSuccessListener { identityApiResult -> val user = identityApiResult.user user.setUserAttribute("example attribute key", "example attribute value") } ``` #### identifyTask success listener ```java BaseIdentityTask identifyTask = new BaseIdentityTask() .addSuccessListener(new TaskSuccessListener() { @Override public void onSuccess(IdentityApiResult identityApiResult) { MParticleUser user = identityApiResult.getUser(); user.setUserAttribute("example attribute key", "example attribute value"); } }); ``` > **Note** > > Always include `identifyRequest` in the initialization snippet. If you don't have the user's email at initialization, omit the `.email()` call — the SDK+ will still initialize, and you can identify the user later via [3. Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#identify-android). See [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#error-handling) for how to handle the `addFailureListener` callback — without error handling you may see data consistency issues at scale. ### Initializing with fonts Instead of or in addition to supplying fonts on One Platform, you may use fonts already bundled with your application. This removes the potential for fonts to be downloaded at initialization time, reducing network utilization and the chance of download errors. #### Using font assets Pass a map to the `roktOptions` builder method that maps font PostScript names to their filepath in the `assets` directory. Check with your account manager if you are unsure of the PostScript names being used in your layout. ### Font assets ```kotlin import com.mparticle.MParticle import com.mparticle.MParticleOptions class YourApplication : Application() { override fun onCreate() { super.onCreate() val options: MParticleOptions = MParticleOptions.builder(this) .credentials("your-key", "your-secret") .environment(MParticle.Environment.Development) .roktOptions(RoktOptions(fontFilePathMap = mapOf("Arial-Bold" to "fonts/arialbold.otf"))) .build() MParticle.start(options) } } ``` ### Font assets ```java import com.mparticle.MParticle; import com.mparticle.MParticleOptions; public class YourApplication extends Application { @Override public void onCreate() { super.onCreate(); Map fontFilePathMap = new HashMap<>(); fontFilePathMap.put("Arial-Bold", "fonts/arialbold.otf"); MParticleOptions options = MParticleOptions.builder(this) .credentials("your-key", "your-secret") .environment(MParticle.Environment.Development) .roktOptions(new RoktOptions(fontFilePathMap)) .build(); MParticle.start(options); } } ``` ## 3. Identify the User The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#initialize-android) identifies the current user using the identifiers you provided in the script's `identifyRequest` object. After SDK initialization, you should keep the user's identity in sync whenever they log in, log out, or otherwise provide an identifier (for example, during checkout) using the appropriate method as described below. ### Supported user identifiers ### Show supported user identifiers | Field | Type | Description | | ------------ | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Pass the customer's raw, unhashed email address via `.email("j.smith@example.com")`. | | `mobile` | string | Pass the customer's phone number in E.164 format via `.userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515")`. | | `customerid` | string | Pass your internal customer/account identifier via `.customerId("cust_10482")`. Send on every screen for logged-in users. | | `other` | string | Pass a SHA-256-hashed email via `.userIdentity(MParticle.IdentityType.Other, "hashed email")`. Only use when the raw email cannot be provided. | | `other2` | string | Pass a SHA-256-hashed mobile number via `.userIdentity(MParticle.IdentityType.Other2, "hashed mobile")`. | To identify the user: ### 1. Create an identifyRequest object Create an `identifyRequest` object to contain the user's identifiers. You should integrate the user's raw, unhashed email address into the `email` field. ### 2. Create an identityCallback To set additional user attributes, create an `identityCallback`. If the `identifyRequest` succeeds, then any user attributes you set inside the callback are assigned to the identified user. ### 3. Send the request using the method that matches the user's action Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action: - **`MParticle.getInstance()?.Identity()?.login`:** call when the user logs in or creates an account. - **`MParticle.getInstance()?.Identity()?.identify`:** call when you obtain the user's email mid-session without a login transition (for example, a guest enters their email at checkout). - **`MParticle.getInstance()?.Identity()?.logout`:** call when the user logs out. Calling these methods transitions the SDK's record of the current user's state. The `login` and `logout` methods also automatically log a corresponding event to improve Rokt's attribution. For example, to identify a user named Jane Smith with the email address `j.smith@example.com`, mobile number `+13125551515`, and customer ID `cust_10482`: #### Identify Jane Smith ```kotlin // 1. Create the identifyRequest object val identifyRequest = IdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email via .email(). // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both. .email("j.smith@example.com") .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email") // only if raw email unavailable // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both. .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number") // only if raw mobile unavailable .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515") .customerId("cust_10482") .build() // 2. Optionally set user attributes once the request succeeds. val identityCallback = BaseIdentityTask() .addSuccessListener { identityApiResult -> val user = identityApiResult.user user.setUserAttribute("firstname", "Jane") user.setUserAttribute("lastname", "Smith") } // 3. Call one of the following methods that best matches the user's action: MParticle.getInstance()?.Identity()?.login(identifyRequest, identityCallback) // Call when the user logs in or creates an account MParticle.getInstance()?.Identity()?.identify(identifyRequest, identityCallback) // Call when you obtain the user's email mid-session, but not during a login MParticle.getInstance()?.Identity()?.logout() // Call when the user logs out ``` #### Identify Jane Smith ```java // 1. Create the identifyRequest object IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email via .email(). // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both. .email("j.smith@example.com") .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email") // only if raw email unavailable // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both. .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number") // only if raw mobile unavailable .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515") .customerId("cust_10482") .build(); // 2. Optionally set user attributes once the request succeeds. BaseIdentityTask identityCallback = new BaseIdentityTask() .addSuccessListener(new TaskSuccessListener() { @Override public void onSuccess(IdentityApiResult identityApiResult) { MParticleUser user = identityApiResult.getUser(); user.setUserAttribute("firstname", "Jane"); user.setUserAttribute("lastname", "Smith"); } }); // 3. Call one of the following methods that best matches the user's action: MParticle.getInstance().Identity().login(identifyRequest, identityCallback); // Call when the user logs in or creates an account MParticle.getInstance().Identity().identify(identifyRequest, identityCallback); // Call when you obtain the user's email mid-session, but not during a login MParticle.getInstance().Identity().logout(); // Call when the user logs out ``` ## 4. Set User Attributes Set user attributes **progressively** as the user navigates your app, not just at checkout. The more attributes you set, the better Rokt can resolve the customer and deliver relevant offers. ### Set User Attributes ```kotlin import com.mparticle.MParticle // Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method. val currentUser = MParticle.getInstance()?.Identity()?.currentUser // Once you have successfully set the current user to `currentUser`, you can set user attributes with: currentUser?.setUserAttribute("custom-attribute-name", "custom-attribute-value") // Note: all user attributes (including list attributes and tags) must have distinct names. // Rokt recommends setting as many of the following user attributes as possible: currentUser?.setUserAttribute("firstname", "John") currentUser?.setUserAttribute("lastname", "Doe") // Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901' currentUser?.setUserAttribute("mobile", "3125551515") currentUser?.setUserAttribute("age", "33") currentUser?.setUserAttribute("gender", "M") currentUser?.setUserAttribute("billingcity", "Brooklyn") currentUser?.setUserAttribute("billingstate", "NY") currentUser?.setUserAttribute("billingzipcode", "123456") currentUser?.setUserAttribute("dob", "yyyymmdd") currentUser?.setUserAttribute("title", "Mr") currentUser?.setUserAttribute("language", "en") currentUser?.setUserAttribute("predictedltv", "136.23") // You can create a user attribute to contain a list of values currentUser?.setUserAttributeList("favorite-genres", listOf("documentary", "comedy", "romance", "drama")) // To remove a user attribute, call removeUserAttribute and pass in the attribute name. All user attributes share the same key space. currentUser?.removeUserAttribute("attribute-to-remove") ``` ### Set User Attributes ```java import com.mparticle.MParticle; import com.mparticle.MParticleUser; // Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method. MParticleUser currentUser = MParticle.getInstance().Identity().getCurrentUser(); // Once you have successfully set the current user to `currentUser`, you can set user attributes with: currentUser.setUserAttribute("custom-attribute-name", "custom-attribute-value"); // Note: all user attributes (including list attributes and tags) must have distinct names. // Rokt recommends setting as many of the following user attributes as possible: currentUser.setUserAttribute("firstname", "John"); currentUser.setUserAttribute("lastname", "Doe"); // Phone numbers can be formatted either as '1234567890', or '+1 (234) 567-8901' currentUser.setUserAttribute("mobile", "3125551515"); currentUser.setUserAttribute("age", "33"); currentUser.setUserAttribute("gender", "M"); currentUser.setUserAttribute("billingcity", "Brooklyn"); currentUser.setUserAttribute("billingstate", "NY"); currentUser.setUserAttribute("billingzipcode", "123456"); currentUser.setUserAttribute("dob", "yyyymmdd"); currentUser.setUserAttribute("title", "Mr"); currentUser.setUserAttribute("language", "en"); currentUser.setUserAttribute("predictedltv", "136.23"); // You can create a user attribute to contain a list of values List attributeList = new ArrayList<>(); attributeList.add("documentary"); attributeList.add("comedy"); attributeList.add("romance"); attributeList.add("drama"); currentUser.setUserAttributeList("favorite-genres", attributeList); // To remove a user attribute, call removeUserAttribute and pass in the attribute name. All user attributes share the same key space. currentUser.removeUserAttribute("attribute-to-remove"); ``` ### User attributes Set as many of the following as you can collect: ### Show all user attributes | Field | Type | Description | | -------------------- | ------- | --------------------------------------------------------------------------------------------------------------------------------- | | `firstname` | string | Customer's first name. Used for personalization. | | `lastname` | string | Customer's last name. Used for personalization. | | `mobile` | string | Phone number formatted as `1112345678` or `+1 (222) 345-6789`. Used for identity resolution and relevance. | | `birthyear` | integer | Customer's birth year (e.g. `1990`). Preferred date-of-birth field. Alternates: `dob`, `age`. Used for eligibility and relevance. | | `age` | integer | Customer's age. Alternate to `dob`. Used for eligibility and relevance. | | `dob` | string | Date of birth, `yyyymmdd`. Alternate to `age`. Used for eligibility and relevance. | | `gender` | string | Customer's gender. For example, `M`, `F`, `Male`, or `Female`. Used for relevance. | | `title` | string | Honorific. For example, `Mr`, `Mrs`, `Ms`. Used for personalization. | | `language` | string | ISO 639-1 language code associated with the purchase. Used for relevance. | | `billingaddress1` | string | Street address (e.g. `123 Main St`). Used for identity resolution and relevance. | | `billingaddress2` | string | Apartment/unit (e.g. `Apt 4B`). Used for identity resolution. | | `billingcity` | string | Billing city. Used for relevance. | | `billingstate` | string | Billing state / province / region. Used for relevance and eligibility. | | `billingzipcode` | string | Full ZIP or postcode (US preference is ZIP+4). Used for identity resolution and relevance. | | `country` | string | ISO 3166-1 alpha-2 country code (e.g. `US`, `GB`, `AU`). Used for eligibility and relevance. | | `newcustomer` | boolean | Whether this is a first-time buyer. Used for relevance. | | `customertype` | string | Whether the user is authenticated (`guest` / `logged_in`). Used for relevance. | | `loyaltytier` | string | Partner loyalty program tier. Used for relevance and eligibility. | | `loyaltyid` | string | Loyalty program member ID. Used for identity resolution. | | `predictedltv` | decimal | Predicted total lifetime value, typically from a partner ML model. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance. | | `acquisitionchannel` | string | How the customer was originally acquired. Used for relevance. | All user attributes (including list attributes) must have distinct names. ## 5. Log Events Track screen views, commerce events, and custom events so Rokt can understand where each customer is in their journey. ### Event category: Screen views Call `MParticle.getInstance()?.logScreen()` with the name of the screen (e.g. `"homepage"`, `"product_detail_page"`). Include any additional custom attributes in the info map. #### Log a screen view ```kotlin MParticle.getInstance()?.logScreen( "homepage", mapOf("custom-attribute" to "custom-value") ) ``` #### Log a screen view ```java HashMap screenInfo = new HashMap<>(); screenInfo.put("custom-attribute", "custom-value"); if (MParticle.getInstance() != null) { MParticle.getInstance().logScreen("homepage", screenInfo); } ``` ### Event category: Commerce events Commerce events carry product-level details for the user's journey. Trigger a separate commerce event for each product action the customer takes. Investing in full commerce event coverage is one of the highest-leverage things you can do during your integration. Each event tells Rokt something different about where the customer is in their journey: a product view signals exploration, an add-to-cart signals consideration, a checkout start signals purchase intent, and a completed purchase confirms conversion. With a richer signal, Rokt can personalize offers more effectively, measure placement performance accurately, and attribute conversions to the right touchpoints. Doing this work during your initial integration also avoids a retrofit later. The signal compounds over time: each event Rokt receives adds context used to sharpen personalization, improve attribution accuracy, and better resolve and segment your customer base on future visits. Commerce events are logged with `CommerceEvent`, using a product action constant that identifies the customer action (viewing a product, adding to cart, starting checkout, completing a purchase, etc.). #### Show all product action types | Customer action | Product action constant | | -------------------------- | ------------------------------ | | Product detail page viewed | `Product.VIEW_DETAIL` | | Product clicked | `Product.CLICK` | | Item added to cart | `Product.ADD_TO_CART` | | Item removed from cart | `Product.REMOVE_FROM_CART` | | Item added to wishlist | `Product.ADD_TO_WISHLIST` | | Item removed from wishlist | `Product.REMOVE_FROM_WISHLIST` | | Checkout flow initiated | `Product.CHECKOUT` | | Checkout option selected | `Product.CHECKOUT_OPTION` | | Order confirmed | `Product.PURCHASE` | | Order refunded | `Product.REFUND` | Tracking a commerce event takes three phases: #### 1. Define the product Build a `Product` with the `Product.Builder`. Set name, SKU, and price as constructor args; set additional fields like `quantity`, `category`, `brand`, and `variant` via builder calls. ##### Define a product ```kotlin val product = Product.Builder("Double Room - Econ Rate", "econ-1", 100.00) .quantity(4.0) .category("room") .brand("lodge-o-rama") .variant("standard") .build() ``` ##### Define a product ```java Product product = new Product.Builder("Double Room - Econ Rate", "econ-1", 100.00) .quantity(4.0) .category("room") .brand("lodge-o-rama") .variant("standard") .build(); ``` #### 2. Summarize the transaction Build a `TransactionAttributes` for `Purchase`, `Checkout`, and `CheckoutOption` events. Include shipping and order-level coupons when applicable — order-level coupons belong here, not on individual products. ##### Summarize the transaction ```kotlin val transactionAttributes = TransactionAttributes("ORDER-12345") .setRevenue(149.99) .setTax(12.50) .setShipping(5.99) .setCouponCode("SUMMER20") ``` ##### Summarize the transaction ```java TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-12345") .setRevenue(149.99) .setTax(12.50) .setShipping(5.99) .setCouponCode("SUMMER20"); ``` #### 3. Log the commerce event Build a `CommerceEvent` with `CommerceEvent.Builder`, passing a product action constant from the table above and your product(s). Attach `transactionAttributes` when applicable, then call `MParticle.getInstance()?.logEvent(event)`. Pick the customer action you want to log: ##### Commerce event: PLP impression Log a product listing page (or category page) view as a product impression. Pass every visible product in a single `CommerceEvent` built with `addImpression`, and set the impression's list name to the list / category the customer is browsing. | Field | Type | Required | Description | | ---------- | ------ | -------- | ----------------------------------------------------------------------------------- | | `Name` | string | yes | List or category name (e.g. `"Mens Running Shoes"`). Becomes `list_name`. | | `Products` | array | yes | Product objects from `createProduct`. Set `Position` to each item's 1-indexed rank. | | `currency` | string | yes | ISO 4217 currency code (passed as event-level customAttribute). | ###### Example PLP impression event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Impression import com.mparticle.commerce.Product val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .category("Shoes") .brand("BrandX") .position(1) .build() val impression = Impression("Mens Running Shoes", product) val event = CommerceEvent.Builder(impression) .customAttributes(mapOf("currency" to "USD")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example PLP impression event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Impression; import com.mparticle.commerce.Product; Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .category("Shoes") .brand("BrandX") .position(1) .build(); Impression impression = new Impression("Mens Running Shoes", product); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(impression) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: ViewDetail Log when a customer opens a product detail page. | Field | Type | Required | Description | | ------------- | ------- | -------- | ----------------------------------- | | `productsku` | string | yes | Product SKU. | | `productname` | string | yes | Display name. | | `itemprice` | decimal | yes | Per-unit price at the time of view. | | `currency` | string | yes | ISO 4217 currency code. | | `list_name` | string | no | Set if the user arrived from a PLP. | ###### Example ViewDetail event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build() val event = CommerceEvent.Builder(Product.VIEW_DETAIL, product) .customAttributes(mapOf("currency" to "USD", "list_name" to "PLP-Running")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example ViewDetail event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build(); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); attributes.put("list_name", "PLP-Running"); CommerceEvent event = new CommerceEvent.Builder(Product.VIEW_DETAIL, product) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: AddToCart Log when a customer adds an item to the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units added. | | `itemprice` | decimal | yes | Per-unit price at time of add. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level coupon, if applied at add-time. | ###### Example AddToCart event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build() val event = CommerceEvent.Builder(Product.ADD_TO_CART, product) .customAttributes(mapOf("currency" to "USD")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example AddToCart event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build(); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(Product.ADD_TO_CART, product) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: RemoveFromCart Log when a customer removes an item from the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ----------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units removed. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example RemoveFromCart event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product val product = Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build() val event = CommerceEvent.Builder(Product.REMOVE_FROM_CART, product) .customAttributes(mapOf("currency" to "USD")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example RemoveFromCart event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; Product product = new Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build(); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(Product.REMOVE_FROM_CART, product) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: Cart page view Log when the customer arrives on the cart page. Since cart page views do not have a native product action, use `MPEvent.Builder` with the event name `"view_cart"` and `EventType.Other`. Pass the full cart contents as a custom attribute. | Field | Type | Required | Description | | --------------- | --------- | -------- | -------------------------------------------------------------------------- | | `event_name` | string | yes | Always `"view_cart"`. | | `event_type` | EventType | yes | Use `MParticle.EventType.Other`. | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `cartitemcount` | integer | yes | Number of cart lines. | | `totalprice` | decimal | yes | Cart total. | | `currency` | string | yes | ISO 4217 currency code. | | `couponcode` | string | no | Order-level promo, if applied. | Each entry in the `cartitems` array has the following shape: | Field | Type | Description | | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cartitemid` | string | Stable partner-side cart-line identifier. Usually equals `productsku` when there is one line per SKU; use a unique value if you allow multiple lines for the same SKU (e.g. gift-wrap variants). | | `productsku` | string | Product SKU / stock identifier. | | `productname` | string | Product display name. | | `productcategory` | string | Product category / taxonomy leaf. | | `productbrand` | string | Product brand. | | `productvariant` | string | Variant identifier (size, color, etc.). | | `itemprice` | decimal | Per-unit price at event time. | | `unitprice` | decimal | Per-unit list price pre-discount. Omit if equal to `itemprice`. | | `quantity` | integer | Units in this line. | | `currency` | string | ISO 4217 code. Omit if matches the top-level currency. | | `couponcode` | string | Coupon applied to this line (if any). Order-level promos belong in the top-level `couponcode` placement attribute. | | `productposition` | integer | 1-indexed rank of the product within a list or search results. | ###### Example cart page view event ```kotlin val cartItems = """[ {"cartitemid":"SKU-001","productsku":"SKU-001","productname":"Trail Runner v3","itemprice":129.95,"quantity":1}, {"cartitemid":"SKU-002","productsku":"SKU-002","productname":"Cushion Insole","itemprice":19.95,"quantity":2} ]""" val cartEvent = MPEvent.Builder("view_cart", EventType.Other) .customAttributes(mapOf( "cartitemcount" to "3", "totalprice" to "169.85", "currency" to "USD", "couponcode" to "SUMMER20", "cartitems" to cartItems )) .build() MParticle.getInstance()?.logEvent(cartEvent) ``` ###### Example cart page view event ```java String cartItems = "[" + "{\"cartitemid\":\"SKU-001\",\"productsku\":\"SKU-001\",\"productname\":\"Trail Runner v3\",\"itemprice\":129.95,\"quantity\":1}," + "{\"cartitemid\":\"SKU-002\",\"productsku\":\"SKU-002\",\"productname\":\"Cushion Insole\",\"itemprice\":19.95,\"quantity\":2}" + "]"; Map attributes = new HashMap<>(); attributes.put("cartitemcount", "3"); attributes.put("totalprice", "169.85"); attributes.put("currency", "USD"); attributes.put("couponcode", "SUMMER20"); attributes.put("cartitems", cartItems); MPEvent cartEvent = new MPEvent.Builder("view_cart", EventType.Other) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(cartEvent); } ``` ##### Commerce event: Checkout Log when the customer enters the checkout flow. Send the full set of cart products plus a `TransactionAttributes` summary. | Field | Type | Required | Description | | --------------- | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `totalprice` | decimal | yes | Cart total before tax/shipping. | | `cartitemcount` | integer | yes | Number of cart lines. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level promo, if applied. | ###### Example Checkout event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product import com.mparticle.commerce.TransactionAttributes val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build() val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build() val transactionAttributes = TransactionAttributes() .setCouponCode("SUMMER20") .setRevenue(169.85) val event = CommerceEvent.Builder(Product.CHECKOUT, product1) .products(listOf(product1, product2)) .transactionAttributes(transactionAttributes) .customAttributes(mapOf( "currency" to "USD", "cartitemcount" to "3", "totalprice" to "169.85" )) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example Checkout event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; import com.mparticle.commerce.TransactionAttributes; Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build(); Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build(); TransactionAttributes transactionAttributes = new TransactionAttributes() .setCouponCode("SUMMER20") .setRevenue(169.85); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); attributes.put("cartitemcount", "3"); attributes.put("totalprice", "169.85"); CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT, product1) .products(Arrays.asList(product1, product2)) .transactionAttributes(transactionAttributes) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: CheckoutOption (shipping) Log when the customer completes the shipping step. Use the `Product.CHECKOUT_OPTION` action, set `checkoutOptions("shipping")`, and pass the shipping selections as custom attributes. | Field | Type | Required | Description | | ---------------- | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `shippingmethod` | string | yes | `standard` / `express` / `next_day`. | | `zipcode` | string | yes | Shipping ZIP / postcode. | | `country` | string | yes | ISO 3166-1 alpha-2 country code. | | `totalprice` | decimal | yes | Cart total. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example CheckoutOption (shipping) event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build() val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build() val event = CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1) .products(listOf(product1, product2)) .checkoutOptions("shipping") .customAttributes(mapOf( "shippingmethod" to "express", "zipcode" to "94103", "country" to "US", "totalprice" to "169.85", "currency" to "USD" )) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example CheckoutOption (shipping) event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build(); Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build(); Map attributes = new HashMap<>(); attributes.put("shippingmethod", "express"); attributes.put("zipcode", "94103"); attributes.put("country", "US"); attributes.put("totalprice", "169.85"); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1) .products(Arrays.asList(product1, product2)) .checkoutOptions("shipping") .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: CheckoutOption (payment) Log when the customer completes the payment step. Use the `Product.CHECKOUT_OPTION` action, set `checkoutOptions("payment")`, and pass the payment method selected as custom attributes. | Field | Type | Required | Description | | ------------------------ | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `paymenttype` | string | yes | `credit_card` / `paypal` / `apple_pay` / etc. | | `payment_method` | string | no | Specific method when relevant (e.g. card brand). | | `paymentServiceProvider` | string | no | PSP identifier (e.g. `stripe`). Must be camelCase. | | `ccbin` | string | no | First 6-8 digits of the card, if a card was used. | | `totalprice` | decimal | yes | Cart total. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example CheckoutOption (payment) event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build() val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build() val event = CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1) .products(listOf(product1, product2)) .checkoutOptions("payment") .customAttributes(mapOf( "paymenttype" to "credit_card", "payment_method" to "visa", "paymentServiceProvider" to "stripe", "ccbin" to "424242", "totalprice" to "169.85", "currency" to "USD" )) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example CheckoutOption (payment) event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build(); Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build(); Map attributes = new HashMap<>(); attributes.put("paymenttype", "credit_card"); attributes.put("payment_method", "visa"); attributes.put("paymentServiceProvider", "stripe"); attributes.put("ccbin", "424242"); attributes.put("totalprice", "169.85"); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(Product.CHECKOUT_OPTION, product1) .products(Arrays.asList(product1, product2)) .checkoutOptions("payment") .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: Purchase Log when an order is confirmed. Send the full set of cart products plus a `TransactionAttributes` summary including order ID, revenue, tax, shipping, and any order-level coupon. | Field | Type | Required | Description | | --------------- | ------- | -------- | ------------------------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents at time of order, JSON-stringified before passing as a custom attribute. | | `transactionId` | string | yes | Order / transaction identifier. | | `totalprice` | decimal | yes | Order total (Revenue). | | `tax` | decimal | yes | Total tax on the order. | | `shipping` | decimal | yes | Shipping cost. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level promo, if applied. | | `cartitemcount` | integer | no | Number of cart lines. | ###### Example Purchase event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product import com.mparticle.commerce.TransactionAttributes val product1 = Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build() val product2 = Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build() val transactionAttributes = TransactionAttributes("ORDER-10482") .setRevenue(169.85) .setTax(14.20) .setShipping(5.99) .setCouponCode("SUMMER20") val event = CommerceEvent.Builder(Product.PURCHASE, product1) .products(listOf(product1, product2)) .transactionAttributes(transactionAttributes) .customAttributes(mapOf("currency" to "USD", "cartitemcount" to "3")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example Purchase event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; import com.mparticle.commerce.TransactionAttributes; Product product1 = new Product.Builder("Trail Runner v3", "SKU-001", 129.95).quantity(1.0).build(); Product product2 = new Product.Builder("Cushion Insole", "SKU-002", 19.95).quantity(2.0).build(); TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-10482") .setRevenue(169.85) .setTax(14.20) .setShipping(5.99) .setCouponCode("SUMMER20"); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); attributes.put("cartitemcount", "3"); CommerceEvent event = new CommerceEvent.Builder(Product.PURCHASE, product1) .products(Arrays.asList(product1, product2)) .transactionAttributes(transactionAttributes) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ##### Commerce event: Refund Log when an order (or a line within it) is refunded. Send only the products being refunded, plus a `TransactionAttributes` object referencing the original order. | Field | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------- | | `productsku` | string | yes | SKU of the refunded line(s). | | `quantity` | integer | yes | Units refunded. | | `transactionId` | string | yes | Original order ID being refunded against. | | `totalprice` | decimal | yes | Refunded amount. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example Refund event ```kotlin import com.mparticle.commerce.CommerceEvent import com.mparticle.commerce.Product import com.mparticle.commerce.TransactionAttributes val refundedProduct = Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build() val transactionAttributes = TransactionAttributes("ORDER-10482") .setRevenue(129.95) val event = CommerceEvent.Builder(Product.REFUND, refundedProduct) .transactionAttributes(transactionAttributes) .customAttributes(mapOf("currency" to "USD")) .build() MParticle.getInstance()?.logEvent(event) ``` ###### Example Refund event ```java import com.mparticle.commerce.CommerceEvent; import com.mparticle.commerce.Product; import com.mparticle.commerce.TransactionAttributes; Product refundedProduct = new Product.Builder("Trail Runner v3", "SKU-001", 129.95) .quantity(1.0) .build(); TransactionAttributes transactionAttributes = new TransactionAttributes("ORDER-10482") .setRevenue(129.95); Map attributes = new HashMap<>(); attributes.put("currency", "USD"); CommerceEvent event = new CommerceEvent.Builder(Product.REFUND, refundedProduct) .transactionAttributes(transactionAttributes) .customAttributes(attributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ### Event category: Custom events Track custom events using `MPEvent.Builder`, passing an event name, event type, and optional custom attributes. #### Show custom event types | Type | Use for | | -------------------------- | ------------------------------------------------------------- | | `EventType.Navigation` | User navigation flows and screen transitions within your app. | | `EventType.Location` | Location-based interactions and movements. | | `EventType.Search` | Search queries and search-related actions. | | `EventType.Transaction` | Financial transactions and purchase-related activity. | | `EventType.UserContent` | User-generated content like reviews, comments, or posts. | | `EventType.UserPreference` | User settings, preferences, and customization choices. | | `EventType.Social` | Social media interactions and sharing activities. | | `EventType.Other` | Anything that doesn't fit the categories above. | #### Log a custom event ```kotlin val event = MPEvent.Builder("video_watched", EventType.Navigation) .customAttributes(mapOf("category" to "Destination Intro", "title" to "Paris")) .build() MParticle.getInstance()?.logEvent(event) ``` #### Log a custom event ```java Map customAttributes = new HashMap<>(); customAttributes.put("category", "Destination Intro"); customAttributes.put("title", "Paris"); MPEvent event = new MPEvent.Builder("video_watched", EventType.Navigation) .customAttributes(customAttributes) .build(); if (MParticle.getInstance() != null) { MParticle.getInstance().logEvent(event); } ``` ## 6. Show a Placement Call `selectPlacements` on every payment and confirmation screen you want Rokt to render content on. Include one of the following page identifiers to specify the screen type and whether it's for testing or production: - `stg.rokt.conf`: A confirmation screen in a staging (or testing) environment. - `prod.rokt.conf`: A confirmation screen in a production environment. - `stg.rokt.payments`: A payments screen in a staging (or testing) environment. - `prod.rokt.payments`: A payments screen in a production environment. ### Placement attributes Pass these attributes in the `attributes` map of `selectPlacements`. Always supply the most recent value — attributes passed here override any earlier `setUserAttribute` calls. ### Show all placement attributes | Field | Type | Description | | ------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Customer email (unhashed). Used for identity resolution. | | `firstname` | string | Customer first name. Used for personalization. | | `lastname` | string | Customer last name. Used for personalization. | | `mobile` | string | Customer mobile number in E.164 format. Used for identity resolution. | | `confirmationref` | string | Order / confirmation reference number. Used for relevance and deduplication. | | `currency` | string | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance. | | `country` | string | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance. | | `language` | string | Customer's preferred language (ISO 639-1). Used for relevance. | | `totalprice` | decimal | Total cart value including tax and shipping. Used for relevance. | | `amount` | decimal | Cart subtotal before tax and shipping. Distinct from `totalprice`. Used for relevance. | | `cartItems` | array | Structured array of cart-line objects. Must be camelCase. Used for relevance. | | `couponcode` | string | Promo code applied to the order, if any. Used for relevance. | | `newcustomer` | boolean | Whether this is a first-time buyer. Used for relevance. | | `customertype` | string | `guest` or `logged_in`. Used for relevance. | | `value` | decimal | Customer's cumulative purchase value. Used for relevance. | | `subscriptionstatus` | string | Subscription state if applicable (`active`, `trial`, `churned`, `paused`, `none`). Used for relevance and eligibility. | | `customersegment` | string | Partner internal segmentation (e.g. `vip`, `at_risk`, `new`, `reactivated`). Used for relevance. | | `paymenttype` | string | Payment method selected (`credit_card`, `paypal`, `apple_pay`, etc.). Used for Pay+ eligibility. | | `paymentServiceProvider` | string | Comma-separated list of payment methods accepted on the screen (e.g. `applepay,paypal,cardpayment`). Values must be lowercase with no spaces. See [Payment Service Provider](https://docs.rokt.com/developer-reference/product-deep-dives/pay-plus/partners/data-integration#payment-service-provider) for the full list of accepted values. Used for Pay+ eligibility. | | `ccbin` | string | Credit card BIN (6-8 digits). Used for relevance. | | `billingaddress1` | string | Billing street address. Used for identity resolution and relevance. | | `billingaddress2` | string | Billing apartment / unit. Used for identity resolution. | | `billingcity` | string | Billing city. Used for relevance. | | `billingstate` | string | Billing state or province. Used for relevance. | | `billingzipcode` | string | Billing ZIP / postcode. Used for identity resolution and relevance. | | `billingname` | string | Full cardholder name on the billing address. Used for identity resolution. | | `shippingmethod` | string | Shipping method selected (`standard`, `express`, `next_day`). Used for relevance. | | `shippingname` | string | Full recipient name on the shipping address. Used for relevance. | | `shippingaddress1` | string | Shipping street address. Used for relevance. | | `shippingcity` | string | Shipping city. Used for relevance. | | `shippingstate` | string | Shipping state or province. Used for relevance. | | `shippingzipcode` | string | Shipping ZIP or postcode. Used for relevance. | | `shippingcountry` | string | Shipping country (ISO 3166-1 alpha-2). Used for relevance. | | `partnerpaymentreference` | string | Non-guessable identifier for the customer's vaulted payment method. Used for Shoppable Ads card forwarding. | | `last4digits` | string | Last 4 digits of the card used. Used for identity resolution. | | `plcc` | string | `"yes"` or `"no"` — whether the customer has a private-label credit card. Used for Pay+ relevance. | | `discountamount` | decimal | Order-level discount applied. Used for Pay+ relevance. | | `prescreen` | string | `"yes"` or `"no"` — whether the customer has pre-qualified for a credit offer. Used for Pay+ relevance. | | `adsexperience` | string | Pass `"shoppable"` when targeting a Shoppable Ads experience. | ### Placement position: Overlay Overlay placements render on top of your confirmation screen in a Rokt-managed container, requiring no changes to your app's existing layout. To insert an overlay placement, call `selectPlacements` once the confirmation screen loads: #### Overlay placement ```kotlin import com.mparticle.MParticle import com.mparticle.rokt.RoktConfig val attributes = mapOf( // Identity "email" to "j.smith@example.com", "firstname" to "Jenny", "lastname" to "Smith", "mobile" to "+13125551515", // Transaction "confirmationref" to "54321", "currency" to "USD", "country" to "US", "language" to "en", "totalprice" to "149.99", "couponcode" to "SUMMER20", // Customer context "newcustomer" to "false", "customertype" to "logged_in", "value" to "2340.00", "subscriptionstatus" to "active", "customersegment" to "vip", // Payment (include paymenttype and paymentServiceProvider for Pay+) "paymenttype" to "credit_card", "paymentServiceProvider" to "cardpayment", "ccbin" to "411112", // Billing address "billingaddress1" to "123 Main St", "billingcity" to "Brooklyn", "billingstate" to "NY", "billingzipcode" to "11201", // Shipping "shippingmethod" to "express", "shippingaddress1" to "175 Varick St", "shippingcity" to "New York", "shippingstate" to "NY", "shippingzipcode" to "10014", "shippingcountry" to "US" ) val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build() MParticle.getInstance()?.Rokt()?.selectPlacements( identifier = "RoktExperience", attributes = attributes, config = roktConfig ) ``` #### Overlay placement ```java import com.mparticle.MParticle; import com.mparticle.rokt.RoktConfig; Map attributes = new HashMap<>(); // Identity attributes.put("email", "j.smith@example.com"); attributes.put("firstname", "Jenny"); attributes.put("lastname", "Smith"); attributes.put("mobile", "+13125551515"); // Transaction attributes.put("confirmationref", "54321"); attributes.put("currency", "USD"); attributes.put("country", "US"); attributes.put("language", "en"); attributes.put("totalprice", "149.99"); attributes.put("couponcode", "SUMMER20"); // Customer context attributes.put("newcustomer", "false"); attributes.put("customertype", "logged_in"); attributes.put("value", "2340.00"); attributes.put("subscriptionstatus", "active"); attributes.put("customersegment", "vip"); // Payment (include paymenttype and paymentServiceProvider for Pay+) attributes.put("paymenttype", "credit_card"); attributes.put("paymentServiceProvider", "cardpayment"); attributes.put("ccbin", "411112"); // Billing address attributes.put("billingaddress1", "123 Main St"); attributes.put("billingcity", "Brooklyn"); attributes.put("billingstate", "NY"); attributes.put("billingzipcode", "11201"); // Shipping attributes.put("shippingmethod", "express"); attributes.put("shippingaddress1", "175 Varick St"); attributes.put("shippingcity", "New York"); attributes.put("shippingstate", "NY"); attributes.put("shippingzipcode", "10014"); attributes.put("shippingcountry", "US"); RoktConfig roktConfig = new RoktConfig.Builder() .colorMode(RoktConfig.ColorMode.LIGHT) .build(); if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) { MParticle.getInstance().Rokt().selectPlacements( "RoktExperience", attributes, null, null, null, roktConfig ); } ``` ### Placement position: Embedded Embedded placements render inline at a fixed position in your app that you control (for example, above the payment options on a cart screen). Both Thanks and Pay+ use embedded placements, but Pay+ must use embedded placements. #### 1. Add RoktEmbeddedView to your layout XML Add the `RoktEmbeddedView` to your layout XML at the position where you want the placement to render. Set its height to `wrap_content` so the placement can resize dynamically. #### RoktEmbeddedView in layout XML ```xml ``` #### 2. Wire up callbacks and call selectPlacements Use the `MpRoktEventCallback` interface to respond to placement events (load, unload, loading indicator state, etc.), then call `selectPlacements` from your activity passing the embedded view, callbacks, and config. #### Embedded placement with callbacks ```kotlin import com.mparticle.rokt.RoktConfig import com.mparticle.rokt.RoktEmbeddedView import com.mparticle.MpRoktEventCallback import com.mparticle.UnloadReasons class ConfirmActivity : Activity() { val callbacks = object : MpRoktEventCallback { override fun onLoad() { // Optional callback for when the Rokt placement loads } override fun onUnload(reason: UnloadReasons) { // Optional callback for when the Rokt placement unloads } override fun onShouldShowLoadingIndicator() { // Optional callback to show a loading indicator } override fun onShouldHideLoadingIndicator() { // Optional callback to hide a loading indicator } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val attributes = mapOf( "email" to "j.smith@example.com", "firstname" to "Jenny", "lastname" to "Smith", "billingzipcode" to "90210", "confirmationref" to "54321" ) val roktWidget = findViewById(R.id.roktEmbeddedView) val embeddedViews = mapOf("RoktEmbedded1" to WeakReference(roktWidget)) val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build() MParticle.getInstance()?.Rokt()?.selectPlacements( identifier = "RoktExperience", attributes = attributes, callbacks = callbacks, embeddedViews = embeddedViews, config = roktConfig ) } } ``` #### Embedded placement with callbacks ```java import com.mparticle.rokt.RoktConfig; import com.mparticle.rokt.RoktEmbeddedView; import com.mparticle.MpRoktEventCallback; import com.mparticle.UnloadReasons; public class ConfirmActivity extends Activity { private MpRoktEventCallback callbacks = new MpRoktEventCallback() { @Override public void onLoad() { // Optional callback for when the Rokt placement loads } @Override public void onUnload(UnloadReasons reason) { // Optional callback for when the Rokt placement unloads } @Override public void onShouldShowLoadingIndicator() { // Optional callback to show a loading indicator } @Override public void onShouldHideLoadingIndicator() { // Optional callback to hide a loading indicator } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); Map attributes = new HashMap<>(); attributes.put("email", "j.smith@example.com"); attributes.put("firstname", "Jenny"); attributes.put("lastname", "Smith"); attributes.put("billingzipcode", "90210"); attributes.put("confirmationref", "54321"); RoktEmbeddedView roktWidget = findViewById(R.id.roktEmbeddedView); Map> embeddedViews = new HashMap<>(); embeddedViews.put("RoktEmbedded1", new WeakReference<>(roktWidget)); RoktConfig roktConfig = new RoktConfig.Builder() .colorMode(RoktConfig.ColorMode.LIGHT) .build(); if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) { MParticle.getInstance().Rokt().selectPlacements( "RoktExperience", attributes, callbacks, embeddedViews, null, roktConfig ); } } } ``` > **Caution: Pay+** > > For Pay+ placements, include `paymenttype` and `paymentServiceProvider` in the `selectPlacements` call on each screen. `paymentServiceProvider` communicates what payment methods are available on the payment screen; `paymenttype` communicates what method the user paid with. ### Optional functions | Function | Purpose | | -------------- | ------------------------------ | | `Rokt.close()` | Auto-close overlay placements. | ### Additional configuration Pass optional parameters such as `RoktConfig` to customize the placement UI (e.g. dark/light mode, caching). Font typefaces can also be supplied as a map of PostScript names to `Typeface` objects. ### selectPlacements with RoktConfig and font typefaces ```kotlin val fontTypefaces: MutableMap> = HashMap() fontTypefaces["Arial-Bold"] = WeakReference(yourTypefaceObject) val roktConfig = RoktConfig.Builder().colorMode(RoktConfig.ColorMode.LIGHT).build() MParticle.getInstance()?.Rokt()?.selectPlacements( identifier = "RoktExperience", attributes = attributes, fontTypefaces = fontTypefaces, config = roktConfig ) ``` ### selectPlacements with RoktConfig and font typefaces ```java Map> fontTypefaces = new HashMap<>(); fontTypefaces.put("Arial-Bold", new WeakReference<>(yourTypefaceObject)); RoktConfig roktConfig = new RoktConfig.Builder() .colorMode(RoktConfig.ColorMode.LIGHT) .build(); if (MParticle.getInstance() != null && MParticle.getInstance().Rokt() != null) { MParticle.getInstance().Rokt().selectPlacements( "RoktExperience", attributes, null, null, fontTypefaces, roktConfig ); } ``` > **Note** > > If you want to update the identifier `RoktExperience` or embedded identifier `RoktEmbedded1` with a different value, contact your Rokt account manager to ensure Rokt placements are configured consistently. ### Events API The SDK+ provides placement lifecycle events as a stream through the `MParticle.getInstance()?.Rokt()?.events` API. Use Kotlin Flow to consume events produced by the SDK+. ### Subscribe to Placement Events ```kotlin import com.mparticle.MParticle // owner: LifecycleOwner owner.lifecycleScope.launch { owner.lifecycle.repeatOnLifecycle(Lifecycle.State.CREATED) { MParticle.getInstance()?.Rokt()?.events("RoktExperience")?.collect { roktEvent -> // Handle the event } } } ``` #### Standard events ### Show all standard events | Event | Description | Params | | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | ShowLoadingIndicator | Triggered before the SDK+ calls the Rokt backend. | | | HideLoadingIndicator | Triggered when the SDK+ receives a success or failure from the Rokt backend. | | | PlacementInteractive | Triggered when a placement has been rendered and is interactable. | placementId: String | | PlacementReady | Triggered when a placement is ready to display but has not rendered content yet. | placementId: String | | OfferEngagement | Triggered when the user engages with the offer. | placementId: String | | PositiveEngagement | Triggered when the user positively engages with the offer. | placementId: String | | FirstPositiveEngagement | Triggered when the user positively engages with the offer for the first time. | placementId: String, fulfillmentAttributes: FulfillmentAttributes | | OpenUrl | Triggered when the user presses a URL that is configured to be sent to the partner app. | placementId: String, url: String | | PlacementClosed | Triggered when a placement is closed by the user. | placementId: String | | PlacementCompleted | Triggered when the offer progression reaches the end and no more offers are available to display. Also triggered when cache is hit but the retrieved placement will not be displayed as it has previously been dismissed. | placementId: String | | PlacementFailure | Triggered when a placement could not be displayed due to some failure or when no placements are available to show. | placementId: String (optional) | | CartItemInstantPurchase | Triggered when the catalog item purchase is initiated by the user. | placementId: String, cartItemId: String, catalogItemId: String, currency: String, description: String, linkedProductId: String, totalPrice: Double, quantity: Int, unitPrice: Double | #### Global events Use `Rokt.globalEvents()` to subscribe to SDK+-level events that are not tied to a specific placement. ### Subscribe to global events ```kotlin import com.rokt.roktsdk.RoktEvent // owner: LifecycleOwner owner.lifecycleScope.launch { Rokt.globalEvents().collect { event -> if (event is RoktEvent.InitComplete) { // SDK+ initialization is complete } } } ``` ## 7. Appendix ### Appendix A: App configuration Applications can pass configuration settings through `RoktConfig` so the Android SDK+ uses your app's custom configuration instead of system defaults. #### ColorMode object | Value | Description | | ------ | ----------------------------------------- | | LIGHT | Application is in Light Mode | | DARK | Application is in Dark Mode | | SYSTEM | Application defaults to System Color Mode | #### EdgeToEdgeDisplay | Value | Description | | ---------------- | ------------------------------------------------------ | | `true` (default) | Application supports Edge to Edge display mode | | `false` | Application does not support Edge to Edge display mode | ```kotlin title="RoktConfig with ColorMode and EdgeToEdgeDisplay" import com.mparticle.MParticle import com.mparticle.rokt.RoktConfig val roktConfig = RoktConfig.Builder() .colorMode(RoktConfig.ColorMode.LIGHT) .edgeToEdgeDisplay(true) .build() MParticle.getInstance()?.Rokt()?.selectPlacements( identifier = "RoktExperience", attributes = attributes, config = roktConfig ) ``` #### CacheConfig object | Parameter | Description | | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cacheDurationInSeconds` | Optional duration in seconds for which the Rokt SDK+ should cache the experience. Maximum allowed value is 90 minutes; default is 90 minutes if not provided or invalid. | | `cacheAttributes` | Optional attributes to be used as cache key. If null, all attributes sent in `selectPlacements` will be used as the cache key. | ```kotlin title="Cache for 1200 seconds" import com.mparticle.rokt.CacheConfig import com.mparticle.rokt.RoktConfig // Cache the experience for 1200 seconds, using email and orderNumber as the cache key. val roktConfig = RoktConfig.Builder() .cacheConfig(CacheConfig( cacheDurationInSeconds = 1200, cacheAttributes = mapOf("email" to "j.smith@example.com", "orderNumber" to "123") )) .build() MParticle.getInstance()?.Rokt()?.selectPlacements( identifier = "RoktExperience", attributes = attributes, config = roktConfig ) ``` ### Appendix B: Jetpack Compose support with RoktLayout For screens implemented using Jetpack Compose, the SDK+ provides the `RoktLayout` composable for a modern, declarative integration of Rokt placements. `RoktLayout` supports Overlay, BottomSheet, and Embedded placement types without manually invoking `selectPlacements`. ```kotlin title="Jetpack Compose placement with RoktLayout" import com.mparticle.kits.RoktLayout import com.mparticle.MpRoktEventCallback import com.mparticle.UnloadReasons @Composable fun MainScreen(modifier: Modifier = Modifier) { Column( modifier = modifier .background(Color.LightGray) .padding(8.dp), ) { val attributes = mapOf( "email" to "j.smith@example.com", "firstname" to "Jenny", "lastname" to "Smith", "billingzipcode" to "90210", "confirmationref" to "54321" ) val callbacks = object : MpRoktEventCallback { override fun onLoad() = println("View loaded") override fun onUnload(reason: UnloadReasons) = println("View unloaded due to: $reason") override fun onShouldShowLoadingIndicator() = println("Show loading indicator") override fun onShouldHideLoadingIndicator() = println("Hide loading indicator") } val roktConfig = RoktConfig.Builder() .colorMode(RoktConfig.ColorMode.DARK) .cacheConfig(CacheConfig( cacheDurationInSeconds = 1200, cacheAttributes = mapOf("email" to "j.smith@example.com") )) .build() RoktLayout( sdkTriggered = true, identifier = "RoktExperience", attributes = attributes, location = "RoktEmbedded1", modifier = Modifier .fillMaxWidth() .background(Color.Black), mpRoktEventCallback = callbacks, config = roktConfig ) } } ``` #### Parameters | Parameter | Type | Description | | --------------------- | -------------------- | --------------------------------------------------------------------------- | | `sdkTriggered` | Boolean | Controls when the placement should be triggered. | | `identifier` | String | The identifier of the Rokt experience (e.g. `"RoktExperience"`). | | `location` | String? | Optional location name for embedded placements (e.g. `"RoktEmbedded1"`). | | `attributes` | Map\ | Map of attributes to pass to the placement. | | `modifier` | Modifier | Compose `Modifier` to customize layout, styling, and UI behavior. | | `mpRoktEventCallback` | MpRoktEventCallback | Optional callback to handle placement events (load, unload, loading state). | | `config` | RoktConfig? | Optional configuration for color mode, caching, etc. | ### Appendix C: Error handling The IDSync API is intended to be central to your app's state and is designed to be fast and highly-available. Similar to how your app may prevent users from logging in, logging out, or modifying their state without an internet connection — treat these APIs as gating operations to maintain a consistent user state. The SDK+ will not retry API calls automatically, but provides callback APIs so you can do so according to your business logic. If you do not implement error handling, you may see data consistency issues at scale. The SDK+ always returns the HTTP status and body of the underlying HTTP response. For client-side issues (device out of coverage, client-side timeout, invalid identity requests), the SDK+ returns `IdentityApi.UNKNOWN_ERROR` along with an informative error message. ```kotlin title="IDSync error handling" MParticle.getInstance()?.Identity()?.identify(identifyRequest) ?.addFailureListener { identityHttpResponse -> if (identityHttpResponse?.httpCode == IdentityApi.UNKNOWN_ERROR) { // Device is likely offline — retry the request } else if (identityHttpResponse?.httpCode == IdentityApi.THROTTLE_ERROR) { // Throttled (429) — retry with backoff } } ?.addSuccessListener { identityApiResult -> // Proceed with the identified user } ``` ```java title="IDSync error handling" MParticle.getInstance().Identity().identify(identifyRequest) .addFailureListener(new TaskFailureListener() { @Override public void onFailure(IdentityHttpResponse identityHttpResponse) { if (identityHttpResponse.getHttpCode() == IdentityApi.UNKNOWN_ERROR) { // Device is likely offline — retry the request } else if (identityHttpResponse.getHttpCode() == IdentityApi.THROTTLE_ERROR) { // Throttled (429) — retry with backoff } } }) .addSuccessListener(new TaskSuccessListener() { @Override public void onSuccess(IdentityApiResult identityApiResult) { // Proceed with the identified user } }); ``` ### Appendix D: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — the customer acts in a WebView (for example a payment page) then returns to the app. Pass `sessionId` and `sessionToken` into Android `setSession`. - **Native to web** — the customer starts in the app then continues in a WebView. Read the native session with `getSession` and seed Web `launcherOptions` before init. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; window.location.href = deepLink; ``` #### Setting the session Extract the session ID and session token from the deep link and pass them to the SDK+ with `setSession` before calling `selectPlacements`. ```kotlin title="Handle deep link and setSession" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val sessionId = intent.data?.getQueryParameter("sessionId") val sessionToken = intent.data?.getQueryParameter("sessionToken") if (!sessionId.isNullOrEmpty()) { MParticle.getInstance()?.Rokt()?.setSession( RoktSession( sessionId = sessionId, sessionToken = sessionToken, ), ) } // Proceed with your confirmation flow } ``` ```java title="Handle deep link and setSession" @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Uri data = getIntent().getData(); if (data != null) { String sessionId = data.getQueryParameter("sessionId"); String sessionToken = data.getQueryParameter("sessionToken"); if (sessionId != null && !sessionId.isEmpty()) { MParticle.getInstance().Rokt().setSession( new RoktSession(sessionId, sessionToken) ); } } // Proceed with your confirmation flow } ``` #### Passing from native to web After a native placement, read the current session and inject it into the WebView before Web SDK+ init. `getSession` returns `null` when there is no session or the token has expired — show a native placement first, or pass `sessionId` alone. ```kotlin title="Read native session for WebView" val session = MParticle.getInstance()?.Rokt()?.getSession() val sessionId = session?.sessionId val sessionToken = session?.sessionToken ``` ```java title="Read native session for WebView" RoktSession session = MParticle.getInstance().Rokt().getSession(); String sessionId = session != null ? session.getSessionId() : null; String sessionToken = session != null ? session.getSessionToken() : null; ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `setSession` before `selectPlacements` so native uses the web session. - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session. - Empty strings are ignored and will not update the session. - Always URL-encode the session ID and session token when passing as a query parameter. ## 8. Test Your Integration To confirm the SDK+ initializes and events log correctly: ### 1. Enable verbose SDK+ logging Enable verbose SDK+ logging before initialization so you can see what's being sent. ```kotlin title="Enable verbose SDK+ logging" MParticle.setLogLevel(MParticle.LogLevel.VERBOSE) ``` ### 2. Build and run your app Build and run your app with `environment = MParticle.Environment.Development`. ### 3. Trigger selectPlacements Trigger `selectPlacements` on the screen where the placement should render and confirm the placement loads. ### 4. Verify events Verify the events are logged and the `identifyRequest` call succeeds. ### Troubleshooting If the placement doesn't render or events don't appear, check the Android Logcat for Rokt SDK+ errors. Common issues: #### Initialization errors - Confirm `credentials` key and secret match the values from your Rokt account manager. - Confirm `MParticle.start(options)` runs in `Application.onCreate()` before any `selectPlacements` or `logEvent` call. #### Identity errors If the `identifyTask` failure listener fires, see [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#error-handling) for the `IdentityApi` error codes and retry guidance. Without error handling you may see data consistency issues at scale. #### Placement not rendering - Confirm the placement `identifier` (e.g. `RoktExperience`) matches what your Rokt account manager configured. - For embedded placements, confirm the embedded view identifier (e.g. `RoktEmbedded1`) matches the layout configuration. - Check that the attributes map contains at least `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`. #### SSL handshake errors when using a proxy While testing in your development environment, you might hit SSL handshake errors when an HTTP debugging proxy like Charles or Proxyman is running, or when you're behind a corporate network proxy. This is expected: the SDK+ pins its SSL certificate, and a proxy intercepts HTTPS by presenting its own certificate, which fails the pin. To let the proxy inspect SDK+ traffic, disable pinning in your development build by adding `setPinningDisabledInDevelopment(true)` to the `NetworkOptions` builder in your [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/android/#initialize-android). ### Disable SSL pinning for proxy debugging ```kotlin val networkOptions = NetworkOptions.builder() // Only takes effect in the development environment; production builds stay pinned. .setPinningDisabledInDevelopment(true) .build() ``` ### Disable SSL pinning for proxy debugging ```java NetworkOptions networkOptions = NetworkOptions.builder() // Only takes effect in the development environment; production builds stay pinned. .setPinningDisabledInDevelopment(true) .build(); ``` # 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.3 or later After running `pub add`, your `pubspec.yaml` should pin the package to **2.3 or later**. Shoppable Ads requires 2.0 or later. From 2.3, on iOS, the plugin warns at launch if the app links the SDK twice and reports a missing Rokt kit the same way as Android. #### pubspec.yaml ```yaml dependencies: mparticle_flutter_sdk: ^2.3.0 ``` ### Target: iOS From Flutter 3.44, Flutter adds iOS plugins to your app with Swift Package Manager by default, and `mparticle_flutter_sdk` brings in the mParticle SDK that way. Add the Rokt SDK+ with the same dependency manager your Flutter app uses: #### Dependency manager: Swift Package Manager (default) ##### 3. Add the Rokt SDK+ Swift package In Xcode, open `ios/Runner.xcworkspace` and set the **Runner** target's **iOS Deployment Target** to 15.6. Then select **File → Add Package Dependencies**, enter the URL below, set the dependency rule to **Up to Next Major Version** from 9.3.1, and add the **`RoktSDKPlus`** product to the **Runner** target. `RoktSDKPlus` includes the mParticle Rokt kit and `RoktPaymentExtension`. | Package | Repository URL | Product | | ----------------- | ------------------------------------------- | ------------- | | Rokt SDK+ for iOS | `https://github.com/ROKT/rokt-sdk-plus-ios` | `RoktSDKPlus` | Don't add any mParticle or Rokt pods, such as `pod 'RoktSDKPlus'` or `pod 'mParticle-Apple-SDK'`, to `ios/Podfile`. A pod adds a second copy of the SDK: the app still builds, but the Rokt kit registers with the wrong copy. `mparticle_flutter_sdk` 2.3 and later logs `[mParticle] The mParticle SDK is loaded more than once` at launch when this happens. Commit the Xcode project change and `ios/Runner.xcworkspace/xcshareddata/swiftpm/Package.resolved`. Swift Package Manager needs Flutter 3.41 or later. On Flutter 3.41 to 3.43, turn it on with `flutter config --enable-swift-package-manager`. #### Dependency manager: CocoaPods (deprecated) > **Caution** > > CocoaPods trunk becomes read-only on December 2, 2026, so no new mParticle or Rokt pod versions can be published after that date. Use this option only when Swift Package Manager is turned off for your app: on Flutter versions before 3.44, or with `enable-swift-package-manager: false` under `flutter: config:` in your app's `pubspec.yaml`. ##### 3. Add the Rokt SDK+ pod In `ios/Podfile`, set the platform to iOS 15.6 and add the Rokt SDK+ pod to the `Runner` target. Don't add `mParticle-Apple-SDK`; `mparticle_flutter_sdk` brings it in. The `mParticle-Apple-SDK` pod declares iOS 15.0 but depends on `RoktContracts`, which requires 15.6, so raise lower pod deployment targets in `post_install`. A Podfile can have only one `post_install` block, so replace the one Flutter generated with this one, and keep the rest of the Podfile as Flutter generated it. Then run `pod install` in the `ios` directory. ###### ios/Podfile ```ruby platform :ios, '15.6' target 'Runner' do use_frameworks! pod 'RoktSDKPlus', '~> 9.3' flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__)) end post_install do |installer| installer.pods_project.targets.each do |target| flutter_additional_ios_build_settings(target) target.build_configurations.each do |config| # Raise lower targets only; pods that need a later iOS keep their own. current = config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_s if current.match?(/\A\d+(\.\d+)*\z/) && Gem::Version.new(current) < Gem::Version.new('15.6') config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '15.6' end end end end ``` #### 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 mParticle core and Rokt Kit Gradle dependencies Add the mParticle core SDK and the Rokt Kit to your app's Gradle dependencies. `mparticle_flutter_sdk` builds against the Rokt Kit but doesn't add it to your app. The core SDK and the Rokt Kit are released together, so use the same version for both, at or above the one `mparticle_flutter_sdk` builds against, listed in its [`android/build.gradle`](https://github.com/mParticle/mparticle-flutter-sdk/blob/main/android/build.gradle). 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-core:6.1.9") implementation("com.mparticle:android-rokt-kit:6.1.9") } ``` ##### Gradle DSL: Groovy ###### android/app/build.gradle ```groovy dependencies { implementation "com.mparticle:android-core:6.1.9" implementation "com.mparticle:android-rokt-kit:6.1.9" } ``` #### 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/\/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 - (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 ``` 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 = []; 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 = []; 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 the top-level `couponcode` placement attribute. | | `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` / `applepay` / 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`, `applepay`, etc.). Used for Pay+ eligibility and Shoppable Ads payment method prioritization. | | `paymentServiceProvider` | string | Payment services offered on the page (`applepay`, `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
``` #### 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 `` wrapper described below. > **Caution: Requires SDK+ 2.0** > > Shoppable Ads require `mparticle_flutter_sdk` **2.0.0 or later** and **`RoktSDKPlus` 9.3 or later** 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`, which you added in [Step 1](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#install), so you don't add it separately. ##### 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) { 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 ``` #### 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\ | 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: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — the customer acts in a WebView then returns to the app. Pass `sessionId` and `sessionToken` into `setSession` on iOS or Android. - **Native to web** — the customer starts in the app then continues in a WebView. Read the native session with `getSession` and seed Web `launcherOptions` before init. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; window.location.href = deepLink; ``` #### Setting the session ID on iOS Extract the session ID and session token from the deep link and pass them to the SDK+ with `setSession` before calling `selectPlacements`. Add this to your `AppDelegate.swift`: ```swift title="Handle deep link and setSession (iOS)" func handleDeepLink(url: URL) { let components = URLComponents(url: url, resolvingAgainstBaseURL: false) let sessionId = components?.queryItems?.first(where: { $0.name == "sessionId" })?.value let sessionToken = components?.queryItems?.first(where: { $0.name == "sessionToken" })?.value if let sessionId, !sessionId.isEmpty { let session = MPRoktSession( sessionId: sessionId, sessionToken: sessionToken, expiresAt: nil ) MParticle.sharedInstance().rokt.setSession(session) } // Proceed with your confirmation flow } ``` #### Setting the session ID on Android Extract the session ID and session token from the deep link and pass them to the SDK+ with `setSession` before calling `selectPlacements`. Add this to your `MainActivity`: ```kotlin title="Handle deep link and setSession (Android)" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val sessionId = intent.data?.getQueryParameter("sessionId") val sessionToken = intent.data?.getQueryParameter("sessionToken") if (!sessionId.isNullOrEmpty()) { MParticle.getInstance()?.Rokt()?.setSession( RoktSession( sessionId = sessionId, sessionToken = sessionToken, ), ) } // Proceed with your confirmation flow } ``` #### Passing from native to web After a native placement, read the session from the iOS or Android SDK+ and inject it into the WebView before Web SDK+ init. `getSession` returns nil/`null` when there is no session or the token has expired. ```swift title="Read native session for WebView (iOS)" let session = MParticle.sharedInstance().rokt.getSession() let sessionId = session?.sessionId let sessionToken = session?.sessionToken ``` ```kotlin title="Read native session for WebView (Android)" val session = MParticle.getInstance()?.Rokt()?.getSession() val sessionId = session?.sessionId val sessionToken = session?.sessionToken ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `setSession` before `selectPlacements` so native uses the web session. - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session. - Empty strings are ignored and will not update the session. - Always URL-encode the session ID and session token 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`. ### Target: iOS #### iOS setup errors - `[mParticle] The mParticle SDK is loaded more than once` at launch: the SDK comes from both CocoaPods and Swift Package Manager. With Swift Package Manager, remove every mParticle and Rokt pod from `ios/Podfile`. - A `PlatformException` with `Rokt is unavailable` from a Rokt call: the Rokt kit isn't in the app. Add `RoktSDKPlus` as shown in [Step 1](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#install). From `mparticle_flutter_sdk` 2.3, iOS reports this the same way as Android. - `Compiling for iOS 15.0, but module 'RoktContracts' has a minimum deployment target of iOS 15.6` with CocoaPods: add the `post_install` block from [Step 1](https://docs.rokt.com/integration-guides/ecommerce/sdk/flutter/#install). - Swift Package Manager resolution fails, or the build uses an old minimum iOS version: run `flutter clean`, then in Xcode select **File → Packages → Reset Package Caches**. #### 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`.
# React Native 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/react-native/](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/) > > 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 React Native 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. > **Note** > > You'll write a few lines of native code when you initialize the SDK+ in [Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#initialize-rn). Every other step uses JavaScript through the `react-native-mparticle` package. ## 1. Add the Rokt SDK+ to Your React Native App ### 1. Install the React Native package Add the React Native SDK+ as a dependency to your application: #### Install the React Native package ```shell npm install react-native-mparticle --save ``` ### 2. Import the package into your app Import the package into your React Native app code and get an instance of the SDK: #### Import the React Native package ```javascript import MParticle from 'react-native-mparticle'; ``` Continue setting up the SDK+ on your native project. Use the **Target** pill above to switch between iOS and Android. ### Target: iOS React Native and `react-native-mparticle` install with CocoaPods. From version 4.0.0, `pod install` takes the mParticle SDK and the Rokt SDK+ for iOS from Swift Package Manager and links them into your app target. Pick the dependency manager for the Rokt SDK+: #### Dependency manager: Swift Package Manager (default) ##### 3. Configure your Podfile At the top of your `ios/Podfile`, before the first `target` block, set the iOS deployment target to 15.6 and list the Rokt SDK+ in `$RNMParticleSPMKits`. `RoktSDKPlus` includes the mParticle Rokt kit and `RoktPaymentExtension`, so don't also list `mParticle-Rokt`. To pin the mParticle core SDK to an exact version, also set `$RNMParticleSPMCoreVersion`. Don't declare any mParticle or Rokt pods, such as `pod 'RoktSDKPlus'` or `pod 'mParticle-Rokt'`. If a pod would add a second copy of the SDK, `pod install` stops and lists the pods to remove. ###### ios/Podfile ```ruby platform :ios, '15.6' # Before the first target block $RNMParticleSPMKits = ['RoktSDKPlus'] ``` ##### 4. Install pods Run `pod install`. It prints each Swift package it links and the exact version it pins. Commit the `.xcodeproj` change and `Package.resolved` so every build resolves the same versions. This works with static libraries and both `use_frameworks!` linkages, and no `pre_install` hook is needed. ###### Install pods ```shell bundle exec pod install ``` #### Dependency manager: CocoaPods (deprecated) > **Caution** > > CocoaPods trunk becomes read-only on December 2, 2026, and a future release of `react-native-mparticle` removes this option. `RoktSDKPlus` is supported only through Swift Package Manager, so Shoppable Ads placements, which need `RoktPaymentExtension`, require the Swift Package Manager setup. With CocoaPods, leave the payment-extension lines out of the initialization code in [Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#initialize-rn). ##### 3. Configure your Podfile At the top of your `ios/Podfile`, before the first `target` block, set `$RNMParticleDisableSPM = true`. Because the mParticle and Rokt pods contain Swift code, add a `pre_install` block that builds them as dynamic frameworks, then add the mParticle Rokt kit pod to your app target. If your project uses frameworks instead, skip the `pre_install` block and run `pod install` with `USE_FRAMEWORKS=static` or `USE_FRAMEWORKS=dynamic`. ###### ios/Podfile ```ruby platform :ios, '15.6' $RNMParticleDisableSPM = true pre_install do |installer| installer.pod_targets.each do |pod| if ['mParticle-Apple-SDK', 'mParticle-Apple-SDK-ObjC', 'mParticle-Apple-SDK-Swift', 'mParticle-Rokt', 'Rokt-Widget', 'RoktContracts', 'RoktUXHelper', 'DcuiSchema'].include?(pod.name) def pod.build_type Pod::BuildType.new(:linkage => :dynamic, :packaging => :framework) end end end end # Inside your app target pod 'mParticle-Rokt', '>= 9.3.1', '< 10.0' ``` ##### 4. Install pods Run `pod install` to apply the changes. If you use frameworks instead of the `pre_install` block, prefix the command with `USE_FRAMEWORKS=static` or `USE_FRAMEWORKS=dynamic`: ###### Install pods ```shell bundle exec pod install # Frameworks instead of the pre_install block: USE_FRAMEWORKS=static bundle exec pod install # or USE_FRAMEWORKS=dynamic ``` ### Target: Android The NPM install step above automatically pulls in the React framework and the mParticle core Android library. Pin the core library and add the Rokt Kit as implementation dependencies in your app project. #### 3. Add the mParticle core and Rokt Kit Gradle dependencies Add the mParticle core SDK and the Rokt Kit to your app's gradle dependencies. The core SDK and the Rokt Kit are released together, so use the same version for both. That version must be at or above the one `react-native-mparticle` builds against, listed in its [`android/build.gradle`](https://github.com/mParticle/react-native-mparticle/blob/main/android/build.gradle). Pin the core SDK too: otherwise Gradle resolves it to the newest 6.x release through `react-native-mparticle`, and it no longer matches the Rokt Kit. ##### Gradle DSL: Kotlin DSL ###### android/app/build.gradle.kts ```kotlin dependencies { implementation("com.mparticle:android-core:6.1.9") implementation("com.mparticle:android-rokt-kit:6.1.9") } ``` ##### Gradle DSL: Groovy ###### android/app/build.gradle ```groovy dependencies { implementation "com.mparticle:android-core:6.1.9" implementation "com.mparticle:android-rokt-kit:6.1.9" } ``` ## 2. Initialize the Rokt SDK+ Initialize the Rokt SDK+ on the native side. The SDK+ must be initialized before any other SDK+ API calls. Use the **Target** pill above to switch between iOS and Android. ### Target: iOS Insert the initialization snippet in your `AppDelegate` file. Replace `your-key` and `your-secret` with the values provided by your Rokt account manager. > **Caution** > > Call `registerPaymentExtension` after `MParticle.sharedInstance().start(with:)` and before `selectShoppableAds`. It is required for Shoppable Ads placements on iOS and needs the Swift Package Manager setup from [Step 1](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#install-rn). If you don't show Shoppable Ads, or you use the CocoaPods setup, leave out the `import RoktPaymentExtension` line and the registration block. #### AppDelegate initialization (Swift) ```swift import mParticle_Apple_SDK import RoktPaymentExtension // Shoppable Ads only 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 MParticle.sharedInstance().start(with: options) // Shoppable Ads only: register after MParticle.sharedInstance().start(), before selectShoppableAds if let paymentExt = RoktPaymentExtension( applePayMerchantId: "merchant.com.yourapp.rokt" ) { MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExt) } return true } ``` #### AppDelegate initialization (Objective-C) ```objectivec #import - (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; [[MParticle sharedInstance] startWithOptions:options]; return YES; } ``` When inserting the initialization snippet, you will see customizable fields for: #### 1. Entering your Rokt key and secret Set `your-key` and `your-secret` in `MParticleOptions(key:secret:)` to the values provided by your Rokt account manager. #### 2. Setting your data environment Set `options.environment` to `.development` while testing to route data to the Development environment, and `.production` to send live customer activity to Production. #### 3. Registering the payment extension Register `RoktPaymentExtension` after `MParticle.sharedInstance().start(with:)` and before `selectShoppableAds` to enable Shoppable Ads payments (including Apple Pay). Replace `merchant.com.yourapp.rokt` with your Apple Pay merchant ID. Required for all Shoppable Ads placements on iOS, and available only with the Swift Package Manager setup, which installs `RoktPaymentExtension` through `RoktSDKPlus`. The Stripe publishable key is configured in your **mParticle Rokt kit** settings (mParticle dashboard) — you only pass the Apple Pay merchant ID in code. `RoktPaymentExtension` is a Swift type; if your `AppDelegate` is Objective-C, do this from a small Swift file. ### Target: Android Configure the Android SDK using an `MParticleOptions` object and a builder class in the `onCreate()` of your `Application` class. > **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 class YourApplicationClass : Application() { override fun onCreate() { super.onCreate() 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 .build() MParticle.start(options) } } ``` #### Application.onCreate initialization (Java) ```java import com.mparticle.MParticle; import com.mparticle.MParticleOptions; public class YourApplicationClass extends Application { @Override public void onCreate() { super.onCreate(); 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.Production) // 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 .build(); MParticle.start(options); } } ``` When inserting the initialization snippet, you will see customizable fields for: #### 1. Entering your Rokt key and secret Set `your-key` and `your-secret` in `.credentials(...)` to the values provided by your Rokt account manager. #### 2. Setting your data environment Set `.environment(...)` to `MParticle.Environment.Development` while testing to route data to the Development environment, and `MParticle.Environment.Production` to send live customer activity to Production. > **Note** > > To identify the user and set additional user attributes, see [Step 3: Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#identify-rn) below. If you don't have the user's email at initialization, you can identify the user later — see [Error handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#error-handling-rn) for how to handle identity errors. ## 3. Identify the User The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#initialize-rn) 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 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. Set additional attributes via the identity callback To set additional user attributes, use the identity callback. If the `identifyRequest` succeeds, any user attributes you set inside the callback are assigned to the identified user. ### 3. Send the request using the method that matches the user's action Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action: - **`MParticle.Identity.login`:** call when the user logs in or creates an account. - **`MParticle.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.Identity.logout`:** call when the user logs out. Calling these methods transitions the SDK's record of the current user's state. The `login` and `logout` methods also automatically log a corresponding event to improve Rokt's attribution. For example, to identify a user named Jane Smith with the email address `j.smith@example.com`, mobile number `+13125551515`, and customer ID `cust_10482`: #### Identify Jane Smith ```javascript // 1. Create the identifyRequest object const request = new MParticle.IdentityRequest(); // Preferred: pass the customer's raw, unhashed email. // If you can only provide a SHA-256-hashed email, remove .email and use other instead — do not pass both. request.email = 'j.smith@example.com'; request.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 mobile — do not pass both. // (Called 'other2' on Android and 'mobileSha256' on iOS; both use this same field.) request.other2 = 'SHA-256 hashed mobile number'; // only if raw mobile unavailable request.mobile = '+13125551515'; request.customerId = 'cust_10482'; // 2. User attributes are set using the identity callback const identityCallback = (error, userId) => { if (error) { console.debug(error); } else { const user = new MParticle.User(userId); user.setUserAttribute('firstname', 'Jane'); user.setUserAttribute('lastname', 'Smith'); } }; // 3. Call one of the following methods that best matches the user's action: MParticle.Identity.login(request, identityCallback); // Call when the user logs in or creates an account MParticle.Identity.identify(request, 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. ### Set user attributes ```javascript import MParticle from 'react-native-mparticle'; // Retrieve the current user. This will only succeed if you have identified the user during SDK+ initialization or by calling the identify method. MParticle.Identity.getCurrentUser((currentUser) => { if (currentUser) { // Once you have 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.setUserAttributeArray('favorite-genres', ['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.logScreenEvent()` with the name of the screen (e.g. `"homepage"`, `"product_detail_page"`). Include any additional custom attributes in the info object. #### Log a screen view ```javascript import MParticle from 'react-native-mparticle'; MParticle.logScreenEvent('homepage', { '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 `MParticle.CommerceEvent.createProductActionEvent`, using a product action type 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 type | | -------------------------- | ------------------------------------------------ | | Product detail page viewed | `MParticle.ProductActionType.ViewDetail` | | Product clicked | `MParticle.ProductActionType.Click` | | Item added to cart | `MParticle.ProductActionType.AddToCart` | | Item removed from cart | `MParticle.ProductActionType.RemoveFromCart` | | Item added to wishlist | `MParticle.ProductActionType.AddToWishlist` | | Item removed from wishlist | `MParticle.ProductActionType.RemoveFromWishlist` | | Checkout flow initiated | `MParticle.ProductActionType.Checkout` | | Checkout option selected | `MParticle.ProductActionType.CheckoutOption` | | Order confirmed | `MParticle.ProductActionType.Purchase` | | Order refunded | `MParticle.ProductActionType.Refund` | Tracking a commerce event takes three phases: #### 1. Define the product Create an `MParticle.Product` with the product's name, SKU, price, and quantity. Set additional fields like `category`, `brand`, and `position` directly on the instance. ##### Define a product ```javascript const product = new MParticle.Product( 'Double Room - Econ Rate', 'econ-1', 100.00, 4 ); product.category = 'room'; product.brand = 'lodge-o-rama'; product.variant = 'standard'; ``` #### 2. Summarize the transaction Create an `MParticle.TransactionAttributes` for `Purchase`, `Checkout`, and `CheckoutOption` events. Include shipping and order-level coupons when applicable — order-level coupons belong here, not on individual products. ##### Summarize the transaction ```javascript const transactionAttributes = new MParticle.TransactionAttributes('ORDER-12345') .setRevenue(149.99) .setTax(12.50) .setShipping(5.99) .setCouponCode('SUMMER20'); ``` #### 3. Log the commerce event Build a commerce event with `MParticle.CommerceEvent.createProductActionEvent`, passing the product action type, your product(s), and (when applicable) the `transactionAttributes`. Then call `MParticle.logCommerceEvent`. 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 from `MParticle.Product`. Set `position` to each item's 1-indexed rank. | | `currency` | string | yes | ISO 4217 currency code (passed as event-level customAttribute). | ###### Example PLP impression ```javascript import MParticle from 'react-native-mparticle'; const product = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); product.position = 1; // 1-indexed rank in the list const impression = new MParticle.Impression('Mens Running Shoes', [product]); const commerceEvent = MParticle.CommerceEvent.createImpressionEvent([impression]); commerceEvent.currency = 'USD'; MParticle.logCommerceEvent(commerceEvent); ``` ##### 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. | ###### Example ViewDetail event ```javascript import MParticle from 'react-native-mparticle'; const product = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.ViewDetail, [product], ); commerceEvent.currency = 'USD'; commerceEvent.customAttributes = { 'listname': 'PLP-Running' }; MParticle.logCommerceEvent(commerceEvent); ``` ##### Commerce event: AddToCart Log when a customer adds an item to the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units added. | | `itemprice` | decimal | yes | Per-unit price at time of add. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level coupon, if applied at add-time. | ###### Example AddToCart event ```javascript import MParticle from 'react-native-mparticle'; const product = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.AddToCart, [product], ); commerceEvent.currency = 'USD'; MParticle.logCommerceEvent(commerceEvent); ``` ##### Commerce event: RemoveFromCart Log when a customer removes an item from the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ----------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units removed. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example RemoveFromCart event ```javascript import MParticle from 'react-native-mparticle'; const product = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.RemoveFromCart, [product], ); commerceEvent.currency = 'USD'; MParticle.logCommerceEvent(commerceEvent); ``` ##### 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 `MParticle.Event` with the event name `"view_cart"` and `Other` event type. Pass the full cart contents as custom attributes. | Field | Type | Required | Description | | --------------- | --------- | -------- | --------------------------------------------------------------- | | `event_name` | string | yes | Always `"view_cart"`. | | `event_type` | EventType | yes | Use `MParticle.EventType.Other`. | | `cartitems` | string | yes | JSON-serialized array of cart-line objects. Used for relevance. | | `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 the top-level `couponcode` placement attribute. | | `productposition` | integer | 1-indexed rank of the product within a list or search results. | ###### Example cart page view event ```javascript import MParticle from 'react-native-mparticle'; const 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 }, ]; const event = new MParticle.Event() .setName('view_cart') .setType(MParticle.EventType.Other) .setInfo({ cartitemcount: 3, totalprice: 169.85, currency: 'USD', couponcode: 'SUMMER20', cartitems: JSON.stringify(cartitems), }); MParticle.logMPEvent(event); ``` ##### 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. | ###### Example Checkout event ```javascript import MParticle from 'react-native-mparticle'; const product1 = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const product2 = new MParticle.Product('Cushion Insole', 'SKU-002', 19.95, 2); const transactionAttributes = new MParticle.TransactionAttributes('YOUR_CHECKOUT_ID') .setRevenue(169.85) .setCouponCode('SUMMER20'); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.Checkout, [product1, product2], transactionAttributes, ); commerceEvent.currency = 'USD'; commerceEvent.customAttributes = { cartitemcount: 3 }; MParticle.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. | 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. | ###### Example CheckoutOption (shipping) event ```javascript import MParticle from 'react-native-mparticle'; const product1 = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const product2 = new MParticle.Product('Cushion Insole', 'SKU-002', 19.95, 2); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.CheckoutOption, [product1, product2], ); commerceEvent.setCheckoutOptions('shipping'); commerceEvent.currency = 'USD'; commerceEvent.customAttributes = { shippingmethod: 'express', zipcode: '94103', country: 'US', totalprice: 169.85, }; MParticle.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. | 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. | ###### Example CheckoutOption (payment) event ```javascript import MParticle from 'react-native-mparticle'; const product1 = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const product2 = new MParticle.Product('Cushion Insole', 'SKU-002', 19.95, 2); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.CheckoutOption, [product1, product2], ); commerceEvent.setCheckoutOptions('payment'); commerceEvent.currency = 'USD'; commerceEvent.customAttributes = { paymenttype: 'credit_card', payment_method: 'visa', paymentServiceProvider: 'stripe', ccbin: '424242', totalprice: 169.85, }; MParticle.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. | 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. | ###### Example Purchase event ```javascript import MParticle from 'react-native-mparticle'; const product1 = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const product2 = new MParticle.Product('Cushion Insole', 'SKU-002', 19.95, 2); const transactionAttributes = new MParticle.TransactionAttributes('ORDER-10482') .setRevenue(169.85) .setTax(14.20) .setShipping(5.99) .setCouponCode('SUMMER20'); const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.Purchase, [product1, product2], transactionAttributes, ); commerceEvent.currency = 'USD'; commerceEvent.customAttributes = { cartitemcount: 3 }; MParticle.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. | Field | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------- | | `productsku` | string | yes | SKU of the refunded line(s). | | `quantity` | integer | yes | Units refunded. | | `transactionId` | string | yes | Original order ID being refunded against. | | `totalprice` | decimal | yes | Refunded amount. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example Refund event ```javascript import MParticle from 'react-native-mparticle'; const refundedProduct = new MParticle.Product('Trail Runner v3', 'SKU-001', 129.95, 1); const transactionAttributes = new MParticle.TransactionAttributes('ORDER-10482') // original order id .setRevenue(129.95); // refunded amount const commerceEvent = MParticle.CommerceEvent.createProductActionEvent( MParticle.ProductActionType.Refund, [refundedProduct], transactionAttributes, ); commerceEvent.currency = 'USD'; MParticle.logCommerceEvent(commerceEvent); ``` ### Event category: Custom events Track custom events using `MParticle.Event`, passing an event name, event type, and optional custom attributes. #### Show custom event types | Type | Use for | | ------------------------------------ | ------------------------------------------------------------- | | `MParticle.EventType.Navigation` | User navigation flows and screen transitions within your app. | | `MParticle.EventType.Location` | Location-based interactions and movements. | | `MParticle.EventType.Search` | Search queries and search-related actions. | | `MParticle.EventType.Transaction` | Financial transactions and purchase-related activity. | | `MParticle.EventType.UserContent` | User-generated content like reviews, comments, or posts. | | `MParticle.EventType.UserPreference` | User settings, preferences, and customization choices. | | `MParticle.EventType.Social` | Social media interactions and sharing activities. | | `MParticle.EventType.Other` | Anything that doesn't fit the categories above. | #### Log a custom event ```javascript import MParticle from 'react-native-mparticle'; const event = new MParticle.Event() .setName('video_watched') .setType(MParticle.EventType.Navigation) .setInfo({ category: 'Destination Intro', title: 'Paris' }); MParticle.logMPEvent(event); ``` ## 6. Show a Placement Call `selectPlacements` on every payment and confirmation screen you want Rokt to render content on. Include one of the following page identifiers to specify the screen type and whether it's for testing or production: - `stg.rokt.conf`: A confirmation screen in a staging (or testing) environment. - `prod.rokt.conf`: A confirmation screen in a production environment. - `stg.rokt.payments`: A payments screen in a staging (or testing) environment. - `prod.rokt.payments`: A payments screen in a production environment. 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/react-native/#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` map of `selectPlacements`. Always supply the most recent value — attributes passed here override any earlier `setUserAttribute` calls. ### Show all placement attributes | Field | Type | Description | | ------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Customer email (unhashed). Used for identity resolution. | | `firstname` | string | Customer first name. Used for personalization. | | `lastname` | string | Customer last name. Used for personalization. | | `mobile` | string | Customer mobile number in E.164 format. Used for identity resolution. | | `confirmationref` | string | Order / confirmation reference number. Used for relevance and deduplication. | | `currency` | string | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance. | | `country` | string | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance. | | `language` | string | Customer's preferred language (ISO 639-1). Used for relevance. | | `totalprice` | decimal | Total cart value including tax and shipping. Used for relevance. | | `amount` | 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 ```javascript import MParticle from 'react-native-mparticle'; const 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', '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', }; const roktConfig = MParticle.Rokt.createRoktConfig('light'); MParticle.Rokt.selectPlacements( 'RoktExperience', // identifier attributes, // attributes map undefined, // no embedded placeholders for overlay roktConfig, // configuration ); ``` ### 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. Add RoktLayoutView to your view hierarchy Place a `RoktLayoutView` in your screen at the position where you want the placement to render. Its `placeholderName` identifies the view, and you pass the same name to `selectPlacements`. #### 2. Call selectPlacements with the placeholder name Build your attributes and pass the `placeholderName` of each `RoktLayoutView` in the `placeholders` array. The view does not need to be mounted yet: the SDK+ waits up to 2 seconds for each named placeholder, so you can call `selectPlacements` from `useEffect` on the screen that renders it. From version 4.0.0, `placeholders` accepts only an array of names. A map of names to node handles logs an error, and the placement is requested without embedded views. See the [React Native SDK+ 4.0 migration guide](https://docs.rokt.com/developer-reference/sdks/sdk-migration-guide#react-native-sdk-plus-40). #### Embedded placement ```javascript import React, { useEffect } from 'react'; import MParticle, { RoktLayoutView } from 'react-native-mparticle'; const ConfirmationScreen = () => { useEffect(() => { const attributes = { 'email': 'j.smith@example.com', 'firstname': 'Jenny', 'lastname': 'Smith', 'billingzipcode': '90210', 'confirmationref': '54321', }; // Each name must match the placeholderName of a RoktLayoutView MParticle.Rokt.selectPlacements( 'RoktExperience', attributes, ['RoktEmbedded1'], ); }, []); // Integrate the RoktLayoutView in the view hierarchy return ; }; ``` For `RoktLayoutView` props and mount-timing behavior, see [Appendix B: RoktLayoutView component](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#appendix-b-roktlayoutview-component). > **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 placements (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 are supported on **iOS only** in the React Native SDK+. The Android path of this SDK+ does not support interstitial placements. The code below must only be invoked on the iOS path of your app. To enable interstitial placements on iOS, first ensure the iOS-specific setup is complete: 1. Configure Apple Pay for your iOS app — 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). 2. Register `RoktPaymentExtension` in your React Native iOS `AppDelegate` after `MParticle.sharedInstance().start()` and before `selectShoppableAds`. The registration snippet is included in the [iOS initialization code in Step 2](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#initialize-rn). If no payment extension is registered, Shoppable Ads can fire a `PlacementFailure` event. > **Note: Expo** > > If you use Expo, include the Rokt SDK+ in your config plugin (`iosKits: ["RoktSDKPlus"]` in `app.json`). With the default Swift Package Manager setup, this also adds `RoktPaymentExtension`. The config plugin does not add the payment-extension registration automatically — after `expo prebuild`, add it to the generated `AppDelegate` on the same launch path as mParticle initialization, and re-apply it if you regenerate native projects with `--clean`. Then call `selectShoppableAds` from your React Native JavaScript layer with the confirmation-screen identifier. The method returns a Promise and renders the Shoppable Ads experience as an overlay — no embedded placeholders are required. On iOS, the SDK+ renders it when an eligible offer is available. #### Display Shoppable Ads (iOS only) ```javascript import { Platform } from 'react-native'; import MParticle from 'react-native-mparticle'; // Interstitial placements are iOS only — do not invoke on Android if (Platform.OS === 'ios') { const 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', 'amount': '137.50', // 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 (required for Shoppable Ads order fulfillment) 'shippingaddress1': '175 Varick St', 'shippingcity': 'New York', 'shippingstate': 'NY', 'shippingzipcode': '10014', 'shippingcountry': 'US', }; const roktConfig = MParticle.Rokt.createRoktConfig('system'); // selectShoppableAds returns a Promise; listen for native Rokt events for purchase outcomes MParticle.Rokt.selectShoppableAds( 'prod.rokt.conf', // use 'stg.rokt.conf' in test environments attributes, roktConfig, ).catch((error) => console.debug(error)); } ``` Subscribe to the `CartItemInstantPurchase` and related Shoppable Ads events via the [Events API](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#events-api) to handle purchase flows initiated from the interstitial experience. ### Optional functions | Function | Purpose | | ------------------------ | ------------------------------ | | `MParticle.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 ```javascript import MParticle from 'react-native-mparticle'; const roktConfig = MParticle.Rokt.createRoktConfig( 'light', MParticle.Rokt.createCacheConfig(1200, { 'email': 'j.smith@example.com', 'orderNumber': '123' }), ); MParticle.Rokt.selectPlacements( 'RoktExperience', attributes, undefined, // no embedded placeholders for overlay 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 `NativeEventEmitter` mechanism. ### Subscribe to placement events ```javascript import { NativeEventEmitter } from 'react-native'; import MParticle from 'react-native-mparticle'; const eventManagerEmitter = new NativeEventEmitter(MParticle.RoktEventManager); eventManagerEmitter.addListener('RoktEvents', data => { console.log(`event received ${JSON.stringify(data)}`); }); ``` #### 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 | | 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 (iOS only). | placementId: String, cartItemId: String, catalogItemId: String, currency: String, description: String, linkedProductId: String, totalPrice: number, quantity: number, unitPrice: number | ## 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 | #### EdgeToEdgeDisplay (Android only) This boolean controls whether the Rokt SDK+ renders in edge-to-edge display mode on Android (default `true`). Set to `false` if your app does not support edge-to-edge display. ```kotlin title="EdgeToEdgeDisplay" import com.mparticle.rokt.RoktConfig val roktConfig = RoktConfig.Builder() .edgeToEdgeDisplay(true) .build() ``` #### CacheConfig object | Parameter | Description | | ----------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cacheDuration` | 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. | ```javascript title="RoktConfig with ColorMode and CacheConfig" import MParticle from 'react-native-mparticle'; // Cache the experience for 1200 seconds, using email and orderNumber as the cache key. const roktConfig = MParticle.Rokt.createRoktConfig( 'light', MParticle.Rokt.createCacheConfig( 1200, { 'email': 'j.smith@example.com', 'orderNumber': '123' } ), ); MParticle.Rokt.selectPlacements( 'RoktExperience', attributes, undefined, // no embedded placeholders for overlay roktConfig, ); ``` ### Appendix B: RoktLayoutView component For embedded placements, the React Native SDK+ provides the `RoktLayoutView` component, which renders a Rokt placement at its position in your app's view hierarchy. Pass its `placeholderName` to `selectPlacements` as shown in the [embedded placement example](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#placement-rn). #### Parameters | Parameter | Type | Description | | ----------------- | ------ | ------------------------------------------------------------------------------------------------------------------------------------------ | | `placeholderName` | string | The embedded view identifier (for example, `"RoktEmbedded1"`). Must match a name in the `placeholders` array passed to `selectPlacements`. | #### Behavior - **Unmounted placeholders delay the request by up to 2 seconds.** If a named `RoktLayoutView` has not mounted when you call `selectPlacements`, the SDK+ waits for it, then calls Rokt with the views it has. A misspelled or never-rendered name therefore delays the request by 2 seconds and logs `Cannot resolve placeholder`. - **A waiting call can end in `PlacementFailure`.** If `MParticle.Rokt.close()` runs, or a newer `selectPlacements` call with the same identifier replaces it, before its placeholders mount, the waiting call emits a `PlacementFailure` event. Version 4.0.0 removed the earlier map of `placeholderName` to `findNodeHandle(ref)`. Version 3.4.0 and later 3.x releases accept both forms, so you can switch to names before you upgrade. See the [React Native SDK+ 4.0 migration guide](https://docs.rokt.com/developer-reference/sdks/sdk-migration-guide#react-native-sdk-plus-40). ### 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. ```javascript title="IDSync error handling" import MParticle from 'react-native-mparticle'; const request = new MParticle.IdentityRequest(); request.email = 'j.smith@example.com'; MParticle.Identity.identify(request, (error, userId) => { if (error) { // Inspect error.code to determine the cause: // - Network errors: retry the request // - Throttle errors (429): retry with backoff console.debug('Identity error:', error); } else { // Proceed with the identified user const user = new MParticle.User(userId); user.setUserAttribute('firstname', 'Jane'); } }); ``` #### iOS error codes On iOS, the native `MPIdentityErrorResponseCode` enum defines the following client-side codes. Inspect `error.code` in the native `onIdentifyComplete` callback to determine the cause: | `MPIdentityErrorResponseCode` | Description | | ----------------------------------------------- | -------------------------------------------------------------------------------------- | | `MPIdentityErrorResponseCodeRequestInProgress` | An IDSync HTTP request was not performed because one is already 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 because the SDK+ is disabled due to opt-out. | | `MPIdentityErrorResponseCodeUnknown` | The IDSync HTTP request failed due to an unknown error. | In addition to the client-side codes above, `error.code` may contain a server-generated HTTP status code: | Value | Description | | ----- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | 400 | The IDSync HTTP call failed due to an invalid request body. Inspect the error details for more information. | | 401 | The IDSync HTTP call failed due to an authentication error. Verify that your API key is correct. | | 403 | The IDSync HTTP call failed because this operation is not provisioned for your account. Contact your Rokt account manager to enable it. | | 429 | The IDSync HTTP call was throttled and should be retried with exponential backoff. This may indicate a user "hotkey" or an incorrect implementation resulting in higher than expected IDSync volume. | | 5xx | The IDSync HTTP call failed due to a Rokt server-side issue. Contact your account representative for additional information. | #### Android error codes On Android, the IDSync API always returns the HTTP status code and body of the underlying HTTP response. For client-side failures (device offline, timeout, invalid request), the SDK+ returns `IdentityApi.UNKNOWN_ERROR`. For throttling (HTTP 429), it returns `IdentityApi.THROTTLE_ERROR`. Handle both in your failure listener: ```kotlin title="Android IDSync error handling (Kotlin)" MParticle.getInstance()?.Identity()?.identify(identifyRequest) ?.addFailureListener { identityHttpResponse -> if (identityHttpResponse?.httpCode == IdentityApi.UNKNOWN_ERROR) { // Device is likely offline — retry the request } else if (identityHttpResponse?.httpCode == IdentityApi.THROTTLE_ERROR) { // Throttled (429) — retry with backoff } } ``` ### Appendix D: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — pass `sessionId` (and `sessionToken` when you have it) into the app, then call `setSessionId`. - **Native to web** — the customer starts in the app then continues in a WebView. Read the session from iOS `getSession` or Android `getSession` (id + token) and seed Web `launcherOptions` before init. React Native `setSessionId` is id-only. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; 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`. Handle this in your native AppDelegate (iOS) or Activity (Android) before the React Native layer loads: ```javascript title="Set sessionId from deep link (React Native)" import { Linking } from 'react-native'; import MParticle from 'react-native-mparticle'; // Listen for incoming deep links Linking.addEventListener('url', ({ url }) => { const sessionId = new URL(url).searchParams.get('sessionId'); if (sessionId) { void MParticle.Rokt.setSessionId(sessionId); } }); ``` #### Passing from native to web After a native placement, read the session from the iOS or Android SDK+ and inject it into the WebView before Web SDK+ init. ```swift title="Read native session for WebView (iOS)" let session = MParticle.sharedInstance().rokt.getSession() let sessionId = session?.sessionId let sessionToken = session?.sessionToken ``` ```kotlin title="Read native session for WebView (Android)" val session = MParticle.getInstance()?.Rokt()?.getSession() val sessionId = session?.sessionId val sessionToken = session?.sessionToken ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `setSessionId` before `selectPlacements` so native uses the web session. - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session. - Empty strings are ignored and will not update the session. - Always URL-encode the session ID and session token when passing as a query parameter. ## 8. Test Your Integration To confirm the SDK+ initializes and events log correctly: ### 1. Enable verbose SDK+ logging Enable verbose SDK+ logging before initialization so you can see what's being sent. ```javascript title="Enable verbose SDK+ logging" // For iOS verbose logging, add to your Swift AppDelegate: // MParticle.sharedInstance().logLevel = .verbose // For Android verbose logging, add to your Application class before MParticle.start(): // MParticle.setLogLevel(MParticle.LogLevel.VERBOSE) ``` ### 2. Build and run against a development key Build and run your app with the environment set to `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 native console (Xcode for iOS, Logcat for Android) for Rokt SDK+ errors. Common issues: #### Initialization errors - Confirm the key and secret in your native initialization match the values from your Rokt account manager. - Confirm the native SDK+ `start` call runs before any `selectPlacements` or event logging calls. - For iOS Shoppable Ads, confirm `RoktPaymentExtension` is registered after `start()` and before `selectShoppableAds`. #### Identity errors If the identity callback fires with an error, see [Error handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/react-native/#error-handling-rn) for 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 `placeholderName` on the `RoktLayoutView` component. - Check that the attributes map contains at least `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`. - For interstitial placements, confirm `Platform.OS === 'ios'` before calling `selectShoppableAds` — interstitial placements are not supported on Android. # 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() { #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() { #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() { { "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 { { "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 { { "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() { { "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 { // 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() .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 ``` #### 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 { ["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() { { "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 { // 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\ | | 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() { { "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: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — pass `sessionId` (and `sessionToken` when you have it) into the app, then call `SetSessionId`. - **Native to web** — the customer starts in the app then continues in a WebView. Read the session from iOS `getSession` or Android `getSession` and seed Web `launcherOptions` before init. MAUI `SetSessionId` is id-only. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; 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 ``` #### Passing from native to web After a native placement, read the session from the iOS or Android SDK+ and inject it into the WebView before Web SDK+ init. ```swift title="Read native session for WebView (iOS)" let session = MParticle.sharedInstance().rokt.getSession() let sessionId = session?.sessionId let sessionToken = session?.sessionToken ``` ```kotlin title="Read native session for WebView (Android)" val session = MParticle.getInstance()?.Rokt()?.getSession() val sessionId = session?.sessionId val sessionToken = session?.sessionToken ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `SetSessionId` before `SelectPlacements` so native uses the web session. - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session. - Empty strings are ignored and will not update the session. - Always URL-encode the session ID and session token 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`. # Cordova 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/cordova/](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/) > > 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 Cordova 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. ## 1. Add the Rokt SDK+ to Your Cordova App Install the SDK+ and the Rokt kit plugin: ### Install Cordova plugins ```bash cordova plugin add @mparticle/cordova-sdk cordova plugin add @mparticle/cordova-rokt-kit ``` Version 5.0 of the plugins requires Cordova CLI 12 or later. ### Target: iOS On iOS, the plugins add the mParticle SDK and the Rokt kit as Swift packages, so CocoaPods is not used and no `Podfile` is generated. This requires cordova-ios 8 or later and an iOS deployment target of 15.0 or later. Set the deployment target in `config.xml` before you add the iOS platform: #### config.xml ```xml ``` #### Add the iOS platform ```bash cordova platform add ios@8 ``` If you're upgrading from version 4.x, see the [Cordova SDK+ 5.0 migration guide](https://docs.rokt.com/developer-reference/sdks/sdk-migration-guide#cordova-sdk-plus-50). ## 2. Initialize the Rokt SDK+ Insert the following initialization snippet in the relevant native entry point for each platform. 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. You can find a full example in the [example app](https://github.com/mParticle/cordova-plugin-mparticle/tree/main/example). ### Target: iOS and Android When inserting the initialization snippet, you will see customizable fields for: #### 1. Entering your Rokt key and secret Set `your-key` and `your-secret` to the values provided by your Rokt account manager. (iOS uses `MParticleOptions(key:secret:)`. Android uses `.credentials(...)`.) #### 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. Identifying your user and setting attributes In `identifyRequest`, pass the user's raw, un-hashed email. For hashed emails and other identifiers, see [Supported user identifiers](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#supported-user-identifiers). Once identified, set additional user attributes via the success callback (iOS: `onIdentifyComplete`. Android: wire the request into the options builder via `.identify(identifyRequest)` and use a success listener — see [Step 3: Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#identify-cordova) for the pattern). > **Note** > > Always include `identifyRequest` in the initialization snippet. If you don't have the user's email at initialization, omit the email assignment (iOS) or the `.email(...)` call (Android) — the SDK+ will still initialize, and you can identify the user later via [3. Identify the User](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#identify-cordova). See [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#error-handling) for how to handle identify failures — without error handling you may see data consistency issues at scale. ### Target: iOS cordova-ios 8 generates `platforms/ios/App/AppDelegate.swift`. Add the initialization snippet to it. Cordova regenerates `platforms/`, so keep your copy outside it and copy it in with an `after_prepare` hook, as the example app does with its [hook](https://github.com/mParticle/cordova-plugin-mparticle/blob/main/example/hooks/after_prepare/010_copy_platform_specific_files.js) and [`AppDelegate.swift`](https://github.com/mParticle/cordova-plugin-mparticle/blob/main/example/platform_overrides/ios/AppDelegate.swift). #### AppDelegate initialization (iOS) ```swift import UIKit import mParticle_Apple_SDK @main #if compiler(>=6.1) @objc @implementation #else @_objcImplementation #endif extension AppDelegate { open override func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { let options = MParticleOptions(key: "your-key", secret: "your-secret") // Specify the data 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 // Identify the current user: // If you do not have the user's email address, you can pass in a null value let request = MPIdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email address in 'email'. // If you can only provide a SHA-256-hashed email, set it in 'other' instead of email — do not pass both. request.email = "j.smith@example.com" // request.setIdentity("sha256 hashed email goes here", identityType: .other) // only if raw email unavailable options.identifyRequest = request options.onIdentifyComplete = { apiResult, error in if let user = apiResult?.user { // If the user is identified, set additional user attributes user.setUserAttribute("example attribute key", value: "example attribute value") } } MParticle.sharedInstance().start(with: options) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } // Keep the rest of the generated AppDelegate, such as application(_:configurationForConnecting:options:). } ``` ### Target: Android #### MainActivity initialization (Android) ```java import com.mparticle.MParticle; import com.mparticle.MParticleOptions; public class MainActivity extends CordovaActivity { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); // Enable Cordova apps to be started in the background Bundle extras = getIntent().getExtras(); if (extras != null && extras.getBoolean("cdvStartInBackground", false)) { moveTaskToBack(true); } // Set by in config.xml loadUrl(launchUrl); // Identify the current user: // If you do not have the user's email address, you can pass in a null value IdentityApiRequest identifyRequest = IdentityApiRequest.withEmptyUser() // Preferred: pass the customer's raw, unhashed email via .email(). // If you can only provide a SHA-256-hashed email, remove .email() and use .userIdentity(Other) instead — do not pass both. .email("j.smith@example.com") // .userIdentity(MParticle.IdentityType.Other, "SHA-256 hashed email") // only if raw email unavailable // If you can only provide a SHA-256-hashed mobile number, use 'other2' instead of MobileNumber — do not pass both. // .userIdentity(MParticle.IdentityType.Other2, "SHA-256 hashed mobile number") // only if raw mobile unavailable .userIdentity(MParticle.IdentityType.MobileNumber, "+13125551515") .customerId("cust_10482") .build(); MParticleOptions options = MParticleOptions.builder(this) .credentials( "your-key", // The key provided by your Rokt account manager "your-secret" // The secret provided by your Rokt account manager ) // Specify the data environment with environment: // Set it to Development if you are still testing your integration. // Set it to Production if your integration is ready for production data. // The default is AutoDetect which attempts to detect the environment automatically. .environment(MParticle.Environment.Development) .identify(identifyRequest) .build(); MParticle.start(options); } } ``` ## 3. Identify the User The [SDK+ initialization script](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#initialize-cordova) 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 `email` field. ### 2. Create an identityCallback To set additional user attributes, create an `identityCallback`. If the `identifyRequest` succeeds, then any user attributes you set inside the callback are assigned to the identified user. ### 3. Send the request using the method that matches the user's action Pass the `identifyRequest` (and optional `identityCallback`) to the method that matches the user's action: - **`identity.login`:** call when the user logs in or creates an account. - **`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). - **`identity.logout`:** call when the user logs out. Calling these methods transitions the SDK's record of the current user's state. The `login` and `logout` methods also automatically log a corresponding event to improve Rokt's attribution. For example, to identify a user named Jane Smith with the email address `j.smith@example.com`, mobile number `+13125551515`, and customer ID `cust_10482`: #### Identify Jane Smith ```javascript // 1. Create the identifyRequest object var identifyRequest = new mparticle.IdentityRequest(); // Preferred: pass the customer's raw, unhashed email address. // If you can only provide a SHA-256-hashed email, use setUserIdentity with 'other' instead — do not pass both. identifyRequest.setEmail('j.smith@example.com'); identifyRequest.setUserIdentity(mparticle.UserIdentityType.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. // (Called 'other2' on Android and 'mobileSha256' on iOS; both use this same field.) identifyRequest.setUserIdentity(mparticle.UserIdentityType.Other2, 'SHA-256 hashed mobile number'); // only if raw mobile unavailable identifyRequest.setUserIdentity(mparticle.UserIdentityType.MobileNumber, '+13125551515'); identifyRequest.setCustomerId('cust_10482'); // 2. Optionally set user attributes once the request succeeds. var identityCallback = { onSuccess: function(userID) { var user = new mparticle.User(userID); user.setUserAttribute('firstname', 'Jane'); user.setUserAttribute('lastname', 'Smith'); }, onError: function(errorResponse) { console.error('Identify error: ' + JSON.stringify(errorResponse)); } }; // 3. Call one of the following methods that best matches the user's action: var identity = new mparticle.Identity(); identity.login(identifyRequest, identityCallback.onSuccess); // Call when the user logs in or creates an account identity.identify(identifyRequest, identityCallback.onSuccess); // Call when you obtain the user's email mid-session, but not during a login 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 ```javascript var identity = new mparticle.Identity(); identity.getCurrentUser(function(userID) { var currentUser = new mparticle.User(userID); // 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.setUserAttributeArray('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 | 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.logScreenEvent` with the name of the screen (e.g. `'homepage'`, `'product_detail_page'`). Include any additional custom attributes in the info object. #### Log a screen view ```javascript mparticle.logScreenEvent('homepage', { '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 `mparticle.eCommerce.logProductAction`, using a product action type 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 type | | -------------------------- | ------------------------------------------------ | | Product detail page viewed | `mparticle.ProductActionType.ViewDetail` | | Product clicked | `mparticle.ProductActionType.Click` | | Item added to cart | `mparticle.ProductActionType.AddToCart` | | Item removed from cart | `mparticle.ProductActionType.RemoveFromCart` | | Item added to wishlist | `mparticle.ProductActionType.AddToWishlist` | | Item removed from wishlist | `mparticle.ProductActionType.RemoveFromWishlist` | | Checkout flow initiated | `mparticle.ProductActionType.Checkout` | | Checkout option selected | `mparticle.ProductActionType.CheckoutOption` | | Order confirmed | `mparticle.ProductActionType.Purchase` | | Order refunded | `mparticle.ProductActionType.Refund` | Tracking a commerce event takes three phases: #### 1. Define the product Build a product with `mparticle.eCommerce.createProduct`. Set additional fields like `Category`, `Brand`, and `Position` directly on the returned object. ##### Define a product ```javascript var product = mparticle.eCommerce.createProduct( 'Double Room - Econ Rate', // Name 'econ-1', // SKU 100.00, // Price 4 // Quantity ); product.Category = 'room'; product.Brand = 'lodge-o-rama'; product.Variant = 'standard'; ``` #### 2. Summarize the transaction Build a `transactionAttributes` object for `Purchase`, `Checkout`, and `CheckoutOption` events. Use PascalCase keys (`Id`, `Revenue`, `Tax`, `Shipping`, `Coupon`). Order-level coupons belong here, not on individual products. ##### Summarize the transaction ```javascript var transactionAttributes = { Id: 'ORDER-12345', Revenue: 149.99, Tax: 12.50, Shipping: 5.99, Coupon: 'SUMMER20' }; ``` #### 3. Log the commerce event Call `mparticle.eCommerce.logProductAction`, passing the product action type, your product(s), event-level attributes, optional custom flags, and (when applicable) the `transactionAttributes`. Pick the customer action you want to log: ##### Commerce event: PLP impression Log a product listing page (or category page) view as a product impression. Pass every visible product in a single impression call, and set the list name to the list / category the customer is browsing. | Field | Type | Required | Description | | ---------- | ------ | -------- | ------------------------------------------------------------------------- | | `Name` | string | yes | List or category name (e.g. `"Mens Running Shoes"`). Becomes `list_name`. | | `Products` | array | yes | Product objects from `createProduct`. Set `Position` to each item's rank. | | `currency` | string | yes | ISO 4217 currency code (passed as event-level custom attribute). | ###### Example PLP impression event ```javascript var product = mparticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); product.Category = 'Shoes'; product.Brand = 'BrandX'; product.Position = 1; var impression = { Name: 'Mens Running Shoes', Products: [product] }; mparticle.eCommerce.logImpression(impression, { '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. | | `list_name` | string | no | Set if the user arrived from a PLP. | ###### Example ViewDetail event ```javascript var product = mparticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); mparticle.eCommerce.logProductAction( mparticle.ProductActionType.ViewDetail, [product], { 'currency': 'USD', 'list_name': 'PLP-Running' } ); ``` ##### Commerce event: AddToCart Log when a customer adds an item to the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ------------------------------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units added. | | `itemprice` | decimal | yes | Per-unit price at time of add. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level coupon, if applied at add-time. | ###### Example AddToCart event ```javascript var product = mparticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); mparticle.eCommerce.logProductAction( mparticle.ProductActionType.AddToCart, [product], { 'currency': 'USD' } ); ``` ##### Commerce event: RemoveFromCart Log when a customer removes an item from the cart. | Field | Type | Required | Description | | ------------ | ------- | -------- | ----------------------- | | `productsku` | string | yes | Product SKU. | | `quantity` | integer | yes | Units removed. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example RemoveFromCart event ```javascript var product = mparticle.eCommerce.createProduct( 'Trail Runner v3', // Name 'SKU-001', // SKU 129.95, // Price 1 // Quantity ); mparticle.eCommerce.logProductAction( mparticle.ProductActionType.RemoveFromCart, [product], { 'currency': 'USD' } ); ``` ##### Commerce event: Cart page view Log when the customer arrives on the cart page. Since cart page views do not have a native product action, use `mparticle.logEvent` with the event name `'view_cart'` and `mparticle.EventType.Other`. Pass the full cart contents as a custom attribute. | Field | Type | Required | Description | | --------------- | --------- | -------- | -------------------------------------------------------------------------- | | `event_name` | string | yes | Always `'view_cart'`. | | `event_type` | EventType | yes | Use `mparticle.EventType.Other`. | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `cartitemcount` | integer | yes | Number of cart lines. | | `totalprice` | decimal | yes | Cart total. | | `currency` | string | yes | ISO 4217 currency code. | | `couponcode` | string | no | Order-level promo, if applied. | Each entry in the `cartitems` array has the following shape: | Field | Type | Description | | ----------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `cartitemid` | string | Stable partner-side cart-line identifier. Usually equals `productsku` when there is one line per SKU; use a unique value if you allow multiple lines for the same SKU (e.g. gift-wrap variants). | | `productsku` | string | Product SKU / stock identifier. | | `productname` | string | Product display name. | | `productcategory` | string | Product category / taxonomy leaf. | | `productbrand` | string | Product brand. | | `productvariant` | string | Variant identifier (size, color, etc.). | | `itemprice` | decimal | Per-unit price at event time. | | `unitprice` | decimal | Per-unit list price pre-discount. Omit if equal to `itemprice`. | | `quantity` | integer | Units in this line. | | `currency` | string | ISO 4217 code. Omit if matches the top-level currency. | | `couponcode` | string | Coupon applied to this line (if any). Order-level promos belong in the top-level `couponcode` placement attribute. | | `productposition` | integer | 1-indexed rank of the product within a list or search results. | ###### Example cart page view event ```javascript var cartItems = JSON.stringify([ { 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 } ]); mparticle.logEvent( 'view_cart', mparticle.EventType.Other, { 'cartitemcount': '3', 'totalprice': '169.85', 'currency': 'USD', 'couponcode': 'SUMMER20', 'cartitems': cartItems } ); ``` ##### Commerce event: Checkout Log when the customer enters the checkout flow. Send the full set of cart products plus a `transactionAttributes` summary. | Field | Type | Required | Description | | --------------- | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `totalprice` | decimal | yes | Cart total before tax/shipping. | | `cartitemcount` | integer | yes | Number of cart lines. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level promo, if applied. | ###### Example Checkout event ```javascript var product1 = mparticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1); var product2 = mparticle.eCommerce.createProduct('Cushion Insole', 'SKU-002', 19.95, 2); var transactionAttributes = { Coupon: 'SUMMER20', Revenue: 169.85 }; mparticle.eCommerce.logProductAction( mparticle.ProductActionType.Checkout, [product1, product2], { 'currency': 'USD', 'cartitemcount': '3', 'totalprice': '169.85' }, null, transactionAttributes ); ``` ##### Commerce event: CheckoutOption (shipping) Log when the customer completes the shipping step. Use the `mparticle.ProductActionType.CheckoutOption` action and pass `checkoutOption: 'shipping'` along with the shipping selections. | Field | Type | Required | Description | | ---------------- | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `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. | ###### Example CheckoutOption (shipping) event ```javascript var product1 = mparticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1); var product2 = mparticle.eCommerce.createProduct('Cushion Insole', 'SKU-002', 19.95, 2); mparticle.eCommerce.logProductAction( mparticle.ProductActionType.CheckoutOption, [product1, product2], { 'checkoutOption': 'shipping', 'shippingmethod': 'express', 'zipcode': '94103', 'country': 'US', 'totalprice': '169.85', 'currency': 'USD' } ); ``` ##### Commerce event: CheckoutOption (payment) Log when the customer completes the payment step. Use the `mparticle.ProductActionType.CheckoutOption` action and pass `checkoutOption: 'payment'` along with the payment method selected. | Field | Type | Required | Description | | ------------------------ | ------- | -------- | -------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents, JSON-stringified before passing as a custom attribute. | | `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. | ###### Example CheckoutOption (payment) event ```javascript var product1 = mparticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1); var product2 = mparticle.eCommerce.createProduct('Cushion Insole', 'SKU-002', 19.95, 2); mparticle.eCommerce.logProductAction( mparticle.ProductActionType.CheckoutOption, [product1, product2], { 'checkoutOption': 'payment', 'paymenttype': 'credit_card', 'payment_method': 'visa', 'paymentServiceProvider': 'stripe', 'ccbin': '424242', 'totalprice': '169.85', 'currency': 'USD' } ); ``` ##### Commerce event: Purchase Log when an order is confirmed. Send the full set of cart products plus a `transactionAttributes` summary including order ID, revenue, tax, shipping, and any order-level coupon. | Field | Type | Required | Description | | --------------- | ------- | -------- | ------------------------------------------------------------------------------------------- | | `cartitems` | array | yes | Full cart contents at time of order, JSON-stringified before passing as a custom attribute. | | `transactionId` | string | yes | Order / transaction identifier. | | `totalprice` | decimal | yes | Order total (Revenue). | | `tax` | decimal | yes | Total tax on the order. | | `shipping` | decimal | yes | Shipping cost. | | `currency` | string | yes | ISO 4217 currency code. | | `couponCode` | string | no | Order-level promo, if applied. | | `cartitemcount` | integer | no | Number of cart lines. | ###### Example Purchase event ```javascript var product1 = mparticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1); var product2 = mparticle.eCommerce.createProduct('Cushion Insole', 'SKU-002', 19.95, 2); var transactionAttributes = { Id: 'ORDER-10482', Revenue: 169.85, Tax: 14.20, Shipping: 5.99, Coupon: 'SUMMER20' }; mparticle.eCommerce.logProductAction( mparticle.ProductActionType.Purchase, [product1, product2], { 'currency': 'USD', 'cartitemcount': '3' }, null, transactionAttributes ); ``` ##### Commerce event: Refund Log when an order (or a line within it) is refunded. Send only the products being refunded, plus a `transactionAttributes` object referencing the original order. | Field | Type | Required | Description | | --------------- | ------- | -------- | ----------------------------------------- | | `productsku` | string | yes | SKU of the refunded line(s). | | `quantity` | integer | yes | Units refunded. | | `transactionId` | string | yes | Original order ID being refunded against. | | `totalprice` | decimal | yes | Refunded amount. | | `currency` | string | yes | ISO 4217 currency code. | ###### Example Refund event ```javascript var refundedProduct = mparticle.eCommerce.createProduct('Trail Runner v3', 'SKU-001', 129.95, 1); var transactionAttributes = { Id: 'ORDER-10482', Revenue: 129.95 }; mparticle.eCommerce.logProductAction( mparticle.ProductActionType.Refund, [refundedProduct], { 'currency': 'USD' }, null, transactionAttributes ); ``` ### Event category: Custom events Track custom events using `mparticle.logEvent`, passing an event name, event type, and optional custom attributes. #### Show custom event types | Type | Use for | | ------------------------------------ | ------------------------------------------------------------- | | `mparticle.EventType.Navigation` | User navigation flows and screen transitions within your app. | | `mparticle.EventType.Location` | Location-based interactions and movements. | | `mparticle.EventType.Search` | Search queries and search-related actions. | | `mparticle.EventType.Transaction` | Financial transactions and purchase-related activity. | | `mparticle.EventType.UserContent` | User-generated content like reviews, comments, or posts. | | `mparticle.EventType.UserPreference` | User settings, preferences, and customization choices. | | `mparticle.EventType.Social` | Social media interactions and sharing activities. | | `mparticle.EventType.Other` | Anything that doesn't fit the categories above. | #### Log a custom event ```javascript mparticle.logEvent( 'video_watched', mparticle.EventType.Navigation, { 'category': 'Destination Intro', 'title': 'Paris' } ); ``` ## 6. Show a Placement Call `mparticle.Rokt.selectPlacements` on every payment and confirmation screen you want Rokt to render content on. Include one of the following page identifiers to specify the screen type and whether it's for testing or production: - `stg.rokt.conf`: A confirmation screen in a staging (or testing) environment. - `prod.rokt.conf`: A confirmation screen in a production environment. - `stg.rokt.payments`: A payments screen in a staging (or testing) environment. - `prod.rokt.payments`: A payments screen in a production environment. ### Placement attributes Pass these attributes in the `attributes` map of `selectPlacements`. Always supply the most recent value — attributes passed here override any earlier `setUserAttribute` calls. ### Show all placement attributes | Field | Type | Description | | ------------------------ | ------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `email` | string | Customer email (unhashed). Used for identity resolution. | | `firstname` | string | Customer first name. Used for personalization. | | `lastname` | string | Customer last name. Used for personalization. | | `mobile` | string | Customer mobile number in E.164 format. Used for identity resolution. | | `confirmationref` | string | Order / confirmation reference number. Used for relevance and deduplication. | | `currency` | string | Transaction currency (ISO 4217, e.g. `USD`, `GBP`, `AUD`). Used for relevance. | | `country` | string | ISO 3166-1 alpha-2 country code. Used for eligibility and relevance. | | `language` | string | Customer's preferred language (ISO 639-1). Used for relevance. | | `totalprice` | decimal | Total cart value including tax and shipping. Used for relevance. | | `amount` | 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` | string | JSON-serialized array of cart items. Used for relevance. | | `adsexperience` | string | Pass `"shoppable"` when deliberately targeting a Shoppable Ads experience. | ### Placement position: Overlay Overlay placements render on top of your confirmation screen in a Rokt-managed container, requiring no changes to your app's existing layout. To insert an overlay placement, call `selectPlacements` once the confirmation screen loads: #### Overlay placement ```javascript var 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', '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' }; var config = { colorMode: mparticle.Rokt.ColorMode.LIGHT }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config ); ``` ### 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. To insert an embedded placement, pass the embedded view identifier in your `selectPlacements` call: #### Embedded placement ```javascript var attributes = { 'email': 'j.smith@example.com', 'firstname': 'Jenny', 'lastname': 'Smith', 'billingzipcode': '90210', 'confirmationref': '54321' }; var config = { colorMode: mparticle.Rokt.ColorMode.LIGHT }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config, 'RoktEmbedded1' // The embedded view identifier ); ``` > **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 are supported on **iOS only** in the Cordova SDK+. The Android path of this plugin does not support interstitial placements. Do not invoke interstitial placement logic on Android. > **Note: Apple Pay setup required** > > To enable Shoppable Ads with Apple Pay on iOS, add the payment extension plugin with `cordova plugin add @mparticle/cordova-rokt-payment-extension`, then register `RoktPaymentExtension` in `AppDelegate.swift` after `MParticle.sharedInstance().start(with:)` and before `selectPlacements`. Configure `stripePublishableKey` in your **mParticle Rokt kit** settings (mParticle dashboard). The kit forwards it to Rokt, so in code you pass only the Apple Pay merchant ID, plus a `urlScheme` if you offer Afterpay / Clearpay. Follow the [Apple Pay iOS setup guide](https://docs.rokt.com/developer-reference/product-deep-dives/shoppable-ads/payment-methods/apple-pay#ios-setup) to create a merchant ID and configure your Xcode project. > > ```swift title="Register the payment extension (iOS)" > import RoktPaymentExtension > > // In application(_:didFinishLaunchingWithOptions:), after MParticle.sharedInstance().start(with:) > if let paymentExtension = RoktPaymentExtension( > applePayMerchantId: "merchant.com.yourapp.rokt", // omit if not offering Apple Pay > urlScheme: "myapp" // omit if not offering Afterpay / Clearpay > ) { > MParticle.sharedInstance().rokt.registerPaymentExtension(paymentExtension) > } > ``` > > If you pass `urlScheme`, register it under `CFBundleURLSchemes` in your app's `Info.plist`, or the initializer returns `nil`. On iOS, interstitial placements are triggered through the same `selectPlacements` call used for overlay and embedded placements. The iOS native layer handles the interstitial UI; your JavaScript code triggers it and listens for the resulting events. To implement an interstitial placement on iOS, call `selectPlacements` from your confirmation screen logic. Pass the same comprehensive attributes you use for overlay placements: #### Interstitial placement (iOS only) ```javascript // Guard: only invoke on iOS if (cordova.platformId === 'ios') { var 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', '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 (required for Shoppable Ads fulfillment) 'shippingaddress1': '175 Varick St', 'shippingcity': 'New York', 'shippingstate': 'NY', 'shippingzipcode': '10014', 'shippingcountry': 'US' }; var config = { colorMode: mparticle.Rokt.ColorMode.LIGHT }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config ); } ``` > **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.Rokt.close()` | Auto-close overlay placements. | ### Additional configuration Pass optional parameters such as a `config` object to customize the placement UI (e.g. dark/light mode, caching). ### selectPlacements with config ```javascript var config = { colorMode: mparticle.Rokt.ColorMode.LIGHT, cacheConfig: { cacheDurationInSeconds: 1200, cacheAttributes: { 'email': 'j.smith@example.com', 'orderNumber': '123' } } }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config ); ``` > **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 you can subscribe to. Use the `onEvent` callback in your `selectPlacements` call to respond to load state, engagement, and failures. ### Subscribe to placement events ```javascript mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config, null, function(event) { // Handle placement events if (event && event.eventType) { console.log('Rokt event: ' + event.eventType); } } ); ``` #### 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: Object | | 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 (iOS only). | placementId: String, cartItemId: String, catalogItemId: String, currency: String, description: String, linkedProductId: String, totalPrice: Number, quantity: Number, unitPrice: Number | ## 7. Appendix ### Appendix A: App configuration Applications can pass configuration settings through the `config` object 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 | ```javascript title="selectPlacements with ColorMode" var config = { colorMode: mparticle.Rokt.ColorMode.LIGHT }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config ); ``` #### 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. | ```javascript title="Cache for 1200 seconds" // Cache the experience for 1200 seconds, using email and orderNumber as the cache key. var config = { cacheConfig: { cacheDurationInSeconds: 1200, cacheAttributes: { 'email': 'j.smith@example.com', 'orderNumber': '123' } } }; mparticle.Rokt.selectPlacements( 'RoktExperience', attributes, config ); ``` #### EdgeToEdgeDisplay (Android only) Controls whether the Rokt overlay respects Android's edge-to-edge display mode. This configuration applies to the **Android** path only; iOS does not use this flag. | Value | Description | | ---------------- | ------------------------------------------------------ | | `true` (default) | Application supports edge-to-edge display mode | | `false` | Application does not support edge-to-edge display mode | The Cordova SDK+ does not currently expose `EdgeToEdgeDisplay` as a JavaScript config option. If your Android app opts out of edge-to-edge display and the overlay renders incorrectly, configure it in your native Android code: ```kotlin title="EdgeToEdgeDisplay (native Android — RoktConfig)" import com.mparticle.rokt.RoktConfig val roktConfig = RoktConfig.Builder() .edgeToEdgeDisplay(false) // set to false if your app does not use edge-to-edge .build() ``` Pass `roktConfig` to `selectPlacements` in your native Android layer, or raise this with your Rokt account manager if you need Cordova-level support. ### Appendix B: Native UI components The Cordova SDK+ uses native Rokt UI components rendered by the underlying iOS and Android SDK+s. There is no Cordova-specific declarative UI component (equivalent to Jetpack Compose or SwiftUI). The placement UI is managed entirely by the native layer and surfaced through the `selectPlacements` API. ### 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 Cordova SDK+'s `mparticle.Identity.identify()` callback pattern surfaces errors through the `onError` handler. Inspect the `errorResponse` object to determine the appropriate action: ```javascript title="IDSync error handling" var identifyTask = { onSuccess: function(userID) { // IDSync succeeded — proceed with the identified user console.log('Identify success, userID: ' + userID); }, onError: function(errorResponse) { if (errorResponse && errorResponse.httpCode !== undefined) { if (errorResponse.httpCode === -1) { // Device is likely offline (maps to UNKNOWN_ERROR on Android, // MPIdentityErrorResponseCodeClientNoConnection on iOS) — retry the request } else if (errorResponse.httpCode === 429) { // Throttled — retry with exponential backoff } else if (errorResponse.httpCode >= 500) { // Server-side error — contact your account representative } else { // Inspect errorResponse for implementation issues (e.g. 400 invalid request, 401 auth error) console.error('Identity error: ' + JSON.stringify(errorResponse)); } } } }; var identity = new mparticle.Identity(); identity.identify(identifyRequest, identifyTask.onSuccess); ``` #### iOS error codes On iOS, the native SDK+ maps failures to `MPIdentityErrorResponseCode` values. The Cordova bridge surfaces these as the `httpCode` in the JavaScript error response. Key codes to handle: | Code | Meaning | Action | | ----------------------------------------------- | ---------------------------------------- | ----------------------------------------------------------- | | `MPIdentityErrorResponseCodeClientNoConnection` | Device offline or no network | Retry the request | | `MPIdentityErrorResponseCodeClientSideTimeout` | TCP connection timed out | Retry the request | | `MPIdentityErrorResponseCodeRequestInProgress` | Another IDSync request already in flight | Inspect implementation; retry if infrequent | | `MPIdentityErrorResponseCodeRetry` | SDK+-level retry signal | Retry the request | | `429` (HTTP) | Rate-limited by Rokt servers | Retry with exponential backoff | | `400` (HTTP) | Invalid request body | Inspect `errorResponse` — typically an implementation issue | | `401` (HTTP) | Authentication error | Verify your API key | #### Android error codes On Android, the native SDK+ returns `IdentityApi.UNKNOWN_ERROR` for client-side failures (device offline, client-side timeout, invalid requests). A `429` response maps to `IdentityApi.THROTTLE_ERROR`. Both signal the appropriate retry strategy: - **`UNKNOWN_ERROR`** (device offline or client-side issue): retry the request once connectivity is restored. - **`THROTTLE_ERROR` / 429**: retry with exponential backoff. This can indicate a user "hotkey" or a higher-than-expected IDSync volume — inspect your implementation if it occurs frequently. ### Appendix D: Sharing a session between web and native When a user journey spans web and native, keep a single Rokt session in both directions: - **Web to native** — pass `sessionId` (and `sessionToken` when you have it) into the app, then call `setSessionId`. - **Native to web** — the customer starts in the app then continues in a WebView. Read the session from iOS `getSession` or Android `getSession` and seed Web `launcherOptions` before init. Cordova `setSessionId` is id-only. #### 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; const sessionToken = ''; ``` > **Note** > > The **session ID** is a unique GUID assigned to the current user journey. Use it to correlate activity across web and native. When sharing a session, also pass the matching **session token** when the SDK+ has one (`getSession` on Android and iOS, or the token from the Web selection). Id-only handoff still continues the same visit; Transactions mints a token for that session id. #### Passing to native app via deep link Pass the session ID and matching session token to your native app using a deep link: ```javascript title="Deep-link to native app" const deepLink = `myapp://confirmation` + `?sessionId=${encodeURIComponent(sessionId)}` + `&sessionToken=${encodeURIComponent(sessionToken)}`; 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`. ```javascript title="Set sessionId in Cordova" // Extract the sessionId from your deep link handler and set it before selectPlacements var sessionId = getSessionIdFromDeepLink(); // Your deep link parsing logic if (sessionId) { mparticle.Rokt.setSessionId(sessionId); } // Then proceed with selectPlacements mparticle.Rokt.selectPlacements('RoktExperience', attributes, config); ``` #### Passing from native to web After a native placement, read the session from the iOS or Android SDK+ and inject it into the WebView before Web SDK+ init. ```swift title="Read native session for WebView (iOS)" let session = MParticle.sharedInstance().rokt.getSession() let sessionId = session?.sessionId let sessionToken = session?.sessionToken ``` ```kotlin title="Read native session for WebView (Android)" val session = MParticle.getInstance()?.Rokt()?.getSession() val sessionId = session?.sessionId val sessionToken = session?.sessionToken ``` Before initializing the Web SDK+ in the WebView, copy the native session into `launcherOptions`. Pass `sessionToken` when the native SDK+ returned one. `sessionId` alone is enough for visit continuity — Transactions mints a token for that id on the first Web offers request. ```javascript title="Seed Web SDK+ from native session" window.mParticle = window.mParticle || { config: {} }; window.mParticle.config.launcherOptions = { sessionId: sessionId, }; if (sessionToken) { window.mParticle.config.launcherOptions.sessionToken = sessionToken; } ``` Set `launcherOptions` before `mParticle.init` so the launcher receives the session on first load. #### Notes - Call `setSessionId` before `selectPlacements` so native uses the web session. - Set Web `launcherOptions` before `mParticle.init` so the WebView uses the native session. - Empty strings are ignored and will not update the session. - Always URL-encode the session ID and session token when passing as a query parameter. ## 8. Test Your Integration To confirm the SDK+ initializes and events log correctly: ### 1. Enable verbose SDK+ logging Enable verbose SDK+ logging before initialization so you can see what's being sent. #### Target: iOS ```swift title="Enable verbose SDK+ logging (iOS)" MParticle.sharedInstance().logLevel = .verbose ``` #### Target: Android ```java title="Enable verbose SDK+ logging (Android)" MParticleOptions options = MParticleOptions.builder(this) .credentials("your-key", "your-secret") .environment(MParticle.Environment.Development) .logLevel(MParticle.LogLevel.VERBOSE) .build(); ``` ### 2. Build and run your app Build and run your app against a development key with the environment set to Development on both platforms. ### 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 native device logs (Xcode console for iOS, Android Logcat for Android) for Rokt SDK+ errors. Common issues: #### Initialization errors - Confirm the key and secret match the values from your Rokt account manager on both iOS (`your-key` / `your-secret` in `MParticleOptions(key:secret:)`) and Android (`.credentials("your-key", "your-secret")`). - Confirm `MParticle.start` (Android) and `MParticle.sharedInstance().start(with:)` (iOS) run before any `selectPlacements` or `logEvent` call. - On iOS, confirm the verbose log shows `Included kits: {Rokt}`. If it doesn't, install `@mparticle/cordova-rokt-kit` at the same major version as `@mparticle/cordova-sdk`. #### Identity errors If the identify callback fires with an error, see [Error Handling](https://docs.rokt.com/integration-guides/ecommerce/sdk/cordova/#error-handling) 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. - Check that the attributes map contains at least `email`, `firstname`, `lastname`, `billingzipcode`, and `confirmationref`.