Booking engine integration

5 minutes

One script tag, four data attributes, one callback. The widget handles the quote, the hold, and the UI chip. Your server confirms (or releases) when the booking succeeds (or fails).

Drop-in snippet
Paste before </body> on the booking page.
<!-- 1. Drop this on the booking engine page (just before </body>) -->
<script
  src="https://www.guestmaker.ai/loyalty-widget/credit-widget.v1.js"
  data-tenant="${YOUR_GUESTMAKER_API_KEY}"
  data-email="${memberEmail}"
  data-amount="${grandTotal}"
  data-currency="EUR"
  data-channel="direct"
  data-on-apply="onLoyaltyCreditApply"
  async
></script>
<div id="gm-loyalty-credit"></div>

<!-- 2. Listen for the apply callback -->
<script>
  window.onLoyaltyCreditApply = function (payload) {
    // payload = {
    //   hold_id: string,
    //   points: number,
    //   amount: number,         // in your booking currency
    //   currency: 'EUR',
    //   expires_at: string,     // ISO 8601
    //   external_reference_id: string,
    // }
    applyDiscountToCart(payload.amount);
    pendingHoldId = payload.hold_id;
  };
</script>
Data attributes
All four are required.
data-tenant

Your GuestMaker API key with the `guests:write` scope. Create one at /developers and paste the full `gm_*` key here.

data-email

Verified member email address from the booking session. The widget calls /credit/quote with this to look up the spendable balance.

data-amount

Grand total of the booking in major units (e.g. 320 for €320). Used by the widget to compute the maximum applicable cash credit.

data-currency

ISO 4217 currency code (default `EUR`).

data-channel

Booking channel: `direct` / `website` / `phone` / `walk_in`. Must be in the tenant's allowed_channels (guardrail #632 blocks OTA/TTOO regardless).

data-on-apply

Name of the global window function that will receive the apply payload (default `onLoyaltyCreditApply`).

Callback signature
Define window.onLoyaltyCreditApply before the script loads.
interface LoyaltyCreditApplyPayload {
  hold_id: string;
  points: number;
  amount_value: number;         // major units, matches data-currency
  currency: string;             // ISO 4217
  expires_at: string;           // ISO 8601 timestamp
  external_reference_id: string;
}

declare global {
  interface Window {
    onLoyaltyCreditApply?: (payload: LoyaltyCreditApplyPayload) => void;
  }
}
Worked example — confirm on booking success
The widget only creates the hold. Your backend is responsible for confirming once the booking is committed, or releasing if it fails.
// Server-side: when the booking engine commits the booking,
// confirm the held points so the discount is locked in.

const res = await fetch('https://guestmaker.ai/api/v1/loyalty/credit/confirm', {
  method: 'POST',
  headers: {
    'Authorization': 'Bearer ' + process.env.GUESTMAKER_API_KEY,
    'Content-Type': 'application/json',
  },
  body: JSON.stringify({
    external_reference_id: cartId,   // same value used in the widget
    reservation_id: createdReservationId,
  }),
});

if (!res.ok) {
  // Release the hold so spendable returns to the member.
  await fetch('https://guestmaker.ai/api/v1/loyalty/credit/release', {
    method: 'POST',
    headers: {
      'Authorization': 'Bearer ' + process.env.GUESTMAKER_API_KEY,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({ external_reference_id: cartId }),
  });
  throw new Error('Cash credit confirm failed — booking rolled back');
}

Pass the same external_reference_id on confirm as you did on hold. Idempotency is anchored on that value (guardrail #630), so a retry of either call is safe.