Skip to main content
RentwareRentware
Trial

Tracking reference for developers: window.RTR_HOOKS

Last updated:

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.

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

<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 CustomEvents.

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.

// 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:

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:

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

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:

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:

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.

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

productId is the article ID, quantity the amount added.

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.

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:

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.

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.