Use this guide when replacing an mParticle Braze kit in a browser, iOS, Android, or React Native application. Configure the Hightouch Braze plugin to preserve the data your Braze campaigns and segments depend on.
Plan the replacement
The plugin forwards Hightouch calls to the Braze SDK in your application. Your Hightouch SDK also sends events to Hightouch for collection. Braze message presentation, Content Cards, and push configuration remain part of your application setup.
The plugin uses Hightouch event names and properties by default. mParticle's kits have different mappings, including differences between browser, iOS, and Android. Record your current kit version, settings, and sample Braze output before replacing it.
Inventory these settings and behaviors:
- The identity selected by
userIdentificationType, including its formatting and casing. - Screen or page forwarding, page event names, product identifiers, and bundled commerce events.
- Attribute types, event-to-attribute rules, subscription groups, and consent handling.
- In-app message display, Content Cards, push registration, and custom Braze callbacks.
Use separate test profiles or a test Braze app during comparison. Sending the same event through both kits into production can trigger duplicate purchases or messages.
Install the replacement plugin
Follow the setup for browser, iOS, Android, or React Native. Reuse an existing Braze instance where the SDK supports it, or let the plugin initialize Braze. Avoid initializing Braze twice.
Replace mParticle tracking calls with Hightouch calls using the event specification. Use identify for user IDs and traits, track for custom events and purchases, and page or screen for views.
Preserve identity and user attributes
Pass the same identity value that your mParticle kit used as Hightouch's userId. For example, if the kit used Customer ID, continue sending that value. Switching to an email address or mParticle ID can create a different Braze profile.
Rename reserved mParticle attributes at your call site. The following examples use JavaScript, but the trait names apply to all four SDKs:
htevents.identify("customer-123", {
firstName: "Jane", // Previously $FirstName
lastName: "Doe", // Previously $LastName
email: "jane@example.com",
phone: "+14155550123", // Previously $Mobile
birthday: "1990-01-15",
address: { city: "San Francisco", country: "US" },
plan: "premium",
});
The mobile kits map $Zip to a custom Braze attribute named Zip; send a Zip trait to preserve it. If your kit derived a birth date from $Age, preserve that date with birthday rather than sending age as a custom attribute. Check gender values and date formats against a test profile rather than assuming every kit accepted the same input.
Preserve custom attribute types. If your previous output used strings for specific fields, convert those fields explicitly in your instrumentation or purchase transformer. Keep other values in their original types.
Calling Hightouch's reset() clears Hightouch identity and the plugin's attribute cache. It does not log the current user out of Braze. A later identify with another user ID changes the Braze user.
Match kit settings and event names
Apply the settings that match your existing kit. The purchase recipes below show where to configure the platform-specific options.
| Existing kit behavior | Hightouch configuration |
|---|---|
| Forward views | Use pageTracking: "name" on browser or forwardScreenViews: true on mobile. Both are off by default. |
| Browser page names | Set pageTracking: "path" to use the URL path. Use "name" to prefer the page name, falling back to the path. |
| Browser product names | Set purchaseGrouping: { mode: "perProduct", identifier: "name" }. Keep SKU mode if forwardSkuAsProductName was enabled. |
| Mobile product names | Select product names in your platform's purchase grouping options, as described below. |
| Bundle mobile purchases | Select per-order purchases and apply the bundled purchase recipe. |
| String values | Convert only the fields that previously used strings; preserve the types of other fields. |
| Browser message display | Set automaticallyShowInAppMessages: true when the plugin initializes Braze. |
To preserve mobile product-name identifiers, configure the appropriate option:
- iOS: Set
purchaseGrouping: .perProduct(identifier: .name). - Android: Set
.purchaseGrouping(PurchaseGrouping.perProduct(ProductIdentifier.NAME)), importing both types fromcom.hightouch.analytics.braze. - React Native: Set
purchaseGrouping: { mode: "perProduct", identifier: "name" }.
Custom track events retain the name you send. Other commerce actions, such as Product Added, become custom events. The plugin does not automatically reproduce mParticle's expanded commerce event names (for example, eCommerce - add_to_cart - Item) or split one event into several events. Preserve any names, properties, and extra calls your Braze campaigns require in your instrumentation.
Preserve purchase output
By default, Order Completed and Completed Order are purchases. The plugin logs one purchase per product, using sku, then product_id, then name as the product ID. Other event names remain custom events unless you configure purchase detection.
Supply a finite price for each product, including 0 for free items. Quantity defaults to 1 when omitted and must be an integer from 1 to 100 when supplied. Send currency as a three-letter code, or configure defaultCurrency as a fallback for missing or invalid currency. The plugin trims whitespace and uppercases currency codes, with no automatic USD default. Purchases with invalid inputs are rejected.
Use purchaseDetection: ["Your Purchase Event"] on browser and React Native, purchaseDetection: .eventNames(...) on iOS, or .purchaseDetection(PurchaseDetection.eventNames(...)) on Android for another purchase event name.
The examples below map this Hightouch Order Completed payload. They cover the fields shown, including one order-level custom attribute (channel) and one product-level custom attribute (color). Extend the mappings for your own fields, coupons, promotions, or impressions before cutover.
{
"order_id": "order-123",
"revenue": 26,
"tax": 3,
"shipping": 3,
"currency": "USD",
"channel": "app",
"products": [
{
"sku": "sku-1",
"name": "T-shirt",
"category": "Clothing",
"price": 13,
"quantity": 2,
"color": "blue"
}
]
}
Purchase hooks receive the mapped purchase and its original order and product. They change the Braze purchase without changing the event collected by Hightouch. A hook can return no purchase to skip it. The mobile examples below use numeric price and, when supplied, numeric quantity.
Browser purchases
The mParticle browser kit sends product custom attributes plus Sku. It does not automatically include the order's Transaction Id or totals. Its default product ID is the product name.
Add these options to the destinations.Braze setup on the browser SDK page. This example preserves color as a custom product attribute:
const purchaseOptions = {
purchaseGrouping: { mode: "perProduct", identifier: "name" },
transformPurchase: (purchase, { product }) => {
if (!product) return null;
return {
...purchase,
properties: { color: product.color, Sku: product.sku },
};
},
};
// Include ...purchaseOptions inside destinations.Braze.
If you previously added transaction fields as custom product attributes, include those explicitly. Keep purchases per product to match the browser kit; mobile's bundled commerce recipe does not describe the browser kit's behavior.
iOS purchases
The iOS kit includes renamed transaction fields, flattened custom attributes, and string-valued product fields, including Total Product Amount. This helper preserves that output for the sample payload. It also supports the bundled option described below:
import Foundation
import HightouchBraze
func legacyProperties(_ order: [String: Any], _ product: [String: Any]?,
bundled: Bool) -> [String: Any] {
let orderNames = ["order_id": "Transaction Id", "revenue": "Total Amount",
"tax": "Tax Amount", "shipping": "Shipping Amount"]
var result = [String: Any]()
for (key, name) in orderNames { result[name] = order[key] }
var attributes = [String: Any]()
attributes["channel"] = order["channel"]
if bundled {
result["Attributes"] = attributes
} else {
for (key, value) in attributes { result[key] = String(describing: value) }
}
func mapProduct(_ product: [String: Any]) -> [String: Any] {
var item = product
if item["quantity"] == nil { item["quantity"] = 1 }
var mapped = [String: Any]()
var names = ["name": "Name", "category": "Category"]
if bundled {
names.merge(["sku": "Id", "price": "Item Price", "quantity": "Quantity"]) { _, new in new }
}
for (key, name) in names {
if let value = item[key] { mapped[name] = String(describing: value) }
}
if let price = item["price"] as? NSNumber, let quantity = item["quantity"] as? NSNumber {
mapped["Total Product Amount"] = NSNumber(value: price.doubleValue * quantity.doubleValue).stringValue
}
if bundled {
var custom = [String: Any]()
custom["color"] = item["color"]
mapped["Attributes"] = custom
} else if let color = item["color"] {
mapped["color"] = String(describing: color)
}
return mapped
}
if bundled {
result["products"] = (order["products"] as? [[String: Any]] ?? []).map(mapProduct)
} else if let product = product {
result.merge(mapProduct(product)) { _, value in value }
}
return result
}
let options = BrazeDestination.Options(transformPurchase: { purchase, context in
guard context.product != nil else { return nil }
var result = purchase
result.properties = legacyProperties(context.order, context.product, bundled: false)
return result
})
// Pass options to BrazeDestination(apiKey:endpoint:options:).
Android purchases
The Android kit includes renamed order and product fields and flattened custom attributes. Unlike iOS, its per-product purchase properties do not include Total Product Amount. Its bundled product fields use numbers for price, quantity, and total.
Use this helper and transformer with the Android setup:
import com.hightouch.analytics.braze.BrazeIntegration
fun legacyProperties(order: Map<String, Any>, product: Map<String, Any>?,
bundled: Boolean): Map<String, Any> {
val result = mutableMapOf<String, Any>()
val orderNames = mapOf("order_id" to "Transaction Id", "revenue" to "Total Amount",
"tax" to "Tax Amount", "shipping" to "Shipping Amount")
for ((key, name) in orderNames) order[key]?.let { result[name] = it }
val attributes = order.filterKeys { it == "channel" }
if (bundled) result["Attributes"] = attributes else result.putAll(attributes)
fun mapProduct(product: Map<String, Any>): Map<String, Any> {
val item = if (product.containsKey("quantity")) product else product + ("quantity" to 1)
val mapped = mutableMapOf<String, Any>()
val names = mutableMapOf("name" to "Name", "category" to "Category")
if (bundled) names.putAll(mapOf("sku" to "Id", "price" to "Item Price", "quantity" to "Quantity"))
for ((key, name) in names) item[key]?.let { mapped[name] = it }
val custom = item.filterKeys { it == "color" }
if (bundled) {
mapped["Attributes"] = custom
val price = item["price"] as Number
val quantity = item["quantity"] as Number
mapped["Total Product Amount"] = price.toDouble() * quantity.toDouble()
} else {
mapped.putAll(custom)
}
return mapped
}
if (bundled) {
val products = order["products"] as List<Map<String, Any>>
result["products"] = products.map(::mapProduct)
} else if (product != null) {
result.putAll(mapProduct(product))
}
return result
}
val factory = BrazeIntegration.builder(context, "YOUR_BRAZE_SDK_API_KEY", "YOUR_BRAZE_SDK_ENDPOINT")
.purchaseTransformer { purchase, input ->
if (input.product() == null) null
else purchase.withProperties(legacyProperties(input.order(), input.product(), false))
}
.build()
// Pass factory to Analytics.Builder.use(...).
React Native purchases
Match the native kit used on each operating system. This helper preserves the iOS and Android differences described above for the sample payload:
import { BrazePlugin } from "@ht-sdks/events-sdk-react-native-plugin-braze";
import { Platform } from "react-native";
function legacyProperties(order, product, bundled, ios) {
const result = {};
const orderNames = {
order_id: "Transaction Id",
revenue: "Total Amount",
tax: "Tax Amount",
shipping: "Shipping Amount",
};
for (const [key, name] of Object.entries(orderNames)) {
if (order[key] !== undefined) result[name] = order[key];
}
const attributes =
order.channel === undefined ? {} : { channel: order.channel };
if (bundled) result.Attributes = attributes;
else
Object.assign(
result,
Object.fromEntries(
Object.entries(attributes).map(([key, value]) => [
key,
ios ? String(value) : value,
])
)
);
function mapProduct(product) {
const item = {
...product,
quantity: product.quantity === undefined ? 1 : product.quantity,
};
const mapped = {};
const names = { name: "Name", category: "Category" };
if (bundled)
Object.assign(names, {
sku: "Id",
price: "Item Price",
quantity: "Quantity",
});
for (const [key, name] of Object.entries(names)) {
if (item[key] !== undefined)
mapped[name] = ios ? String(item[key]) : item[key];
}
const custom = item.color === undefined ? {} : { color: item.color };
if (bundled) mapped.Attributes = custom;
else
Object.assign(
mapped,
Object.fromEntries(
Object.entries(custom).map(([key, value]) => [
key,
ios ? String(value) : value,
])
)
);
if (ios || bundled) {
const total = item.price * item.quantity;
mapped["Total Product Amount"] = ios ? String(total) : total;
}
return mapped;
}
if (bundled) result.products = order.products.map(mapProduct);
else Object.assign(result, mapProduct(product));
return result;
}
htClient.add({
plugin: new BrazePlugin({
transformPurchase: (purchase, { order, product }) =>
product
? {
...purchase,
properties: legacyProperties(
order,
product,
false,
Platform.OS === "ios"
),
}
: null,
}),
});
This example uses JavaScript-compatible function bodies. Add types for your tracking plan if you use TypeScript. Keep defaultSettings.integrations.Appboy configured as shown on the SDK page.
Preserve bundled mobile purchases
If bundleCommerceEventData was enabled, log one purchase per order with product ID eCommerce - purchase, order revenue as its price, and quantity 1. Place custom order and product attributes under Attributes. Use the platform helper above to preserve product field names and types.
On iOS, replace the per-product options with:
let options = BrazeDestination.Options(
purchaseGrouping: .perOrder,
transformPurchase: { purchase, context in
var result = purchase
result.productId = "eCommerce - purchase"
result.quantity = 1
result.properties = legacyProperties(context.order, nil, bundled: true)
return result
}
)
On Android, replace the per-product builder configuration with:
import com.hightouch.analytics.braze.PurchaseGrouping
val factory = BrazeIntegration.builder(context, "YOUR_BRAZE_SDK_API_KEY", "YOUR_BRAZE_SDK_ENDPOINT")
.purchaseGrouping(PurchaseGrouping.perOrder())
.purchaseTransformer { purchase, input ->
purchase.withProductId("eCommerce - purchase")
.withQuantity(1)
.withProperties(legacyProperties(input.order(), null, true))
}
.build()
On React Native, replace the per-product plugin registration with:
htClient.add({
plugin: new BrazePlugin({
purchaseGrouping: { mode: "perOrder" },
transformPurchase: (purchase, { order }) => ({
...purchase,
productId: "eCommerce - purchase",
quantity: 1,
properties: legacyProperties(
order,
undefined,
true,
Platform.OS === "ios"
),
}),
}),
});
Do not combine per-order grouping with a per-product identifier option. Supply the original order revenue explicitly. Compare tax, shipping, discounts, and quantities against your old output instead of recomputing revenue from products.
Preserve messaging and application behavior
Keep Braze's UI, Content Card subscriptions, push registration, and permission handling when removing the mParticle kit. Transfer any kit-owned setup to your app before removing the kit. Use the SDK examples linked from the setup pages for application wiring.
If your app supplies an already initialized Braze instance, keep its session lifecycle handling. The Android plugin manages sessions when it initializes Braze itself. Avoid registering another session lifecycle handler for that setup.
The plugin does not copy mParticle's event-to-user attribute rules, subscription-group mappings, consent mappings, or web push primer. Implement required rules explicitly in your application using Hightouch traits or the relevant Braze APIs. Sending a similarly named custom trait does not configure a Braze subscription group or consent API.
Validate and cut over
Compare the old and new builds with equivalent test users and the same kit settings:
- Confirm external user IDs match exactly and anonymous-to-known transitions behave as expected.
- Compare standard fields, custom attributes, and their value types. Repeat an unchanged
identifyand confirm the plugin avoids redundant attribute updates. - Compare custom event names, page/screen names, and properties. Include any expanded commerce events your campaigns use.
- Test multiple products, SKU and name identifiers, custom fields, and bundled orders. Compare purchase counts, prices, quantities, currency, property names, and types.
- Trigger an in-app message, load Content Cards, and test push behavior using your application's presentation code.
- Verify
reset()and a lateridentifywith another user ID. Do not expectreset()to make the Braze user anonymous.
After validation, enable the replacement for a limited rollout and confirm each production event reaches Braze once. Disable the old kit and overlapping direct calls or server-side forwarding for those events. Keep the underlying Braze SDK and required application setup when removing mParticle.
These recipes follow the browser kit, iOS kit, and Android kit. Compare your installed kit version and custom mappings before applying them.