# Référence de suivi pour les développeurs : window.RTR_HOOKS

Comment les widgets Rentware envoient les événements de réservation au dataLayer, à gtag, au pixel Meta, à Umami et au pixel OpenAI : options, payloads, callbacks, configuration GTM et consentement.

Cette page s’adresse aux développeurs et aux agences qui connectent les widgets de réservation Rentware à leurs propres balises. Elle décrit le côté **navigateur**. Le suivi côté serveur via Rentware (GA4, Google Ads et Meta depuis notre serveur) ne nécessite aucun code : voir [Suivi des conversions : que configurer, et où](https://rentware.com/fr/help/suivi-des-conversions/).

## Vue d’ensemble

- Le widget n’envoie **rien** aux balises du navigateur tant que la page ne définit pas `window.RTR_HOOKS`. L’ancien nom `window.RENTWARE_HOOKS` fonctionne aussi ; si les deux existent, `RTR_HOOKS` l’emporte.
- `RTR_HOOKS` est lu à chaque déclenchement d’un événement. Il peut être défini tardivement, par exemple par une balise GTM, tant qu’il existe avant l’événement.
- Le widget ne charge jamais de balise lui-même. `gtag`, `fbq`, `umami` et `oaiq` doivent déjà être présents sur la page ; si une fonction manque, ce canal est ignoré sans message. Seul le `dataLayer` est créé s’il n’existe pas.
- Le widget ne vérifie pas le consentement pour vos balises. Ne les chargez qu’après consentement (c’est le rôle de votre bannière cookies).
- Toutes les valeurs sont **nettes** : remises déduites, cautions exclues, TVA dans `tax`. Les montants sont des nombres en unités principales (`49.5`), sauf mention contraire.

## Snippet

```html
<script>
  window.RTR_HOOKS = Object.assign(window.RTR_HOOKS || {}, {
    // événements e-commerce au format GA4 dans window.dataLayer (Google Tag Manager ; fonctionne aussi sans GTM)
    enableDataLayer: true,

    // gtag('event', …) directement
    // enableGtagTracking: true,
    // gtag('event', 'conversion', …) supplémentaire lors de l’achat
    // gtagAdsSendTo: 'AW-123456789/AbCdEfGhIj',
    // gtag('set', 'user_data', …) pour les conversions améliorées
    // enableGtagUserDataTracking: true,

    // fbq('track', …) sur votre pixel Meta
    // enableMetaTracking: true,
    // en plus, fbq('init', id, {…}) avec les données client (correspondance avancée)
    // metaPixelId: '123456789012345',

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

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

`Object.assign` conserve les clés déjà définies par un autre script.

## Toutes les options

| Clé | Type | Effet |
|---|---|---|
| `enableDataLayer` | boolean | Pousse `add_to_cart`, `begin_checkout`, `add_shipping_info` et `purchase` dans `window.dataLayer` |
| `enableGtagTracking` | boolean | Appelle `gtag('event', …)` avec les mêmes quatre événements |
| `gtagAdsSendTo` | string | Lors de l’achat, appelle en plus `gtag('event', 'conversion', {…, send_to})`, par ex. `'AW-123456789/AbCdEfGhIj'` |
| `enableGtagUserDataTracking` | boolean | À l’enregistrement des données client, appelle `gtag('set', 'user_data', {…})` |
| `enableMetaTracking` | boolean | Appelle `fbq('track', …)` : `AddToCart`, `InitiateCheckout`, `Purchase` |
| `metaPixelId` | string | Avec `enableMetaTracking` : à l’enregistrement des données client, appelle `fbq('init', metaPixelId, {…})` avec les données client |
| `enableUmamiTracking` | boolean | Appelle `umami.track(…)` : `add_to_cart`, `begin_checkout`, `purchase` |
| `enableOpenAiTracking` | boolean | Lors de l’achat, appelle `oaiq('measure', 'order_created', …)` |
| `onAddToCart` | function | Appelée après l’ajout d’un article |
| `onInitiateCheckout` | function | Appelée à l’ouverture du tunnel de commande |
| `onCheckoutSuccess` | function | Appelée lorsque la réservation est finalisée |

## Événements par étape

| Étape | dataLayer / gtag | Pixel Meta | Umami | OpenAI | Se déclenche quand |
|---|---|---|---|---|---|
| Ajout au panier | `add_to_cart` | `AddToCart` | `add_to_cart` | – | un article a été ajouté avec succès |
| Ouverture du tunnel de commande | `begin_checkout` | `InitiateCheckout` | `begin_checkout` | – | le tunnel de commande s’ouvre |
| Données client enregistrées | `add_shipping_info` | – | – | – | le formulaire de contact et de facturation est enregistré |
| Réservation finalisée | `purchase` | `Purchase` | `purchase` | `order_created` | voir « Quand purchase se déclenche » |

Il n’existe pas d’événement navigateur pour le choix du lieu et des dates, la consultation d’un article ou le début du paiement (`add_payment_info` n’existe que côté serveur). Il n’y a pas de `CustomEvent` DOM.

### Quand purchase se déclenche

`purchase` se déclenche lorsque la réservation est **finalisée**, ce qui ne coïncide pas toujours avec le paiement :

- **Réservation, prépaiement ou virement bancaire, bon d’achat :** juste après l’envoi (pas encore payé).
- **Paiement par carte dans le tunnel de commande :** lorsque le paiement réussit.
- **Paiements avec redirection (PayPal, moyens de paiement qui quittent la page) :** seulement lorsque le visiteur revient sur une page comportant le widget. S’il ne revient jamais, il n’y a pas de `purchase` navigateur. L’achat côté serveur est envoyé malgré tout.

## dataLayer

Avant chaque événement, le widget pousse `{ ecommerce: null }` pour vider l’objet ecommerce précédent, comme le recommande Google.

```js
// add_to_cart : chaque article dont la quantité a augmenté, y compris ceux ajoutés automatiquement
{ event: 'add_to_cart', ecommerce: {
  currency: 'EUR',
  value: 59.5,                      // Σ prix × quantité
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2, index: 0 }]
} }

