# Braze Recommended Ecommerce Events


{{< announcement >}}
Braze recommended ecommerce events are currently in beta.
{{< /announcement >}}

When **Enable ecommerce recommended events** [connection setting]({{< ref "destinations/streaming-destinations/braze/setup-guide.md#event-settings" >}}) is toggled on, the Braze destination maps supported RudderStack ecommerce [`track`]({{< ref "event-spec/standard-events/track.md" >}}) events to [Braze recommended ecommerce events](https://www.braze.com/docs/user_guide/data/activation/events/recommended_events) (`ecommerce.*`).

Note that:

- The **Enable ecommerce recommended events** setting is toggled on by default for all new destinations.
- The mapping is the same in [cloud mode]({{< ref "destinations/streaming-destinations/braze/cloud-mode.md" >}}) and [device mode]({{< ref "destinations/streaming-destinations/braze/device-mode.md" >}}). 
- In [hybrid mode]({{< ref "destinations/streaming-destinations/braze/hybrid-mode.md" >}}), user-generated `track` events are sent through cloud mode, so the same mapping applies.
- To use the legacy `Order Completed` purchase mapping instead, toggle **Enable ecommerce recommended events** off in the [event settings]({{< ref "destinations/streaming-destinations/braze/setup-guide.md#event-settings" >}}).

{{< warning >}}
When the setting is toggled on, `Order Completed` is sent as one `ecommerce.order_placed` custom event per order instead of Braze **purchases**. 

Rebuild Braze purchase and revenue analytics and **Made Purchase** segments against `ecommerce.order_placed`.
{{< /warning >}}

## Event mappings

Event names are matched after trimming whitespace, without regard to case. For example, `order completed` and ` Order Completed ` both map.

| RudderStack event | Braze event | `action` |
| :--- | :--- | :--- |
| Product Viewed | `ecommerce.product_viewed` | — |
| Product Added | `ecommerce.cart_updated` | `add` |
| Product Removed | `ecommerce.cart_updated` | `remove` |
| Checkout Started | `ecommerce.checkout_started` | — |
| Order Completed | `ecommerce.order_placed` | — |
| Order Refunded | `ecommerce.order_refunded` | — |
| Order Cancelled | `ecommerce.order_cancelled` | — |

## Field mappings

Where a Braze field lists more than one RudderStack property, the integration uses the first present value.

Fields marked **Required** are required by Braze for commerce handling. Missing values do not block delivery. See [Missing required fields](#missing-required-fields).

### Product Viewed

Maps from [Product Viewed]({{< ref "event-spec/ecommerce-events-spec/ordering.md#product-viewed" >}}). Product fields are read from the top-level event properties (this event has no `products` array).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `product_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `product_id` <br /> `sku` | 
| `product_name` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `name` | 
| `variant_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `variant` <br /> `sku`<br /> `product_id` | 
| `price` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `price` | 
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` | 
| `image_url` | `image_url` | 
| `product_url` | `url` |
| `type` | `type` (array) |

### Cart Updated

Maps from [Product Added]({{< ref "event-spec/ecommerce-events-spec/ordering.md#product-added" >}}) (`action: add`) and [Product Removed]({{< ref "event-spec/ecommerce-events-spec/ordering.md#product-removed" >}}) (`action: remove`).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `cart_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `cart_id` |
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` | 
| `products[]` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `products[]` or top-level product fields folded into one item |
| `total_value` | `total` <br /> `revenue` <br /> `value` |
| `subtotal_value` | `subtotal_value` |
| `tax` | `tax` |
| `shipping` | `shipping` |

Note that:

- `Product Added` and `Product Removed` typically send product fields at the top level. 
- The integration folds those fields into a one-item `products` array — if you send `products[]` explicitly, that array is used instead.

### Checkout Started

Maps from [Checkout Started]({{< ref "event-spec/ecommerce-events-spec/ordering.md#checkout-started" >}}).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `checkout_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `checkout_id` <br /> `order_id` |
| `total_value` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `total` <br /> `revenue` <br /> `value` |
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` |
| `products[]` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `products[]` |
| `cart_id` | `cart_id` |
| `subtotal_value` | `subtotal_value` |
| `tax` | `tax` |
| `shipping` | `shipping` |

### Order Placed

Maps from [Order Completed]({{< ref "event-spec/ecommerce-events-spec/ordering.md#order-completed" >}}).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `order_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `order_id` |
| `total_value` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `total` <br /> `revenue` <br /> `value` |
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` | 
| `products[]` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `products[]` | 
| `cart_id` | `cart_id` |
| `subtotal_value` | `subtotal_value` |
| `tax` | `tax` |
| `shipping` | `shipping` |
| `total_discounts` | `discount` <br /> `total_discounts` |
| `discounts` | `discounts[]` |

{{< info >}}
Each `discounts[]` item is passed through with the shape `{ code, amount, type }`.
{{< /info >}}

### Order Refunded

Maps from [Order Refunded]({{< ref "event-spec/ecommerce-events-spec/ordering.md#order-refunded" >}}).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `order_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `order_id` |
| `total_value` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `total` <br /> `revenue` <br /> `value` |
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` |
| `products[]` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `products[]` |
| `total_discounts` | `discount` <br /> `total_discounts` | 
| `discounts` | `discounts[]` | 

### Order Cancelled

Maps from [Order Cancelled]({{< ref "event-spec/ecommerce-events-spec/ordering.md#order-cancelled" >}}).

| Braze field | RudderStack property | 
| :--- | :--- | 
| `order_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `order_id` | 
| `total_value` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `total` <br /> `revenue` <br /> `value` | 
| `currency` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `currency` | 
| `cancel_reason` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `cancel_reason` <br /> `reason` | 
| `products[]` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `products[]` | 
| `subtotal_value` | `subtotal_value` |
| `tax` | `tax` |
| `shipping` | `shipping` | 
| `total_discounts` | `discount` <br /> `total_discounts` | 
| `discounts` | `discounts[]` |

## Product properties

These fields apply to each item in `products[]` on every mapped event except `ecommerce.product_viewed`.

| Braze field | RudderStack property | 
| :--- | :--- |
| `product_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `product_id` <br /> `sku` |
| `product_name` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `name` |
| `variant_id` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `variant` <br /> `sku` <br /> `product_id` | 
| `quantity` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `quantity` | 
| `price` <br/> <span style="color: #4D4DFF;font-size:12px;">Required</span> | `price` | 
| `image_url` | `image_url` |
| `product_url` | `url` | 

{{< info >}}
Unmapped product keys are nested under `products[].metadata`.
{{< /info >}}

## Examples

The destination sends a Braze custom event (`name` and `properties`). Cloud and device mode wrap it the same way as other custom events.

### Product Viewed

Product Viewed maps to `ecommerce.product_viewed`. Because both `product_id` and `sku` are present, `product_id` wins. `variant_id` comes from `variant`. `sku` is consumed as a fallback key, so it is not copied into `metadata`. `source` is set automatically (`web` for this JavaScript SDK call).

```javascript
rudderanalytics.track("Product Viewed", {
  product_id: "622c6f5d5cf86a4c77358033",
  sku: "8472-998-0112",
  name: "Cones of Dunshire",
  variant: "expansion pack",
  price: 49.99,
  currency: "USD",
  url: "https://www.website.com/product/path",
  image_url: "https://www.website.com/product/path.webp"
});
```

Braze custom event:

```json
{
  "name": "ecommerce.product_viewed",
  "properties": {
    "product_id": "622c6f5d5cf86a4c77358033",
    "product_name": "Cones of Dunshire",
    "variant_id": "expansion pack",
    "price": 49.99,
    "currency": "USD",
    "product_url": "https://www.website.com/product/path",
    "image_url": "https://www.website.com/product/path.webp",
    "source": "web"
  }
}
```

### Product Added

Product Added maps to `ecommerce.cart_updated` with `action: add`. The top-level product fields are folded into a one-item `products` array.

```javascript
rudderanalytics.track("Product Added", {
  cart_id: "skdjsid2dkdkd",
  product_id: "622c6f5d5cf86a4c77358033",
  name: "Cones of Dunshire",
  variant: "expansion pack",
  price: 49.99,
  quantity: 1,
  currency: "USD"
});
```

Braze custom event:

```json
{
  "name": "ecommerce.cart_updated",
  "properties": {
    "cart_id": "skdjsid2dkdkd",
    "currency": "USD",
    "products": [
      {
        "product_id": "622c6f5d5cf86a4c77358033",
        "product_name": "Cones of Dunshire",
        "variant_id": "expansion pack",
        "quantity": 1,
        "price": 49.99
      }
    ],
    "action": "add",
    "source": "web"
  }
}
```

## Automatic fields

| Field | <div style="width: 350px;">Behavior</div> |
| :--- | :--- |
| `source` | Set on every mapped event. If `properties.source` is `web`, `ios`, or `android`, that value is used. Otherwise, cloud mode derives `source` from the event channel and OS, and device mode sends the source platform (`web`, `ios`, or `android`). |
| `action` | Set to `add` or `remove` for `ecommerce.cart_updated` from the RudderStack event name. A customer-sent `properties.action` is not used. |
| `metadata` | Unmapped event-level properties. Unmapped product keys go to `products[].metadata`. These keys are passed through, not dropped. |

## Normalization

- **Lossless type coercion**: Numeric strings are converted to numbers for float fields (`"12.5"` becomes `12.5`). Integral strings are converted to integers (`"2"` becomes `2`). `"2.5"` on an integer field stays a string and the integration logs a warning. Numbers are stringified for string fields. Integers are accepted for float fields. Values that cannot be coerced without loss are sent as-is.
- **Empty-value scrubbing**: `null`, `""`, `{}`, and `[]` are removed from the payload. `0` and `false` are kept.

## Missing required fields

Missing Braze-required fields never fail or drop an event. Braze accepts the payload and processes it as a plain custom event instead of a commerce event, so the integration sends it with the field absent.

| Connection mode | <div style="width: 350px;">Behavior</div> |
| :--- | :--- |
| Device mode | The SDK logs a warning that lists the missing fields (browser console, Logcat, or Xcode console, depending on the SDK log level). |
| Cloud mode | Events that Braze responds to with schema warnings are marked as **Delivered with warning** on the destination [Events]({{< ref "dashboard-guides/event-metrics.md#destination-events" >}}) tab. |

The same send-anyway behavior applies to type mismatches — values that cannot be coerced without loss are sent as-is with a warning.

{{< image src="images/event-stream-destinations/braze-ecommerce-event-delivery.png" alt="Braze destination Events tab showing delivered events with warnings" >}}

The **Event details** table lists the warning category, status code, sample response, and sample payload:

{{< image src="images/event-stream-destinations/braze-ecommerce-delivered-with-warning.png" alt="Event failures and warnings panel showing status code 296 Delivered with warning" >}}

## See more

- [Braze Destination Setup Guide]({{< ref "destinations/streaming-destinations/braze/setup-guide.md" >}})
- [Braze Cloud Mode Integration]({{< ref "destinations/streaming-destinations/braze/cloud-mode.md" >}})
- [Braze Device Mode Integration]({{< ref "destinations/streaming-destinations/braze/device-mode.md" >}})
- [Ecommerce Events Specification]({{< ref "event-spec/ecommerce-events-spec/_index.md" >}})
- [Braze recommended events](https://www.braze.com/docs/user_guide/data/activation/events/recommended_events)

