Skip to main content

The Tie data layer: rr_* event reference

The exact spec for the Tie data layer events: fields, sample code, validation.

T
Written by Tim Hughes

The field-level spec for the four Tie ecommerce events. All examples use a fictional acme-coffee.com store; swap in your real product data.

Start with the table below. How much of this you implement depends on your platform, and on two of the three setups you do not implement all four events. Reading the spec without checking that first is the most common way this goes wrong in both directions: building code you did not need, or leaving a gap that looks fine right up until the reports are empty.

Who implements which event

Your setup

rr_view_item and rr_add_to_cart

rr_begin_checkout and rr_purchase

Standard Shopify (Shopify-hosted theme)

Tie

Tie

Headless Shopify (Hydrogen, Oxygen, Next.js or similar front end on a Shopify backend)

Your developers

The Customer Events pixel you add in Shopify Admin. No code.

Everything else (WooCommerce, Magento, BigCommerce, Salesforce Commerce Cloud, fully custom)

Your developers

Your developers

On standard Shopify you implement nothing on this page. The Tie Pixel loader and the Customer Events pixel together produce all four events for you, so treat this article as reference only.

On headless Shopify your storefront runs your code but your checkout still runs on Shopify. Your app cannot see the checkout, so the Customer Events pixel captures those two events instead. Adding that pixel is a required step, not an optional one: skip it and Tie receives browsing activity with no checkouts and no orders, which looks healthy from the outside.

On everything else you implement all four. Read the note on rr_purchase below before you scope the work, because on these platforms that one event carries more weight than the other three.

On non-Shopify platforms, rr_purchase is your order data

Tie reads order history directly from Shopify on both Shopify setups. On every other platform it does not, so rr_purchase is the only way Tie ever learns about an order.

That makes it the event to get right and the event to verify on its own. It is also the one most often missed, because it lives on the order confirmation page, a different template from your product and cart pages. A site can have three events firing perfectly and still deliver no orders at all.

How it works

  1. Your site pushes events. As a shopper browses, your storefront pushes the rr_* events into window.dataLayer.

  2. The loader listens. The Tie Pixel loader picks up those pushes and reads the fields it needs.

  3. Tie resolves identity. Those signals go to Tie for matching against Tie's opted-in identity graph.

  4. Tie responds. Matched and enriched attributes come back into your data layer as an rrid_response event, which is also how you confirm the round trip is working.

  5. Your tools receive it. Enriched attributes flow on to your email, SMS, and ad platforms.

Tie requires a consent management platform on every site where the tracker runs, and the loader must not execute before the shopper grants consent. See Install the Tie Pixel with your consent banner (CMP).

The four events

Event

Fires when

Payload bucket

rr_view_item

Product detail page load

ecommerce.detail.products

rr_add_to_cart

Add to cart

ecommerce.add.products

rr_begin_checkout

Checkout started

ecommerce.checkout.products

rr_purchase

Order confirmation page

ecommerce.purchase.products

The rr_ prefix is required and the names are fixed. Tie's loader only detects the rr_* events, so a site pushing an un-prefixed view_item will not be tracked. The prefix is historical: Tie was formerly named Revenue Roll.

The two-push pattern

Every event clears the previous ecommerce object first, then pushes:

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({ event: "rr_<event_name>", ecommerce: { /* ... */ } });

Common fields

Field

Meaning

event

The rr_* event name

rr_cart_id

Unique cart or browsing-session ID (cookie- or backend-generated)

email_address

Shopper email. Required on rr_purchase; optional on the other three. Send the shopper's email once it is known, otherwise send null

ecommerce.currencyCode

ISO 4217 currency, for example USD

…products[].name

Product display name, as shown on the product page

…products[].image

Absolute product image URL. Send this on all four events. It is what populates product images in your email and SMS personalization, and it is the field most often left empty on headless and custom storefronts

…products[].id

Internal product ID or SKU

…products[].variantId

Variant ID (size, color, option)

…products[].shopifyId

Shopify global ID shopify_<country>_<product>_<variant>. Shopify backends only; omit it elsewhere

…products[].price

Price of the variant (numeric)

…products[].brand

Brand or vendor

…products[].variant

Human-readable variant label, for example 32oz

…products[].quantity

Quantity. Required on rr_add_to_cart

…products[].variantSku

Variant SKU for ERP or fulfillment. Checkout and purchase events

rr_purchase extra fields

The purchase event also carries buyer identity and order financials, which is what lifts the match rate on orders:

Field

Meaning

first_name, last_name

Buyer billing name

email_address

Buyer email. Required on this event

phone_number

Buyer phone, digits only (strip parentheses, dashes, and spaces)

customer_id

Your internal customer or user ID

country, region, city, zip

Billing address components

purchase.actionField.id

Order number or transaction ID

purchase.actionField.revenue

Gross order total, including tax and shipping

purchase.actionField.tax

Tax amount

purchase.actionField.shipping

Shipping cost

purchase.actionField.coupon

Promo code used

purchase.actionField.discount_amount

Discount value applied

Sample: rr_view_item

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "rr_view_item",
rr_cart_id: cartId,
email_address: customer.email || null, // optional: populate if the shopper is logged in
ecommerce: {
currencyCode: "USD",
detail: {
products: [{
name: "Cold Brew Concentrate 32oz",
id: "PROD-1001",
image: "https://cdn.acme-coffee.com/products/cold-brew-32.png",
variantId: "VAR-200",
price: 18.50,
brand: "Acme Coffee",
variant: "32oz"
}]
}
}
});