// begin_checkout et add_shipping_info : valeur nette du panier
{ 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',         // référence de commande
  currency: 'EUR',
  value: 59.5,                      // net, remises déduites, cautions exclues
  tax: 11.31,                       // TVA
  items: [{ item_id: 'a1b2c3', item_name: 'SUP Board', price: 29.75, quantity: 2 }]
} }
```

- `price` est le prix unitaire net après remise. Les lignes de caution ne figurent jamais dans `items`.
- `transaction_id` est la **référence de commande** (par ex. `SNLDX1`). Le serveur envoie la même valeur comme `transaction_id` GA4, `order_id` Google Ads et `order_id` Meta.
- Le dataLayer ne contient **ni** e-mail, **ni** téléphone, **ni** ID d’événement.

## gtag

Avec `enableGtagTracking`, le widget appelle `gtag('event', name, params)` avec les mêmes objets `ecommerce` que le dataLayer (les items de `purchase` portent aussi `index`).

Avec `gtagAdsSendTo`, l’achat déclenche en plus :

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

Avec `enableGtagUserDataTracking`, à l’enregistrement des données client :

```js
gtag('set', 'user_data', {
  email: 'jane@example.com',
  phone_number: '+4915112345678',   // uniquement si E.164 valide, sinon omis
  address: { first_name: 'Jane', last_name: 'Doe', city: 'Berlin', postal_code: '10115', country: 'DE' }
})
```

L’adresse de livraison est utilisée si le client en a saisi une, sinon l’adresse de facturation.

## Pixel Meta

```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                      // nombre de lignes du panier
}, { eventID: 'purchase_SNLDX1' })
```

- Les montants Meta sont des chaînes à deux décimales.
- `eventID` vaut `purchase_` + référence de commande, le même `event_id` que le Purchase de l’API Conversions côté serveur, si bien que Meta déduplique les deux.
- Avec `metaPixelId`, à l’enregistrement des données client : `fbq('init', metaPixelId, { em, fn, ln, ct, zp, country })` (valeurs en clair ; le pixel les hache).

## Umami

Avec `enableUmamiTracking` et le script Umami sur la 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 })
```

Seul `purchase` porte `revenue`. Les champs vides sont omis. Umami ne dépose aucun cookie ; c’est à vous de décider si vous demandez le consentement.

## Pixel OpenAI Ads

Avec `enableOpenAiTracking` et le pixel OpenAI (`oaiq`) sur la page :

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

Vous pouvez aussi laisser l’option désactivée et appeler `oaiq` vous-même dans `onCheckoutSuccess`.

## Callbacks

Les erreurs levées dans un callback sont interceptées et consignées dans la console, afin de ne pas bloquer la réservation.

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

`productId` est l’ID de l’article, `quantity` la quantité ajoutée.

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

`transactionTotal` est le **montant à payer** (brut, cautions comprises), et non la valeur nette du panier qu’utilisent les autres événements. `transactionId` est la référence de commande.

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

- `ecommerceData.purchase` : l’objet `purchase` du dataLayer (`transaction_id`, `currency`, `value`, `tax`, `items`).
- `fbqData` : les paramètres du Purchase Meta (sans `order_id`).
- `eventID` : `purchase_` + référence de commande.
- `price` : ancien objet `{ gross, net, vat }` en centimes, cautions comprises. Utilisez plutôt `ecommerceData`.

Exemple avec un pixel personnalisé :

