Aller au contenu

Événements

const off = widget.on("reservation:confirmed", ({ reservation }) => {
analytics.track("reservation", { id: reservation.publicId });
});
off(); // arrêter d'écouter

on() renvoie sa propre fonction de désabonnement. Appelez-la lorsque votre composant est démonté ; rien d’autre ne fera le ménage pour vous.

ÉvénementCharge utileQuand
readyL’instance est montée et ses méthodes sont actives.
openedLe panneau est devenu visible. popover / sticky seulement.
closedLe panneau a été masqué. popover / sticky seulement.
reservation:created{ reservation }Une réservation existe côté serveur — quelle qu’en soit l’issue.
reservation:confirmed{ reservation }La réservation est réellement définitive.
step:changed{ step }Le client a changé d’étape — transitions seulement.

S’abonner à ready après que le widget est monté appelle votre gestionnaire une fois, immédiatement — comme promise.then() sur une promesse déjà résolue. Vous n’avez donc jamais de course à gérer :

widget.on("ready", () => {}); // se déclenche même si le widget est monté depuis une seconde
await widget.ready; // équivalent, si vous préférez les promesses

step:changed se déclenche sur les transitions. Il n’existe aucun événement pour l’étape sur laquelle le parcours s’ouvre, et c’est délibéré : en mode popover et sticky, le widget construit son arbre de réservation au chargement de la page et se contente de le masquer. Un événement à ce moment-là se déclencherait donc à chaque page vue, et non à chaque entrée dans le tunnel.

Prenez l’entrée sur l’événement qui la signifie déjà :

ModeÉvénement d’entréeParce que
popover / stickyopenedIl se déclenche sur tous les chemins qui ouvrent le widget.
inlinereadyUn widget en ligne est visible dès qu’il est monté.

Les étapes rapportées sont availability, details, payment et confirmation. payment n’existe que si la réservation exige une empreinte bancaire : un tunnel construit sur ces événements doit donc la traiter comme facultative.

Revenir en arrière est une transition et est rapporté. Commencer une seconde réservation depuis l’écran de confirmation aussi : le parcours revient à availability, et un tunnel qui le masquerait sous-compterait les réservations répétées.

let step = null;
widget.on("opened", () => (step = "availability")); // l'entrée, pour un popover
widget.on("step:changed", ({ step: next }) => analytics.track("funnel", { step: (step = next) }));

reservation:created se déclenche pour toute réservation qui atteint une issue. Cela inclut les réservations en attente de validation par le restaurant et celles qui retiennent une table dans l’attente d’une empreinte bancaire. La réservation existe ; le client ne détient pas nécessairement de table.

reservation:confirmed ne se déclenche que lorsque le client détient une table confirmée.

Les deux sont des événements distincts afin que votre site ne puisse pas lire « créée » comme « réservée ». Envoyer un message de confirmation sur reservation:created finira par notifier un client qui détient une option non réglée plutôt qu’une table.

Utilisez reservation:created pour enregistrer que le tunnel est allé au bout. Utilisez reservation:confirmed pour confirmer la réservation au client.

{
publicId: string; // la clé de jointure
status: string; // « confirmed », « pending », …
date: string; // « 2026-09-03 »
startTime: string; // « 19:30 »
partySize: number;
sectionKey: string | null; // null = salle indifférente
}

status est le statut de la réservation au moment de cet événement, ce qui explique qu’une charge utile created puisse légitimement indiquer pending.

La charge utile ne contient ni nom, ni e-mail, ni téléphone. Votre site détient déjà ces informations : c’est lui qui a envoyé le client, et lorsqu’un jeton a été émis, c’est lui qui a attesté l’identité.

publicId est la clé de jointure. Résolvez-la via l’API ou retrouvez-la dans un webhook, qui s’exécutent tous deux côté serveur. Un événement navigateur est une notification plutôt qu’une source de vérité — il est délivré dans un contexte que le client peut inspecter et modifier — les décisions engageant de l’argent ou un accès doivent donc être prises à partir de l’enregistrement côté serveur.

Le widget n’écrit rien dans les variables globales de votre page. Aucun dataLayer alimenté, aucun appel gtag, aucun compteur tenu pour vous : un gestionnaire de balises ne voit donc le tunnel qu’à travers un gestionnaire d’événement que vous écrivez :

widget.on("reservation:confirmed", ({ reservation }) => {
window.dataLayer?.push({ event: "reservation_confirmed", id: reservation.publicId });
});

Un lien profond n’a pas de page porteuse, donc pas de gestionnaire : le client est sur book.useservice.app, et la réservation n’est pas visible du tout par l’analytique de votre site. Comptez-les depuis l’API ou un webhook.

const widget = ServiceWidget.create({ slug: "chez-marie", mode: "popover" });
// Le tunnel est allé au bout — la réservation existe sous une forme ou une autre.
widget.on("reservation:created", ({ reservation }) => {
analytics.track("reservation_demarree", { id: reservation.publicId });
});
// Le client a réellement une table. On peut le lui annoncer.
widget.on("reservation:confirmed", ({ reservation }) => {
afficherRemerciement(reservation);
});

Pour rattacher la réservation à un séjour, une facture ou une fiche CRM, utilisez le webhook plutôt que l’événement navigateur. Le webhook porte l’enregistrement de la réservation et est délivré que le client ait fermé l’onglet ou non.

Chaque événement ci-dessus est également poussé dans window.dataLayer : un site équipé de GTM mesure donc le tunnel de réservation sans écrire de JavaScript :

{
event: "service_widget_reservation_created",
service_widget: {
slug: "chez-marie",
event: "reservation:created",
reservation: { publicId: "resv_…", status: "confirmed", date: "2026-09-03", startTime: "19:30", partySize: 4, sectionKey: null }
}
}

Le nom event reprend celui de l’événement du widget, deux-points remplacés par un souligné — service_widget_ready, service_widget_opened, service_widget_closed, service_widget_step_changed, service_widget_reservation_created, service_widget_reservation_confirmed. Utilisez-le comme nom de déclencheur « Événement personnalisé » dans GTM.

step:changed ajoute service_widget.step ; les deux événements de réservation ajoutent service_widget.reservation. Les autres ne portent que slug et event.

Le widget pousse un objet, et s’arrête là. Il ne charge aucun outil de mesure, ne dépose aucun cookie et n’émet aucune requête propre. Ce qui quitte le navigateur est décidé par votre conteneur GTM et par votre plateforme de consentement — l’endroit correct pour cette décision, puisqu’il s’agit de votre page et de votre relation de consentement.

Les charges utiles ne contiennent aucune donnée de contact : nom, e-mail et téléphone sont délibérément absents de tous les événements. Ce qui parvient à votre dataLayer est un identifiant de réservation et sa forme.

Si GTM n’est pas encore chargé au montage du widget, les événements arrivent quand même : le widget crée window.dataLayer de la même manière que le fait le snippet de GTM, qui traite ensuite la file déjà constituée.

<script src="…/widget.js" data-slug="chez-marie" data-analytics="off" async></script>
ServiceWidget.create({ slug: "chez-marie", analytics: false });

Seule la chaîne exacte off le désactive sur la balise de script. Toute autre valeur laisse le flux actif.