Last active
December 10, 2025 19:32
-
-
Save cbarley10/64ebafb5c8043ef5b2c8cb61145d9f5e to your computer and use it in GitHub Desktop.
Frontend Events for Guesty Integration
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <script async type="text/javascript" src="https://static.klaviyo.com/onsite/js/klaviyo.js?company_id=COMPANY_ID_HERE"></script> | |
| <script> | |
| (function () { | |
| const originalFetch = window.fetch; | |
| const originalXHROpen = XMLHttpRequest.prototype.open; | |
| const FIELDS = "_id+title+nickname+type+roomType+propertyType+accommodates+amenities+bathrooms+bedrooms+beds+bedType+timezone+defaultCheckInTime+defaultCheckOutTime+address+picture+pictures+prices+publicDescription+terms+taxes+reviews+tags+parentId" | |
| function getCurrentPageURL() { | |
| return window.location.pathname; | |
| } | |
| function isValidEmail(email) { | |
| const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; | |
| return emailRegex.test(email); | |
| } | |
| function isValidPhone(phone) { | |
| const phoneRegex = /^\+?[0-9]{10,15}$/; | |
| return phoneRegex.test(phone); | |
| } | |
| function trackViewedListingOrCheckout(eventName, responseData, value, additionalFields) { | |
| let listingData = { | |
| "Title": responseData.title, | |
| "ID": responseData._id, | |
| "Tags": responseData.tags, | |
| "ImageURL": responseData.picture?.thumbnail || "", | |
| "Property Type": responseData.propertyType, | |
| "URL": window.location.href, | |
| "Listing City": responseData.address?.city || "", | |
| "Listing Country": responseData.address?.country || "", | |
| "Price": responseData.prices?.basePrice, | |
| "Amenities": responseData.amenities, | |
| "Listing Timezone": responseData.timezone, | |
| "$extra": { | |
| "prices": responseData.prices, | |
| "reviews": responseData.reviews, | |
| "taxes": responseData.taxes, | |
| "images": responseData.pictures, | |
| "bedrooms": responseData.bedrooms, | |
| "bathrooms": responseData.bathrooms | |
| } | |
| }; | |
| if (additionalFields && Object.keys(additionalFields).length) { | |
| Object.assign(listingData, additionalFields) | |
| } | |
| if (value) { | |
| listingData["$value"] = value; | |
| } | |
| klaviyo.isIdentified().then(res => { | |
| if (res) { | |
| console.log(`Tracking Klaviyo Event - ${eventName}: `, listingData); | |
| } else { | |
| console.log(`Klaviyo Event - ${eventName} - tracked to local storage, user is not identified yet`); | |
| } | |
| }); | |
| klaviyo.track(`${eventName}`, listingData).then(res => { | |
| klaviyo.isIdentified().then(result => { | |
| if (result) { | |
| console.log(`Klaviyo Event - ${eventName} - Success: ${res}`); | |
| } | |
| }); | |
| }); | |
| } | |
| let checkoutTracked = false; | |
| let quoteResponseData = null; | |
| let totalValue = null; | |
| let additionalFields = {}; | |
| function trackStartedCheckoutOnce() { | |
| if (checkoutTracked || !quoteResponseData) return; | |
| checkoutTracked = true; | |
| trackViewedListingOrCheckout("Started Checkout", quoteResponseData, totalValue, additionalFields); | |
| } | |
| function setupCheckoutIdentifyListeners() { | |
| let emailField = document.querySelector("input[name='email']"); | |
| let phoneNumber = document.querySelector("input[name='phone']"); | |
| let firstName = document.querySelector("input[name='firstName']"); | |
| let lastName = document.querySelector("input[name='lastName']"); | |
| function handleUserInput() { | |
| const user = { | |
| "email": emailField?.value.trim() || "", | |
| "phone_number": phoneNumber?.value || "", | |
| "first_name": firstName?.value || "", | |
| "last_name": lastName?.value || "", | |
| }; | |
| const validEmail = isValidEmail(user.email); | |
| const validPhone = isValidPhone(user.phone_number); | |
| if (validEmail || validPhone) { | |
| klaviyo.identify(user).then(() => { | |
| console.log("Identified Klaviyo User!"); | |
| trackStartedCheckoutOnce(); | |
| }); | |
| } else { | |
| console.log("Neither valid email nor phone entered yet", { validEmail, validPhone }); | |
| } | |
| } | |
| phoneNumber?.addEventListener("blur", handleUserInput); | |
| emailField?.addEventListener("blur", handleUserInput); | |
| } | |
| window.fetch = async function (...args) { | |
| const response = await originalFetch(...args); | |
| const clonedResponse = response.clone(); | |
| const url = typeof args[0] === "string" ? args[0] : args[0]?.url; | |
| if (url?.includes('/api/pm-websites-backend/listings/') && url?.includes("?fields") && !getCurrentPageURL().includes("/checkout")) { | |
| clonedResponse.text().then((data) => { | |
| trackViewedListingOrCheckout("Viewed Listing", JSON.parse(data)); | |
| }); | |
| } | |
| if (url?.includes('/api/pm-websites-backend/reservations/quotes') && getCurrentPageURL().includes("/checkout")) { | |
| clonedResponse.text().then(async (data) => { | |
| let parsedData = JSON.parse(data); | |
| totalValue = parsedData.rates.ratePlans[0].ratePlan.money.fareAccommodationAdjusted; | |
| additionalFields = { | |
| "CheckIn": parsedData.checkInDateLocalized, | |
| "CheckOut": parsedData.checkOutDateLocalized, | |
| "Number of Guests": parsedData.guestsCount, | |
| "Guest Details": parsedData.numberOfGuests, | |
| "CheckIn Date and Time": parsedData.stay[0].eta, | |
| "CheckOut Date and Time": parsedData.stay[0].etd | |
| } | |
| let quoteResponse = await fetch(`https://app.guesty.com/api/pm-websites-backend/listings/${parsedData.unitTypeId}?fields=${FIELDS}&capture=bev2`); | |
| quoteResponseData = await quoteResponse.json(); | |
| klaviyo.isIdentified().then(res => { | |
| if (res) { | |
| trackStartedCheckoutOnce(); | |
| } else { | |
| setupCheckoutIdentifyListeners(); | |
| } | |
| }); | |
| }); | |
| } | |
| return response; | |
| }; | |
| XMLHttpRequest.prototype.open = function (method, url, ...rest) { | |
| this.addEventListener('load', function () { | |
| if (url.includes('/api/pm-websites-backend/listings/') && url.includes('?fields') && !getCurrentPageURL().includes("/checkout")) { | |
| try { | |
| let data = JSON.parse(this.responseText); | |
| trackViewedListingOrCheckout("Viewed Listing", data); | |
| } catch (e) { | |
| console.error("Error parsing JSON response: ", e); | |
| } | |
| } | |
| if (url.includes('/api/pm-websites-backend/reservations/quotes') && getCurrentPageURL().includes("/checkout")) { | |
| try { | |
| let data = JSON.parse(this.responseText); | |
| totalValue = data.rates.ratePlans[0].ratePlan.money.fareAccommodationAdjusted; | |
| additionalFields = { | |
| "CheckIn": data.checkInDateLocalized, | |
| "CheckOut": data.checkOutDateLocalized, | |
| "Number of Guests": data.guestsCount, | |
| "Guest Details": data.numberOfGuests, | |
| "CheckIn Date and Time": data.stay[0].eta, | |
| "CheckOut Date and Time": data.stay[0].etd | |
| } | |
| let xhr = new XMLHttpRequest(); | |
| xhr.open("GET", `https://app.guesty.com/api/pm-websites-backend/listings/${data.unitTypeId}?fields=${FIELDS}&capture=bev2`, true); | |
| xhr.onreadystatechange = function () { | |
| if (xhr.readyState === 4 && xhr.status === 200) { | |
| quoteResponseData = JSON.parse(xhr.responseText); | |
| klaviyo.isIdentified().then(res => { | |
| if (res) { | |
| trackStartedCheckoutOnce(); | |
| } else { | |
| setupCheckoutIdentifyListeners(); | |
| } | |
| }); | |
| } | |
| }; | |
| xhr.send(); | |
| } catch (e) { | |
| console.error("Error parsing JSON response: ", e); | |
| } | |
| } | |
| }); | |
| return originalXHROpen.apply(this, [method, url, ...rest]); | |
| }; | |
| })(); | |
| </script> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment