# 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

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"),
]
```

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

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

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

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

  // Identify the current user:
  // 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 <mParticle_Apple_SDK.h>

// 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 `transactionAttributes.Coupon`.                                                                                               |
| `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` 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/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 <mParticle_Apple_SDK.h>

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 <mParticle_Apple_SDK.h>
@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 <mParticle_Apple_SDK.h>
@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.

### 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 <mParticle_Apple_SDK.h>
@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.&#xA;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<UIOpenURLContext>) {
    for urlContext in URLContexts {
        MParticle.sharedInstance().handleURLContext(urlContext)
    }
}
```

### Objective-C

```objectivec
- (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)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: Passing session ID from web to native

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

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

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

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

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

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

#### Passing to native app via deep link

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

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

#### Setting the session ID

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

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

```objectivec title="Handle deep link and set sessionId"
- (void)handleDeepLink:(NSURL *)url {
    NSURLComponents *components =
        [NSURLComponents componentsWithURL:url resolvingAgainstBaseURL:NO];
    for (NSURLQueryItem *item in components.queryItems) {
        if ([item.name isEqualToString:@"sessionId"]) {
            [[[MParticle sharedInstance] rokt] setSessionIdWithSessionId:item.value];
            break;
        }
    }

    // Proceed with your confirmation flow
}
```

#### Notes

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

### 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<UIOpenURLContext>) {
  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`.

#### 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;
```
