Checkout Events & Callbacks
Listen to booking lifecycle events — track conversions, redirect after checkout, and integrate analytics.
ResytechClient emits events at each stage of the booking flow. Use on() and off() to subscribe.
Events
| Event | Payload | When |
|---|---|---|
checkout:open | CheckoutRequest | The booking modal has opened |
checkout:ready | undefined | The iframe has loaded and is ready to interact |
checkout:complete | { confirmationCode, price, ... } | The customer completed a booking |
checkout:return | { returnUrl } | The customer clicked the post-purchase Continue button. Informational only -- the iframe navigates the top frame to returnUrl regardless |
checkout:close | undefined | The modal has been closed |
giftcard:complete | { giftCardCode, amount } | A gift card purchase succeeded |
giftcard:close | undefined | The customer dismissed the gift card success view |
membership:complete | { membershipNumber, planName } | A membership purchase succeeded |
membership:close | undefined | The customer dismissed the membership success view |
checkout:open
Fires immediately when showUI() is called. The payload is the CheckoutRequest object passed to showUI(), including any pre-fill options and the location from config.
client.on('checkout:open', (data) => {
console.log('Booking started for activity:', data.activity);
console.log('Pre-selected date:', data.date);
});Payload
{
location: string; // Location UUID (always present)
activity?: string; // Activity UUID (if pre-selected)
equipment?: string | EquipmentSelection[]; // Equipment selections, or a single equipment UUID as shorthand (quantity 1, no add-ons)
date?: string; // Date (if pre-selected)
durationMins?: number; // Duration in minutes (if pre-selected)
timeSlotStart?: string; // Time slot start (if pre-selected)
timeSlotEnd?: string; // Time slot end (if pre-selected)
timeSlotPrice?: number; // Price (if pre-selected)
}
interface EquipmentSelection {
equipmentUuid: string;
quantity: number;
addons?: AddonSelection[];
}
interface AddonSelection {
addonUuid: string;
quantity: number;
}checkout:ready
Fires when the iframe has loaded and the booking UI is visible. This event has no payload.
The iframe sends a checkout-ready PostMessage when it is fully initialized. If no message is received within 3 seconds, the iframe is shown anyway (timeout fallback).
client.on('checkout:ready', () => {
console.log('Booking UI is loaded and visible');
});checkout:complete
Fires when the customer successfully completes a booking. The payload includes the confirmation code and price, along with any additional data sent by the booking iframe.
client.on('checkout:complete', (data) => {
console.log('Confirmation:', data.confirmationCode);
console.log('Total price:', data.price);
});Payload
{
confirmationCode: string; // Booking confirmation code
price: number; // Total price charged
// ...additional fields from the booking iframe
}checkout:return
Fires when the customer clicks the Continue button on the confirmation page (only present when a returnUrl is configured for the checkout). The iframe navigates the top frame to the URL regardless — this event is informational so you can run analytics, conversion pixels, or cleanup synchronously before the page is replaced.
client.on('checkout:return', (data) => {
console.log('Customer is being sent to:', data.returnUrl);
// fire pixels, etc. — you can't cancel the navigation
});Payload
{
returnUrl: string; // The URL the top frame is about to navigate to
}checkout:close
Fires when the modal is closed, either by the user or by calling client.close(). This event has no payload. Fires regardless of whether a booking was completed.
client.on('checkout:close', () => {
console.log('Modal closed');
});giftcard:complete
Fires when a gift card purchase succeeds. Mutually exclusive with checkout:complete — gift card flows do not produce a booking confirmation.
client.on('giftcard:complete', (data) => {
console.log('Gift card code:', data.giftCardCode);
console.log('Amount:', data.amount);
});giftcard:close
Fires when the customer dismisses the gift card success view. No payload.
membership:complete
Fires when a membership purchase succeeds.
client.on('membership:complete', (data) => {
console.log('Membership #:', data.membershipNumber);
console.log('Plan:', data.planName);
});membership:close
Fires when the customer dismisses the membership success view. No payload.
Registering and Removing Listeners
// Register
client.on('checkout:complete', handleComplete);
// Remove (must use the same function reference)
client.off('checkout:complete', handleComplete);You can register multiple listeners for the same event:
client.on('checkout:complete', trackConversion);
client.on('checkout:complete', showConfirmationBanner);
client.on('checkout:complete', sendAnalyticsEvent);PostMessage Communication
The booking iframe communicates with the parent window using window.postMessage. The library handles this internally -- you do not need to work with PostMessage directly.
The iframe sends these message types:
| Message Type | Maps To Event | Description |
|---|---|---|
checkout-ready | checkout:ready | Iframe is loaded and ready |
checkout-close | checkout:close | User closed the booking from inside the iframe |
checkout-complete | checkout:complete | Booking was successfully completed |
checkout-return | checkout:return | User clicked the post-purchase Continue button |
gift-card-purchase-complete | giftcard:complete | Gift card purchase succeeded |
gift-card-close | giftcard:close | User dismissed the gift card success view |
membership-purchase-complete | membership:complete | Membership purchase succeeded |
membership-purchase-close | membership:close | User dismissed the membership success view |
Messages are parsed as JSON. Non-JSON messages and messages without a type field are silently ignored. When debug mode is enabled, all received messages are logged to the console.
checkout-close reason field
checkout-close may include an optional reason field that describes how the user dismissed the modal:
| Reason | Source | Gated by config |
|---|---|---|
| (none) | The X button in the header, or completion redirect | Always closes |
'escape' | The user pressed Escape inside the iframe | closeOnEscape |
'backdrop' | The user clicked outside the booking card | closeOnBackdropClick |
When the booking UI sends a checkout-close with reason: 'escape' or reason: 'backdrop', the lib only actually closes the modal if the corresponding config option is true (the default). Reason-less closes (the X button, post-checkout dismissal) always close.
Practical Examples
Conversion Tracking with Google Analytics
client.on('checkout:complete', (data) => {
gtag('event', 'purchase', {
transaction_id: data.confirmationCode,
value: data.price,
currency: 'USD'
});
});Facebook Pixel
client.on('checkout:open', () => {
fbq('track', 'InitiateCheckout');
});
client.on('checkout:complete', (data) => {
fbq('track', 'Purchase', {
value: data.price,
currency: 'USD'
});
});Redirect After Booking
client.on('checkout:complete', (data) => {
window.location.href = `/confirmation?code=${data.confirmationCode}`;
});Show a Confirmation Banner
client.on('checkout:complete', (data) => {
const banner = document.createElement('div');
banner.className = 'success-banner';
banner.textContent = `Booking confirmed! Your code: ${data.confirmationCode}`;
document.body.prepend(banner);
});
client.on('checkout:close', () => {
// Refresh availability after the modal closes
refreshCalendar();
});Log All Events (Debugging)
[
'checkout:open', 'checkout:ready', 'checkout:complete', 'checkout:return', 'checkout:close',
'giftcard:complete', 'giftcard:close',
'membership:complete', 'membership:close',
].forEach(event => {
client.on(event, (data) => {
console.log(`[Resytech] ${event}`, data || '');
});
});Error Handling
If the iframe fails to load or an error occurs during PostMessage handling:
- With
debug: true, errors are logged toconsole.error - With
debug: false(default), errors are silently caught - The
checkout:readyevent fires after a 3-second timeout even if the iframe does not send a ready message, so the user is never stuck on a spinner indefinitely - There is no dedicated error event. If you need to detect load failures, use the
checkout:readyevent timing combined with your own timeout logic.
