Événements
const off = widget.on("reservation:confirmed", ({ reservation }) => { analytics.track("reservation", { id: reservation.publicId });});
off(); // arrêter d'écouteron() 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.
Le catalogue
Section intitulée « Le catalogue »| Événement | Charge utile | Quand |
|---|---|---|
ready | — | L’instance est montée et ses méthodes sont actives. |
opened | — | Le panneau est devenu visible. popover / sticky seulement. |
closed | — | Le 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. |
ready se rejoue
Section intitulée « ready se rejoue »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 secondeawait widget.ready; // équivalent, si vous préférez les promessesstep:changed n’annonce pas la première étape
Section intitulée « step:changed n’annonce pas la première étape »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ée | Parce que |
|---|---|---|
popover / sticky | opened | Il se déclenche sur tous les chemins qui ouvrent le widget. |
inline | ready | Un 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 popoverwidget.on("step:changed", ({ step: next }) => analytics.track("funnel", { step: (step = next) }));« créée » et « confirmée »
Section intitulée « « créée » et « confirmée » »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.
La charge utile
Section intitulée « La charge utile »{ 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.
Les coordonnées sont exclues
Section intitulée « Les coordonnées sont exclues »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.
Analytique
Section intitulée « Analytique »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.
Google Tag Manager
Section intitulée « Google Tag Manager »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.
Le désactiver
Section intitulée « Le désactiver »<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.