# Shopify events and campaigns

After you connect the app and approve Shopify's requested permissions for order, cart, and checkout tracking (`read_orders`, `write_pixels`, `read_customer_events`), order, checkout, and checkout-step events start arriving without a separate Pushwoosh tracking screen. See [Prerequisites](/product/integrations/shopify/#prerequisites) and [Part 1](/product/integrations/shopify/#part-1-installation) for the approval step. Storefront browsing (product views and add-to-cart) also needs [**Push Init Embed** ON](/product/integrations/shopify/#part-4-enable-web-push-and-storefront-browsing-events).

Tracking covers three independent event groups. Each needs different access, so turning one off does not pause the others:

* **Order paid and checkout:** arrive from Shopify even if the [theme embed](/product/integrations/shopify/#part-4-enable-web-push-and-storefront-browsing-events) is off
* **Storefront browsing** (product views and add-to-cart): need the theme embed **ON**
* **Checkout steps:** need Shopify's checkout-tracking permissions from [Part 1](/product/integrations/shopify/#part-1-installation) (`write_pixels` and `read_customer_events`), approved separately from the order-tracking permission (`read_orders`) above

Before you connect the app, check [how Pushwoosh handles money and date attribute types](#order-paid-and-checkout-events) for `PW_OrderCreated` and `PW_AbandonedCart`. A wrong type sent before you catch it can't be fixed retroactively.

## Order paid and checkout events

Use these for post-purchase messaging (`PW_OrderCreated`), abandoned-checkout journeys or segments (`PW_AbandonedCart` / `PW_AbandonedCartUpdate`), and purchase conversion mapping. They arrive from Shopify even if the shopper blocks browser tracking:

<Aside type="caution" title="Requires the app connected and read_orders approved">
No extra toggle beyond that: connect the app in [Part 1](/product/integrations/shopify/#part-1-installation) and approve Shopify's order-tracking permission (`read_orders`). Unlike storefront browsing, these events don't depend on the theme embed being on.
</Aside>

| Event name in Pushwoosh | When it fires |
| ----- | ----- |
| `PW_OrderCreated` | An order is paid |
| `PW_AbandonedCart` | A shopper starts checkout |
| `PW_AbandonedCartUpdate` | The shopper adds, removes, or changes items during an existing checkout |

These correspond to the order and checkout notifications Shopify sends to Pushwoosh automatically (the `orders/paid`, `checkouts/create`, and `checkouts/update` webhooks, listed under [Synchronized entities](/product/integrations/shopify/#synchronized-entities)).

Shopify's own Flow condition, the `checkouts/create` webhook, and the Products block's Cart source are three different things that all get called **cart abandonment**. Pick the one that matches what you're building, using the table below.

| What you want to build | What to use |
| ----- | ----- |
| Reminder from Shopify Flow (Shopify’s own **Cart abandonment** condition) | Shopify Flow + **Pushwoosh send notification** action. Not the `PW_AbandonedCart` event names. |
| Reminder or segment in Pushwoosh from checkout started or cart changed | Customer Journey or segment on `PW_AbandonedCart` / `PW_AbandonedCartUpdate` |
| Show the shopper’s cart items inside an email | Products block [Cart source](/product/content/email-content/drag-and-drop-email-editor/blocks/#show-items-from-the-cart) (uses the `PW_AbandonedCartID` tag at send time). Separate from which trigger started the journey. |

**How these events behave.** `checkouts/create` only means a checkout was started. Shopify hasn't decided it's abandoned, and no order has been confirmed either.

While the shopper edits checkout, `PW_AbandonedCartUpdate` fires again for the same checkout and overwrites the cart stored from `PW_AbandonedCart`. Reminder emails that use Cart source pick up the latest items at send time. You don't need a second journey trigger only to refresh the cart.

`PW_AbandonedCart` and `PW_AbandonedCartUpdate` set the `PW_AbandonedCartID` tag on the shopper, which is what Cart source reads at send time. `PW_OrderCreated` clears it right away, so a shopper who already checked out skips the block instead of getting a reminder for a cart they no longer have. See [What happens when you send PW_OrderCreated](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-an-abandoned-cart-campaign/#what-happens-when-you-send-pw_ordercreated) for the exact clearing behavior.

<Aside type="note" title="A shopper who starts checkout twice re-enters the journey only if you allow it">
By default, a Customer Journey doesn't re-enter someone who's already in it, so a second `checkouts/create` from the same shopper while their first abandoned-cart journey is still running is ignored, not restarted. Turn on **Allow re-entry** on the trigger if you want each new checkout to reset their reminder timing instead. See [Control whether users can re-enter the campaign](/product/customer-journey/journey-elements/entry-elements/trigger-based-entry/#control-whether-users-can-re-enter-the-campaign).
</Aside>

### Attributes of order and checkout events

`PW_OrderCreated` and `PW_AbandonedCart` include:

| Attribute | Description |
| ----- | ----- |
| `orderId` | The order or checkout identifier |
| `email` | The shopper's email |
| `totalPrice` | The order or cart total |
| `shippingAmount` | The shipping cost |
| `taxAmount` | The tax amount |
| `discountAmount` | The discount amount |
| `currency` | The currency code |
| `items` | The cart's line items |
| `customerId` | Only for registered customers, not present for guest checkouts |
| `orderUrl` | The order status page for `PW_OrderCreated`, or the abandoned checkout's recovery URL for `PW_AbandonedCart` and `PW_AbandonedCartUpdate`. Use it to build a "Complete your purchase" link or button. |
| `createdDate` | When the order or checkout was created |
| `abandonedDate` | `PW_AbandonedCart` and `PW_AbandonedCartUpdate` only. When the cart was last touched, not when it was first created |

<Aside type="caution" title="Get the money and date attribute types right">
Pushwoosh infers each attribute's type from the first event it sees, and that inference is unreliable for money and date fields like `totalPrice`, `shippingAmount`, `taxAmount`, `discountAmount`, and `createdDate`/`abandonedDate` above. A whole-number price like `129` infers as an integer, which then truncates `129.90` to `129` on every later event.

**Haven't connected the app yet?** Declare `PW_AbandonedCart` and `PW_OrderCreated` with the correct types before you do, using the table in [Create the events in your Control Panel](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-an-abandoned-cart-campaign/#create-the-events-in-your-control-panel). Skip that guide's **Send the events from your backend** step. The Shopify integration already sends them for you.

**Already connected?** Check the types now: go to **Audience → Events** in the Control Panel, open `PW_AbandonedCart` and `PW_OrderCreated`, and compare each attribute against the same table. Fixing a wrong type there corrects it going forward, but any value already sent under the wrong type is gone for good.
</Aside>

Guest checkouts can still send order and cart events, but without a registered customer ID on the event.

`PW_AbandonedCartUpdate` carries the same attributes. If your account also has the [Magento](/product/integrations/magento-integration/) integration connected, its orders and abandoned carts post to these same event names, so a segment or journey built on `PW_OrderCreated` / `PW_AbandonedCart` catches both stores, and the attribute types above apply to both too. Magento has no equivalent to `PW_AbandonedCartUpdate`, because it doesn't track in-progress checkout edits.

`items` in particular arrives from Shopify with only a few fields filled in.

<Aside type="note" title="Cart items are enriched from your Product Catalog">
The `items` Shopify sends on `PW_AbandonedCart` and `PW_AbandonedCartUpdate` carry only `productId`, `name`, `sku`, `quantity`, and `price`. Pushwoosh fills in whatever else that item is still missing, such as `description`, `imageUrl`, `productUrl`, `category`, and other catalog fields, from the matching product in your connected [Product Catalog](/product/content/product-catalog/) (see [Automatic product catalog connection](/product/integrations/shopify/#connect-your-pushwoosh-app)) whenever a `productId` matches. There's nothing extra to send for this. A product missing from the catalog just keeps showing with the fields Shopify sent.
</Aside>

## Storefront browsing events

Use these for browse-abandonment segments or journeys, and for email recommendation strategies that need product views (for example **Based on what they viewed**). See [How to set up product recommendations in email](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-product-recommendations-in-email/).

<Aside type="caution" title="Requires Push Init Embed ON">
Turn **Push Init Embed** **ON** in [Part 4](/product/integrations/shopify/#part-4-enable-web-push-and-storefront-browsing-events). Same theme embed as web push. Nothing else to add to the theme.

If the embed is off, or a shopper blocks its script, you lose product views and add-to-cart only. `PW_OrderCreated` and `PW_AbandonedCart` still arrive from Shopify.
</Aside>

Both events are collected by the same theme embed. Nothing else to add to the theme once **Push Init Embed** is **ON**:

* `PW_ProductViewed`: the shopper opens a product page.
* `PW_AddToCart`: the shopper adds an item to cart without reloading the page, which is how most Shopify themes do it.

<Aside type="note" title="Two things to check before you build on these events">
* `PW_ProductViewed` counts every page load, with no filtering. Reloads, repeat views in the same visit, and even bot traffic all count the same as a real product view. A recommendation strategy built on this event inherits that noise.
* `PW_AddToCart` only fires if your store's **Add to cart** button doesn't reload the page. Test this yourself: click **Add to cart** in your store and watch what happens. Does just the cart counter update, or does the whole page refresh? Most Shopify themes do the first, updating the counter without reloading (a background request to `/cart/add`). If your theme reloads the page instead, `PW_AddToCart` will never fire, but `PW_ProductViewed` and the order/checkout events still work fine either way.
</Aside>

### Attributes of storefront browsing events

Both events include:

| Attribute | Description |
| ----- | ----- |
| `items` | The viewed or added product(s), each with `productId`, `name`, `sku`, `quantity`, `price` |
| `totalPrice` | The combined price of `items` |
| `productUrl` | The page the shopper was on |
| `currency` | Only when the storefront exposes it |

A message that shows `totalPrice` needs a fallback for when `currency` is missing, since the number alone doesn't say which currency it's in.

Neither event carries `email` or `customerId` as an attribute. The shopper is identified the same way the rest of the theme embed does it, through the SDK session tied to the metafield User ID (see [Part 2](/product/integrations/shopify/#part-2-setup-customer-user-id-via-metafields)), not through the event payload.

`PW_ProductViewed` and `PW_AddToCart`'s `items` carry the same fields Shopify sends before enrichment: `productId`, `name`, `sku`, `quantity`, `price`. Pushwoosh doesn't fill in the rest of the catalog fields for these two events, the way it does for `PW_AbandonedCart`'s `items` (see [Cart items are enriched from your Product Catalog](#order-paid-and-checkout-events) above).

A recommendation strategy that needs full product details, like **Based on what they viewed**, looks them up from your [Product Catalog](/product/content/product-catalog/) by `productId` when the message renders. See [How to set up product recommendations in email](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-product-recommendations-in-email/) for what each strategy needs.

## Checkout step events

Use these to see where people drop off before purchase, and to message people who started checkout but did not reach a paid order.

<Aside type="caution" title="Requires pixel-related app permissions">
Approve the checkout-tracking permissions Shopify asks for when you connect the app (`write_pixels` and `read_customer_events`). See [Part 1](/product/integrations/shopify/#part-1-installation) for that step. No theme embed step. Shopify reports these milestones automatically once those permissions are approved (technically, via Shopify's [Web Pixel](https://shopify.dev/docs/api/web-pixels-api)).
</Aside>

* `PW_CheckoutContactInfo`: the shopper entered their contact information.
* `PW_PaymentInfoSubmitted`: the shopper submitted their payment details.

A completed sale is `PW_OrderCreated` from the section above, not these two events.

`PW_AbandonedCart` only tells you a checkout started. These two events mark two later points in the same checkout, in that order: contact info first, payment details second. A shopper who reached `PW_PaymentInfoSubmitted` and never got a following `PW_OrderCreated` got further than one who only triggered `PW_AbandonedCart`, so a message built on that stage can address a stalled payment specifically, instead of a generic cart reminder.

### Attributes of checkout step events

Both events include:

| Attribute | Description |
| ----- | ----- |
| `items` | The checkout's line items, each with `productId`, `name`, `sku`, `quantity`, `price` |
| `orderId` | The checkout token, or the Shopify order ID once the checkout has one |
| `totalPrice` | The checkout total |
| `email` | Only when the checkout has one. A phone-based checkout (no email entered) sends the event without it. |
| `currency` | Only when the checkout has one |

## Check that it works

Do the matching action in the store, then confirm the event name shows up in Pushwoosh. The event itself is processed in real time, but the two places you'd check it react differently to a name Pushwoosh hasn't seen before:

* In the **Event** dropdown when you [create a segment by events](/product/audience-data-and-segmentation/segmentation/create-segments/by-events/), a new name appears right away.
* In the **Event** dropdown when you add a [Customer Journey](/product/customer-journey/pushwoosh-journey-overview/) trigger, a new name can take up to 10 minutes to show up (that dropdown is cached). If you don't see it yet, wait rather than assume the integration is broken.

| Test action | Event |
| ----- | ----- |
| Pay a test order | `PW_OrderCreated` |
| Start a checkout | `PW_AbandonedCart` |
| Change items in that checkout | `PW_AbandonedCartUpdate` |
| Open a product page (**Push Init Embed** ON) | `PW_ProductViewed` |
| Add an item to cart | `PW_AddToCart` |
| Enter contact info at checkout | `PW_CheckoutContactInfo` |
| Submit payment info at checkout | `PW_PaymentInfoSubmitted` |

## Use the events

After you know which event you need, use it in two places:

* As a trigger or segment condition: pick it in the **Event** dropdown when you [create a segment by events](/product/audience-data-and-segmentation/segmentation/create-segments/by-events/) or add a [Customer Journey](/product/customer-journey/pushwoosh-journey-overview/) trigger.
* In the message text: on the message step, turn on **Personalize message with event attributes** and pick the attribute (for example the order total from `PW_OrderCreated`). You can also insert the attribute with a Liquid placeholder. See [Dynamic Content and Liquid Templates in journeys](/product/customer-journey/journey-elements/dynamic-content-and-liquid-templates-in-journeys/).

For paid orders, you can also map `PW_OrderCreated` as a [conversion event](/product/audience-data-and-segmentation/events/conversion-events/) so Shopify purchases feed RFM segmentation, journey attribution, and [Product block statistics](/product/statistics-and-analytics/messaging-statistics/email-statistics/).

### Example campaigns

Each one fires at a different point in the shopper's path, so they reach different audiences: how far someone got before dropping off, or whether they reached checkout at all, decides which one applies.

* **Abandoned-cart reminder.** Catches every shopper who started checkout, no matter how far they got: the widest audience here. Build it as a Customer Journey: trigger on `PW_AbandonedCart`, wait for `PW_OrderCreated`, then send an email with a [Cart source](/product/content/email-content/drag-and-drop-email-editor/blocks/#show-items-from-the-cart) block to whoever hasn't paid. See [How to set up an abandoned cart campaign](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-an-abandoned-cart-campaign/) for the full setup.
* **Follow-up for stalled checkouts.** Catches only shoppers who got as far as entering contact info but never paid, a narrower slice of the audience above. Segment on `PW_CheckoutContactInfo`, then exclude anyone who also has `PW_PaymentInfoSubmitted` or `PW_OrderCreated`.
* **Recovery for a stalled payment.** Catches shoppers who reached payment but never completed the order, further along than the follow-up above and mutually exclusive with it. Segment on `PW_PaymentInfoSubmitted`, then exclude anyone who also has `PW_OrderCreated`.
* **Browse recommendation email.** Catches shoppers who looked at products but never started checkout at all, a different audience from the reminders above. Use `PW_ProductViewed` with the Products block's **Based on what they viewed** strategy. See [How to set up product recommendations in email](/product/how-to-guides/email-marketing-campaigns/how-to-set-up-product-recommendations-in-email/).