# Checkout Events & Callbacks (/developers/checkout-events)



`ResytechClient` emits events at each stage of the booking flow. Use `on()` and `off()` to subscribe.

Events [#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:close`    | `undefined`                        | The modal has been closed                      |

checkout:open [#checkoutopen]

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.

```javascript
client.on('checkout:open', (data) => {
  console.log('Booking started for activity:', data.activity);
  console.log('Pre-selected date:', data.date);
});
```

Payload [#payload]

```typescript
{
  location: string;       // Location UUID (always present)
  activity?: string;      // Activity UUID (if pre-selected)
  equipment?: string;     // Equipment UUID (if pre-selected)
  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)
}
```

checkout:ready [#checkoutready]

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).

```javascript
client.on('checkout:ready', () => {
  console.log('Booking UI is loaded and visible');
});
```

checkout:complete [#checkoutcomplete]

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.

```javascript
client.on('checkout:complete', (data) => {
  console.log('Confirmation:', data.confirmationCode);
  console.log('Total price:', data.price);
});
```

Payload [#payload-1]

```typescript
{
  confirmationCode: string; // Booking confirmation code
  price: number;            // Total price charged
  // ...additional fields from the booking iframe
}
```

checkout:close [#checkoutclose]

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.

```javascript
client.on('checkout:close', () => {
  console.log('Modal closed');
});
```

Registering and Removing Listeners [#registering-and-removing-listeners]

```javascript
// 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:

```javascript
client.on('checkout:complete', trackConversion);
client.on('checkout:complete', showConfirmationBanner);
client.on('checkout:complete', sendAnalyticsEvent);
```

PostMessage Communication [#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             |

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.

Practical Examples [#practical-examples]

Conversion Tracking with Google Analytics [#conversion-tracking-with-google-analytics]

```javascript
client.on('checkout:complete', (data) => {
  gtag('event', 'purchase', {
    transaction_id: data.confirmationCode,
    value: data.price,
    currency: 'USD'
  });
});
```

Facebook Pixel [#facebook-pixel]

```javascript
client.on('checkout:open', () => {
  fbq('track', 'InitiateCheckout');
});

client.on('checkout:complete', (data) => {
  fbq('track', 'Purchase', {
    value: data.price,
    currency: 'USD'
  });
});
```

Redirect After Booking [#redirect-after-booking]

```javascript
client.on('checkout:complete', (data) => {
  window.location.href = `/confirmation?code=${data.confirmationCode}`;
});
```

Show a Confirmation Banner [#show-a-confirmation-banner]

```javascript
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) [#log-all-events-debugging]

```javascript
['checkout:open', 'checkout:ready', 'checkout:complete', 'checkout:close'].forEach(event => {
  client.on(event, (data) => {
    console.log(`[Resytech] ${event}`, data || '');
  });
});
```

Error Handling [#error-handling]

If the iframe fails to load or an error occurs during PostMessage handling:

* With `debug: true`, errors are logged to `console.error`
* With `debug: false` (default), errors are silently caught
* The `checkout:ready` event 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:ready` event timing combined with your own timeout logic.