Sample: rr_add_to_cart

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "rr_add_to_cart",
rr_cart_id: cartId,
email_address: customer.email || null, // optional: populate if the shopper is logged in
ecommerce: {
currencyCode: "USD",
add: {
products: [{
name: "Cold Brew Concentrate 32oz",
id: "PROD-1001",
image: "https://cdn.acme-coffee.com/products/cold-brew-32.png",
variantId: "VAR-200",
price: 18.50,
brand: "Acme Coffee",
variant: "32oz",
quantity: 1
}]
}
}
});

Sample: rr_begin_checkout

On headless Shopify you do not build this event. The Customer Events pixel produces it.

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "rr_begin_checkout",
rr_cart_id: cartId,
email_address: customer.email || null, // optional: populate if known at checkout start
ecommerce: {
currencyCode: "USD",
checkout: {
actionField: { step: 1 },
products: [{
name: "Cold Brew Concentrate 32oz",
id: "PROD-1001",
variantSku: "CB-32-CONC",
image: "https://cdn.acme-coffee.com/products/cold-brew-32.png",
variantId: "VAR-200",
price: 18.50,
brand: "Acme Coffee",
variant: "32oz"
}]
}
}
});

Sample: rr_purchase

On headless Shopify you do not build this event either. The Customer Events pixel produces it. On every other non-Shopify platform this event is your order data, so treat it as the priority.

window.dataLayer.push({ ecommerce: null });
window.dataLayer.push({
event: "rr_purchase",
rr_cart_id: cartId,
first_name: customer.firstName,
last_name: customer.lastName,
email_address: customer.email,
phone_number: normalizePhone(customer.phone), // digits only
customer_id: customer.id,
country: billing.countryCode,
region: billing.provinceCode,
city: billing.city,
zip: billing.zip,
ecommerce: {
currencyCode: "USD",
purchase: {
actionField: {
id: String(order.name), // for example "#1042"
affiliation: shop.name,
revenue: order.totalPrice, // decimal
tax: order.taxPrice,
shipping: order.shippingPrice,
coupon: order.discountTitle || "",
discount_amount: order.discountAmount || 0
},
products: [{
name: "Cold Brew Concentrate 32oz",
id: "PROD-1001",
variantSku: "CB-32-CONC",
image: "https://cdn.acme-coffee.com/products/cold-brew-32.png",
variantId: "VAR-200",
price: 18.50,
brand: "Acme Coffee",
variant: "32oz"
}]
}
}
});

Validation checks

  • Each event you own appears once per user action, and no more.

  • ecommerce is never null on a fired event. The null push is the reset that precedes it.

  • Every product object carries at least name, id, image, price, brand, and variant.

  • currencyCode and rr_cart_id appear on every ecommerce event.

  • On non-Shopify platforms, rr_purchase fires on a real completed order. Check this one on its own.

When your implementation passes these, work through the full verification guide, then tell your Tie contact so we can validate end to end with you.

Troubleshooting

What you see

Likely cause

Fix

No events at all in the console

The loader has not run, or consent has not been granted

Confirm the loader is in the <head> of every page, and accept your consent banner before testing

Console shows view_item, not rr_view_item

Namespace mismatch

Add the rr_ prefix. Tie detects only the prefixed names

Events appear but ecommerce is empty

The push fired before the product data was available

Move the push to after your product or order data resolves

Nothing fires until you reload with DevTools open

Events fired on page load, before you were watching

Open DevTools first, then load the page

Template placeholders appear as literal text in the payload

Variables are not resolving in your template

Populate the values server-side, or from your own data-layer variables

Product images are blank in your email or SMS

…products[].image is missing

Map and push an absolute image URL on all four events. This is the most common cause, and it is specific to headless and custom storefronts, where the image is rarely in the data layer by default

Everything fires except rr_purchase

The order confirmation page was never instrumented

Instrument it. On non-Shopify platforms this is your only order data

Still stuck? Work through Installed but not seeing data yet, or reach out in your shared Slack channel with Tie.

FAQ

I already have a data layer. Why can't Tie just use it? Tie's loader listens for a specific set of event names and a specific structure. If your existing data layer uses different names, for example view_item instead of rr_view_item, Tie will not detect or process those events.

Can I keep my existing data layer? Yes. The Tie data layer runs alongside an existing one without interfering with it, as long as the rr_* events are implemented. Your other tools are unaffected.

Why implement Tie's schema if I already track the same data elsewhere? Your existing data layers are not wired to Tie's enrichment logic. Tie depends on its own schema to know when to resolve an identity and what to send back, so without it the enrichment, attribution, and downstream activation do not happen.

Can Tie set up the data layer for us? Not on the platforms listed above as your developers. The data layer runs inside your site's code, which means changing it requires production access to your storefront, and the events can carry personal data that you are responsible for under your own privacy and consent obligations. Your developers or your agency implement it; Tie then validates the structure and confirms the enrichment flow works with you. On standard Shopify none of this applies, because Tie installs everything.

Do we need this if we're on Shopify? On standard Shopify, no. On headless Shopify, partly: your developers build the two browsing events and the Customer Events pixel handles checkout and purchase. The table at the top of this page is the short answer.

Did this answer your question?