# Tracking reference for developers: window.RTR_HOOKS

How the Rentware widgets send booking events to the dataLayer, gtag, the Meta pixel, Umami and the OpenAI pixel: options, payloads, callbacks, GTM setup and consent.

This page is for developers and agencies who connect the Rentware booking widgets to their own tags. It describes the **browser** side. Server-side tracking via Rentware (GA4, Google Ads and Meta from our server) needs no code: see [Conversion tracking: what to set up where](https://rentware.com/en/help/conversion-tracking/).

## Overview

- The widget sends **nothing** to tags in the browser unless the page defines `window.RTR_HOOKS`. The older name `window.RENTWARE_HOOKS` works too; if both exist, `RTR_HOOKS` wins.
- `RTR_HOOKS` is read each time an event fires. It can be set late, for example by a GTM tag, as long as it exists before the event.
- The widget never loads a tag itself. `gtag`, `fbq`, `umami` and `oaiq` must already be on the page; if a function is missing, that channel is skipped silently. Only the `dataLayer` is created when it doesn't exist.
- The widget does not check consent for your tags. Load them only after consent (your cookie banner's job).
- All values are **net**: discounts applied, deposits left out, VAT in `tax`. Amounts are numbers in major units (`49.5`), except where noted.

## Snippet

```html
<script>
  window.RTR_HOOKS = Object.assign(window.RTR_HOOKS || {}, {
    // GA4-style ecommerce events in window.dataLayer (Google Tag Manager; works without GTM)
    enableDataLayer: true,

    // gtag('event', …) directly
    // enableGtagTracking: true,
    // extra gtag('event', 'conversion', …) on purchase
    // gtagAdsSendTo: 'AW-123456789/AbCdEfGhIj',
    // gtag('set', 'user_data', …) for enhanced conversions
    // enableGtagUserDataTracking: true,

    // fbq('track', …) on your Meta pixel
    // enableMetaTracking: true,
    // also fbq('init', id, {…}) with customer data (advanced matching)
    // metaPixelId: '123456789012345',

    // umami.track(…)
    // enableUmamiTracking: true,
    // oaiq('measure', 'order_created', …)
    // enableOpenAiTracking: true,

    // onAddToCart: (data) => {},
    // onInitiateCheckout: (data) => {},
    // onCheckoutSuccess: (data) => {},
  })
</script>
```

`Object.assign` keeps keys another script has already set.

## All options

| Key | Type | Effect |
|---|---|---|
| `enableDataLayer` | boolean | Pushes `add_to_cart`, `begin_checkout`, `add_shipping_info` and `purchase` into `window.dataLayer` |
| `enableGtagTracking` | boolean | Calls `gtag('event', …)` with the same four events |
| `gtagAdsSendTo` | string | On purchase, also calls `gtag('event', 'conversion', {…, send_to})`, e.g. `'AW-123456789/AbCdEfGhIj'` |
| `enableGtagUserDataTracking` | boolean | When the customer data is saved, calls `gtag('set', 'user_data', {…})` |
| `enableMetaTracking` | boolean | Calls `fbq('track', …)`: `AddToCart`, `InitiateCheckout`, `Purchase` |
| `metaPixelId` | string | With `enableMetaTracking`: when the customer data is saved, calls `fbq('init', metaPixelId, {…})` with customer data |
| `enableUmamiTracking` | boolean | Calls `umami.track(…)`: `add_to_cart`, `begin_checkout`, `purchase` |
| `enableOpenAiTracking` | boolean | On purchase, calls `oaiq('measure', 'order_created', …)` |
| `onAddToCart` | function | Called after an article was added |
| `onInitiateCheckout` | function | Called when the checkout opens |
| `onCheckoutSuccess` | function | Called when the booking is completed |

## Events per step

| Step | dataLayer / gtag | Meta pixel | Umami | OpenAI | Fires when |
|---|---|---|---|---|---|
| Add to cart | `add_to_cart` | `AddToCart` | `add_to_cart` | – | an article was added successfully |
| Checkout opened | `begin_checkout` | `InitiateCheckout` | `begin_checkout` | – | the checkout opens |
| Customer data saved | `add_shipping_info` | – | – | – | the contact and billing form is saved |
| Booking completed | `purchase` | `Purchase` | `purchase` | `order_created` | see "When purchase fires" |

There is no browser event for choosing location and dates, for viewing an article, or for starting payment (`add_payment_info` exists only server-side). There are no DOM `CustomEvent`s.

### When purchase fires

`purchase` fires when the booking is **completed**, which isn't always when it is paid:

- **Reservation, prepayment or bank transfer, voucher:** right after submitting (not paid yet).
- **Card payment inside the checkout:** when the payment succeeds.
- **Redirect payments (PayPal, payment methods that leave the page):** only when the visitor returns to a page with the widget. If they never come back, there is no browser `purchase`. The server-side purchase is sent anyway.

## dataLayer

Before every event the widget pushes `{ ecommerce: null }` to clear the previous ecommerce object, as Google recommends.

```js
// add_to_cart: every article whose quantity went up, auto-added ones included
{ event: 'add_to_cart', ecommerce: {
  currency: 'EUR',
  value: 59.5,                      // Σ price × quantity
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2, index: 0 }]
} }

// begin_checkout and add_shipping_info: net value of the cart
{ event: 'begin_checkout', ecommerce: { currency: 'EUR', value: 59.5 } }
{ event: 'add_shipping_info', ecommerce: { currency: 'EUR', value: 59.5 } }

// purchase
{ event: 'purchase', ecommerce: {
  transaction_id: 'SNLDX1',         // order reference
  currency: 'EUR',
  value: 59.5,                      // net, discounts applied, deposits left out
  tax: 11.31,                       // VAT
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2 }]
} }
```

- `price` is the discounted net unit price. Deposit lines are never in `items`.
- `transaction_id` is the **order reference** (e.g. `SNLDX1`). The server sends the same value as GA4 `transaction_id`, Google Ads `order_id` and Meta `order_id`.
- The dataLayer contains **no** email, phone or event ID.

## gtag

With `enableGtagTracking`, the widget calls `gtag('event', name, params)` with the same `ecommerce` objects as the dataLayer (`purchase` items also carry `index`).

With `gtagAdsSendTo`, the purchase additionally triggers:

```js
gtag('event', 'conversion', {
  transaction_id: 'SNLDX1', currency: 'EUR', value: 59.5, tax: 11.31,
  items: [/* as above */],
  send_to: 'AW-123456789/AbCdEfGhIj'
})
```

With `enableGtagUserDataTracking`, when the customer data is saved:

```js
gtag('set', 'user_data', {
  email: 'jane@example.com',
  phone_number: '+4915112345678',   // only if valid E.164, otherwise left out
  address: { first_name: 'Jane', last_name: 'Doe', city: 'Berlin', postal_code: '10115', country: 'DE' }
})
```

The shipping address is used when the customer entered one, otherwise the billing address.

## Meta pixel

```js
fbq('track', 'AddToCart', { content_ids: ['a1b2c3'], content_type: 'product', currency: 'EUR', value: '29.75' })

fbq('track', 'InitiateCheckout', { value: '59.50', currency: 'EUR' })

fbq('track', 'Purchase', {
  order_id: 'SNLDX1',
  currency: 'EUR',
  value: '59.50',
  content_ids: ['a1b2c3'],
  content_type: 'product',
  contents: [{ id: 'a1b2c3', quantity: 2, price: '29.75', item_name: 'SUP Board' }],
  num_items: 1                      // number of cart lines
}, { eventID: 'purchase_SNLDX1' })
```

- Meta amounts are strings with two decimals.
- `eventID` is `purchase_` + order reference, the same `event_id` as the server-side Conversions API Purchase, so Meta deduplicates the two.
- With `metaPixelId`, when the customer data is saved: `fbq('init', metaPixelId, { em, fn, ln, ct, zp, country })` (plain values; the pixel hashes them).

## Umami

With `enableUmamiTracking` and the Umami script on the page:

```js
umami.track('add_to_cart', { item_id: 'a1b2c3', item_name: 'SUP Board', quantity: 1, currency: 'EUR', value: 29.75 })
umami.track('begin_checkout', { value: 59.5, currency: 'EUR' })
umami.track('purchase', { revenue: 59.5, currency: 'EUR', transaction_id: 'SNLDX1', num_items: 1 })
```

Only `purchase` carries `revenue`. Empty fields are left out. Umami sets no cookies; whether you ask for consent is your decision.

## OpenAI Ads pixel

With `enableOpenAiTracking` and the OpenAI pixel (`oaiq`) on the page:

```js
oaiq('measure', 'order_created', {
  type: 'contents',
  amount: 5950,                     // minor units (cents), net
  currency: 'EUR',
  contents: [{ id: 'a1b2c3', name: 'SUP Board', content_type: 'product', quantity: 2 }]
}, { event_id: 'purchase_SNLDX1' })
```

You can also leave the option off and call `oaiq` yourself in `onCheckoutSuccess`.

## Callbacks

Errors thrown inside a callback are caught and logged to the console, so they don't break the booking.

```js
onAddToCart({ productId, quantity, currency, name })
```

`productId` is the article ID, `quantity` the amount added.

```js
onInitiateCheckout({ transactionTotal, transactionId })
```

`transactionTotal` is the **amount to pay** (gross, deposits included), not the net cart value the other events use. `transactionId` is the order reference.

```js
onCheckoutSuccess({ ecommerceData, fbqData, eventID, price })
```

- `ecommerceData.purchase`: the dataLayer `purchase` object (`transaction_id`, `currency`, `value`, `tax`, `items`).
- `fbqData`: the Meta Purchase parameters (without `order_id`).
- `eventID`: `purchase_` + order reference.
- `price`: legacy object `{ gross, net, vat }` in cents, deposits included. Use `ecommerceData` instead.

Example, a custom pixel:

```js
window.RTR_HOOKS = Object.assign(window.RTR_HOOKS || {}, {
  onCheckoutSuccess: ({ ecommerceData, eventID }) => {
    const p = ecommerceData.purchase
    myPixel('purchase', { order: p.transaction_id, value: p.value, currency: p.currency, dedup: eventID })
  },
})
```

## Google Tag Manager

1. Add the snippet with `enableDataLayer: true`, either in the page or as a GTM **Custom HTML** tag with the trigger **Initialization – All Pages**.
2. Create **Custom Event** triggers for `add_to_cart`, `begin_checkout`, `add_shipping_info` and `purchase`.
3. Create **Data Layer Variables** (version 2), for example `ecommerce.value`, `ecommerce.currency`, `ecommerce.transaction_id`, `ecommerce.items`.
4. GA4 event tags: event name `{{Event}}` or the fixed name, and "Send Ecommerce data" with data source "Data Layer".
5. **Meta Purchase via GTM:** set the event ID to `purchase_{{DLV - ecommerce.transaction_id}}`. It then matches the server-side Purchase and Meta deduplicates them.

If you also use server-side tracking via Rentware for the same GA4 property, don't send the dataLayer events there too: GA4 only deduplicates `purchase` (by `transaction_id`), so add to cart and the other funnel events would count twice.

## Consent

The widget reads consent for what it sends to the **Rentware server** (which decides what goes to GA4, Google Ads and Meta). It does not gate your own tags.

- It reads **Google Consent Mode v2** from `window.dataLayer`: `gtag('consent', 'default' | 'update', {…})` entries, and GTM-style objects such as `{ event: 'consent_update', ad_storage: 'granted', … }`. It replays them in order: a later `update` overrides earlier values.
- Signals: `ad_storage`, `analytics_storage`, `ad_user_data`, `ad_personalization`. Each one defaults to `denied`.
- If the page has no Consent Mode entry at all, the request is marked as "no consent signal", and the tracking log shows `skipped: no consent signal`.
- Region-specific `default` entries (with `region`) can't be matched to the visitor's location in the browser: only their `denied` values are applied.
- **Late consent works:** consent is read again with every request. A visitor who accepts after the widget loaded is covered from the next request on, as long as your banner pushes a Consent Mode `update` into `window.dataLayer`.
- The widget only reads the dataLayer named `dataLayer` in the same window. A renamed dataLayer, or a widget inside an iframe, means no consent signal.

To check a page: accept the banner, open the console and type `dataLayer`. There must be a `consent` entry (or a `consent_update` event) before the visitor books.

## Click IDs

- The widget captures `gclid`, `gbraid` and `wbraid` from the URL (`gclid` also from Google's `_gl` linker parameter or the `_gcl_aw` cookie) and `fbclid` from the URL or Meta's `_fbc` cookie. The URL wins over the cookie.
- Capture happens **only with `ad_storage` consent**. Nothing is read from cookies or stored before that. If consent arrives later while the click ID is still in the URL, it is captured then.
- Click IDs are kept in `sessionStorage` for the tab (same tab, same domain, gone when the tab closes). The first click wins.
- The widget sends them with its requests to the Rentware server, which uses them for Google Ads and Meta. They are not added to your browser events.
- Landing page and booking page differ? Run the widget script on the landing page too, or let your Google tag (with `url_passthrough`) or Meta pixel write the `_gcl_aw` / `_fbc` cookie. `gbraid` and `wbraid` have no cookie fallback.

## Test checklist

1. Add `RTR_HOOKS`, open the page, accept the banner.
2. Add an article to the cart, then type `dataLayer` in the console: the last entries are `{ ecommerce: null }` and `{ event: 'add_to_cart', … }`.
3. Complete a booking with "reserve" or prepayment and check the `purchase` entry; its `transaction_id` is the order reference.
4. Cancel the test booking in Rentware afterwards.
