Booking Widget Analytics

Event reference and integration examples for GTM, GA4, Meta Pixel, and other analytics tools.

Browser events Tenant isolated 8 sections
No matching sections found.

Booking Widget Analytics Events

The Resevu booking widget exposes browser-local lifecycle hooks that tenant websites can connect to their own Google Tag Manager, GA4, Meta Pixel, or other analytics setup.

The widget does not send analytics data to Resevu, Google, Meta, or another tenant automatically. It only dispatches CustomEvent objects on the window of the page where the widget is embedded. The host website decides whether and how to forward them.

Multi-Tenant Isolation

Every event includes the active tenant slug in event.detail.tenant.

Events are dispatched inside the tenant website's browser page. They do not travel to another website, another tenant, or another GTM container. Normal browser origin isolation also prevents one tenant website from reading another tenant website's window, dataLayer, or sessionStorage.

Always verify the tenant before forwarding an event:

const expectedTenant = 'your-tenant-slug';

window.addEventListener('resevu:booking-status', (event) => {
    if (event.detail?.tenant !== expectedTenant) {
        return;
    }

    // Forward the event to this tenant's analytics setup.
});

This check is recommended even when a page only embeds one Resevu widget. It prevents incorrect attribution if the page is misconfigured later.

Available Events

Browser event When it fires Additional detail fields
resevu:booking-opened The massage booking modal opens booking_type
resevu:booking-step The visible booking step changes step, booking_type
resevu:booking-checkout A booking was accepted and the customer is about to leave for payment Booking checkout payload below
resevu:booking-status The customer returns from payment with success, failed, or pending status plus the persisted booking checkout payload
resevu:voucher-opened The gift-voucher modal opens Optional value, currency
resevu:voucher-checkout A voucher order was accepted and the customer is about to leave for payment Voucher checkout payload below
resevu:voucher-status The customer returns from voucher payment with success, failed, or pending status plus the persisted voucher checkout payload

All events also include:

{
    tenant: 'your-tenant-slug'
}

Booking Checkout Payload

resevu:booking-checkout can contain:

{
    tenant: 'your-tenant-slug',
    event_id: 'stable-browser-event-id',
    booking_type: 'single', // or "duo"
    service_id: 123,
    service_name: 'Example treatment',
    second_service_id: 456, // duo only
    second_service_name: 'Second treatment', // duo only
    duration_minutes: 60,
    value: 75,
    deposit_value: 25,
    currency: 'EUR',
    payment_type: 'deposit' // depends on tenant payment configuration
}

value is the total booking value. deposit_value is the calculated deposit amount. Optional fields can be absent when they are not available.

Voucher Checkout Payload

resevu:voucher-checkout can contain:

{
    tenant: 'your-tenant-slug',
    event_id: 'stable-browser-event-id',
    value: 50,
    currency: 'EUR'
}

Payment Status

Status events contain one of:

success
failed
pending

The checkout payload is stored temporarily in sessionStorage under a tenant-specific key. This allows the widget to reuse the same event_id, value, currency, and service details after the payment redirect.

The final status event is emitted only when the matching checkout payload is still available in the same browser tab. This avoids treating a copied success URL or a later page refresh as a new conversion.

Google Tag Manager Example

The following bridge converts Resevu hooks into tenant-owned dataLayer events. It deliberately forwards only non-personal fields.

(() => {
    const expectedTenant = 'your-tenant-slug';

    const allowedKeys = [
        'tenant',
        'status',
        'step',
        'event_id',
        'booking_type',
        'service_id',
        'service_name',
        'second_service_id',
        'second_service_name',
        'duration_minutes',
        'value',
        'deposit_value',
        'currency',
        'payment_type'
    ];

    const cleanDetail = (detail) => {
        if (!detail || detail.tenant !== expectedTenant) {
            return null;
        }

        return allowedKeys.reduce((result, key) => {
            if (detail[key] !== undefined && detail[key] !== '') {
                result[key] = detail[key];
            }
            return result;
        }, {});
    };

    const push = (name, detail) => {
        const clean = cleanDetail(detail);
        if (!clean) return;

        window.dataLayer = window.dataLayer || [];
        window.dataLayer.push({ event: name, ...clean });
    };

    window.addEventListener('resevu:booking-checkout', (event) => {
        push('booking_payment_redirect', event.detail);
    });

    window.addEventListener('resevu:booking-status', (event) => {
        if (event.detail?.status === 'success') {
            push('booking_complete', event.detail);
        }
    });

    window.addEventListener('resevu:voucher-checkout', (event) => {
        push('voucher_checkout', event.detail);
    });

    window.addEventListener('resevu:voucher-status', (event) => {
        if (event.detail?.status === 'success') {
            push('purchase', event.detail);
        }
    });
})();

In GTM, create Custom Event triggers for the resulting event names. A typical mapping is:

dataLayer event GA4/advertising meaning
booking_payment_redirect Begin or initiate checkout
booking_complete Completed appointment or schedule conversion
voucher_checkout Begin or initiate voucher checkout
purchase Completed gift-voucher purchase

Use event_id for browser/server deduplication if the tenant later adds a server-side conversion integration.

The hooks are functional, local browser events. The tenant website remains responsible for analytics consent and tag configuration.

  • Do not forward names, email addresses, telephone numbers, notes, medical information, or other personal data.
  • Configure GTM tags to respect the tenant website's consent settings.
  • Do not initialize a tenant's GA4 or advertising pixel inside the shared Resevu widget.
  • Keep analytics destinations and credentials in the tenant website or its own tag manager.
  • Treat service names, values, and identifiers according to the tenant's privacy policy and applicable law.

Debugging

Run this in the tenant website's browser console before opening the widget:

[
    'booking-opened',
    'booking-step',
    'booking-checkout',
    'booking-status',
    'voucher-opened',
    'voucher-checkout',
    'voucher-status'
].forEach((name) => {
    window.addEventListener(`resevu:${name}`, (event) => {
        console.log(`resevu:${name}`, event.detail);
    });
});

Verify the following:

  1. event.detail.tenant matches the website's tenant slug.
  2. No personal form fields appear in the payload.
  3. Checkout and final status use the same event_id.
  4. A successful booking produces one completed-booking conversion.
  5. A successful voucher payment produces one purchase conversion.
  6. Refreshing a success URL does not create another conversion.

Payment tests should use the payment provider's test mode whenever available.