```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. Ajoutez le snippet avec `enableDataLayer: true`, soit dans la page, soit comme balise GTM **HTML personnalisé** (Custom HTML) avec le déclencheur **Initialisation – Toutes les pages** (Initialization – All Pages).
2. Créez des déclencheurs **Événement personnalisé** (Custom Event) pour `add_to_cart`, `begin_checkout`, `add_shipping_info` et `purchase`.
3. Créez des **Variables de couche de données** (Data Layer Variables, version 2), par exemple `ecommerce.value`, `ecommerce.currency`, `ecommerce.transaction_id`, `ecommerce.items`.
4. Balises d’événement GA4 : nom d’événement `{{Event}}` ou un nom fixe, et « Envoyer des données d’e-commerce » avec la source de données « Couche de données ».
5. **Purchase Meta via GTM :** définissez l’ID d’événement sur `purchase_{{DLV - ecommerce.transaction_id}}`. Il correspond alors au Purchase côté serveur, et Meta déduplique les deux.

Si vous utilisez aussi le suivi côté serveur via Rentware pour la même propriété GA4, n’y envoyez pas en plus les événements du dataLayer : GA4 ne déduplique que `purchase` (par `transaction_id`), donc l’ajout au panier et les autres événements du tunnel seraient comptés deux fois.

## Consentement

Le widget lit le consentement pour ce qu’il envoie au **serveur Rentware** (qui décide de ce qui part vers GA4, Google Ads et Meta). Il ne conditionne pas vos propres balises.

- Il lit **Google Consent Mode v2** depuis `window.dataLayer` : les entrées `gtag('consent', 'default' | 'update', {…})` et les objets de style GTM comme `{ event: 'consent_update', ad_storage: 'granted', … }`. Il les rejoue dans l’ordre : un `update` ultérieur remplace les valeurs précédentes.
- Signaux : `ad_storage`, `analytics_storage`, `ad_user_data`, `ad_personalization`. Chacun vaut `denied` par défaut.
- Si la page ne contient aucune entrée Consent Mode, la requête est marquée « aucun signal de consentement » et le journal de suivi affiche `skipped: no consent signal`.
- Les entrées `default` propres à une région (avec `region`) ne peuvent pas être rapprochées de la localisation du visiteur dans le navigateur : seules leurs valeurs `denied` sont appliquées.
- **Le consentement tardif fonctionne :** le consentement est relu à chaque requête. Un visiteur qui accepte après le chargement du widget est couvert dès la requête suivante, à condition que votre bannière pousse un `update` Consent Mode dans `window.dataLayer`.
- Le widget ne lit que le dataLayer nommé `dataLayer` dans la même fenêtre. Un dataLayer renommé, ou un widget dans une iframe, signifie qu’il n’y a aucun signal de consentement.

Pour vérifier une page : acceptez la bannière, ouvrez la console et tapez `dataLayer`. Une entrée `consent` (ou un événement `consent_update`) doit être présente avant que le visiteur réserve.

## Identifiants de clic

- Le widget capture `gclid`, `gbraid` et `wbraid` depuis l’URL (`gclid` également depuis le paramètre d’association `_gl` de Google ou le cookie `_gcl_aw`) et `fbclid` depuis l’URL ou le cookie `_fbc` de Meta. L’URL l’emporte sur le cookie.
- La capture n’a lieu **qu’avec le consentement `ad_storage`**. Rien n’est lu dans les cookies ni enregistré avant cela. Si le consentement arrive plus tard alors que l’identifiant de clic est encore dans l’URL, il est capturé à ce moment-là.
- Les identifiants de clic sont conservés dans `sessionStorage` pour l’onglet (même onglet, même domaine, supprimés à la fermeture de l’onglet). Le premier clic l’emporte.
- Le widget les envoie avec ses requêtes au serveur Rentware, qui les utilise pour Google Ads et Meta. Ils ne sont pas ajoutés à vos événements navigateur.
- La page d’atterrissage et la page de réservation sont différentes ? Exécutez aussi le script du widget sur la page d’atterrissage, ou laissez votre balise Google (avec `url_passthrough`) ou votre pixel Meta écrire le cookie `_gcl_aw` / `_fbc`. `gbraid` et `wbraid` n’ont pas de solution de repli par cookie.

## Liste de contrôle pour les tests

1. Ajoutez `RTR_HOOKS`, ouvrez la page, acceptez la bannière.
2. Ajoutez un article au panier, puis tapez `dataLayer` dans la console : les dernières entrées sont `{ ecommerce: null }` et `{ event: 'add_to_cart', … }`.
3. Finalisez une réservation avec « réserver » ou un prépaiement et vérifiez l’entrée `purchase` ; son `transaction_id` est la référence de commande.
4. Annulez ensuite la réservation de test dans Rentware.
