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 namewindow.RENTWARE_HOOKSworks too; if both exist,RTR_HOOKSwins. RTR_HOOKSis 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,umamiandoaiqmust already be on the page; if a function is missing, that channel is skipped silently. Only thedataLayeris 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 }]
} }
priceis the discounted net unit price. Deposit lines are never initems.transaction_idis the order reference (e.g.SNLDX1). The server sends the same value as GA4transaction_id, Google Adsorder_idand Metaorder_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.
eventIDispurchase_+ order reference, the sameevent_idas 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 dataLayerpurchaseobject (transaction_id,currency,value,tax,items).fbqData: the Meta Purchase parameters (withoutorder_id).eventID:purchase_+ order reference.price: legacy object{ gross, net, vat }in cents, deposits included. UseecommerceDatainstead.
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
- Add the snippet with
enableDataLayer: true, either in the page or as a GTM Custom HTML tag with the trigger Initialization – All Pages. - Create Custom Event triggers for
add_to_cart,begin_checkout,add_shipping_infoandpurchase. - Create Data Layer Variables (version 2), for example
ecommerce.value,ecommerce.currency,ecommerce.transaction_id,ecommerce.items. - GA4 event tags: event name
{{Event}}or the fixed name, and “Send Ecommerce data” with data source “Data Layer”. - 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 laterupdateoverrides earlier values. - Signals:
ad_storage,analytics_storage,ad_user_data,ad_personalization. Each one defaults todenied. - 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
defaultentries (withregion) can’t be matched to the visitor’s location in the browser: only theirdeniedvalues 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
updateintowindow.dataLayer. - The widget only reads the dataLayer named
dataLayerin 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,gbraidandwbraidfrom the URL (gclidalso from Google’s_gllinker parameter or the_gcl_awcookie) andfbclidfrom the URL or Meta’s_fbccookie. The URL wins over the cookie. - Capture happens only with
ad_storageconsent. 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
sessionStoragefor 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/_fbccookie.gbraidandwbraidhave no cookie fallback.
Test checklist
- Add
RTR_HOOKS, open the page, accept the banner. - Add an article to the cart, then type
dataLayerin the console: the last entries are{ ecommerce: null }and{ event: 'add_to_cart', … }. - Complete a booking with “reserve” or prepayment and check the
purchaseentry; itstransaction_idis the order reference. - Cancel the test booking in Rentware afterwards.