Prerequisites:
- Your Zenoti webstore must have your Google Tag Manager container connected. If you don't see the option, ask your Zenoti representative to enable it.
- Share your webstore's address (e.g.
yourbrand.zenoti.com) with your ShopMy account manager so it can be registered to your brand account. Orders will not track without this. - If your website uses a Content-Security-Policy, make sure it allows
*.shopmy.us.
Step One: Add click tracking to the marketing website
If adding directly onto the website, place it just before the closing </body> tag. Make sure to replace [domain] on the script with the URL domain of the marketing website. If adding through Google Tag Manager then add this tag to trigger on every page view of the website.
There are 2 options, use Option 1 if the webstore is on a domain similar to the marketing website. So for example if the marketing website is myspa.com and the webstore is something like booking.myspa.com then use Option 1. If the webstore is hosted on Zenoti's domain (e.g. myspa.zenoti.com) then use Option 2.
Option 1:
<script
async
src="https://static.shopmy.us/Affiliates/sms_aff_clicktrack.js?domain=[domain]&expires=30"
></script>Option 2:
<script
async
src="https://static.shopmy.us/Affiliates/sms_aff_clicktrack.js?domain=[domain]&expires=30&decoratehosts=zenoti.com"
></script>Step 2: Add the click tracker to the Zenoti webstore in GTM
- In Google Tag Manager, open the container connected to your Zenoti webstore
- Title the tag "ShopMy Click Tracking"
- Set the Tag Type to Custom HTML
- Set the HTML to the script provided below.
- Set the Triggering field to fire on a "Page View" on "All Pages"
- Press Save
<script
async
src="https://static.shopmy.us/Affiliates/sms_aff_clicktrack.js?expires=30"
></script>Step 3: Add the order confirmation script
- Create a new Custom HTML tag in the webstore's tag manager to trigger on order confirmation.
- Paste the provided code below without editing it.
- Set the tag to trigger on the webstore's order/booking confirmation page.
- Press Save
<script>
/*
Zenoti Webstore order confirmation pixel
*/
(function() {
try {
var cookies = {};
var cookiesList = document.cookie.split(';');
for (var i = 0; i < cookiesList.length; i++) {
if (cookiesList[i]) {
cookies[(cookiesList[i].split('=')[0] || '').trim()] = (cookiesList[i].split('=')[1] || '').trim();
}
}
var layer = window.dataLayer || [];
var purchasePayload = null;
var actionField = null;
var ecommerceCurrency = null;
for (var j = 0; j < layer.length; j++) {
var entry = layer[j];
if (!entry) continue;
// gtag-style push: ['event', 'purchase', payload]. Zenoti pushes Arguments objects, which can
// survive re-serialization as plain objects with numeric keys — match by index, not length.
if (entry[0] === 'event' && entry[1] === 'purchase' && entry[2] && typeof entry[2] === 'object') {
purchasePayload = entry[2];
}
// UA enhanced-ecommerce push: { ecommerce: { purchase: { actionField: {...} } } }
if (entry.ecommerce && entry.ecommerce.purchase && entry.ecommerce.purchase.actionField) {
actionField = entry.ecommerce.purchase.actionField;
ecommerceCurrency = entry.ecommerce.currencyCode;
}
}
var orderId, orderAmount, currency, isReturningCustomer;
if (purchasePayload) {
orderId = purchasePayload.transaction_id;
orderAmount = purchasePayload.value;
currency = purchasePayload.currency;
if (typeof purchasePayload.is_new_guest !== 'undefined') {
isReturningCustomer = !purchasePayload.is_new_guest;
}
}
if (!orderId && actionField) {
orderId = actionField.id;
orderAmount = actionField.revenue;
currency = actionField.currency || ecommerceCurrency;
}
if (typeof isReturningCustomer === 'undefined') isReturningCustomer = false;
if (!orderId || orderAmount == null || !currency) {
console.log('ShopMy: No order id or amount');
return;
}
fetch('https://api.shopmy.us/api/order_confirmation', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
orderAmount: orderAmount,
orderId: orderId,
clickId: cookies.sms_click_id,
currency: currency,
is_returning_customer: isReturningCustomer,
page_url: window.location.href
})
})
.then(function(r) {
if (!r.ok) throw new Error('Invalid order request sent');
return r.json();
})
.then(function() {
var now = new Date();
document.cookie = 'sms_click_id=;expires=' + now.toUTCString() + ';path=/;';
document.cookie = 'sms_click_time=;expires=' + now.toUTCString() + ';path=/;';
})
.catch(function() {
// ignore the error
});
} catch (error) {
console.log('ShopMy: We had an issue with the SMS checkout', error);
}
})();
</script>Step 4: Publish changes
Whenever making changes to the Google Tag Manager scripts, make sure to press Publish at the top-right of the page.
Step 5: Create the Invoice Closed webhook in Zenoti
The webhook lets Zenoti notify ShopMy when an invoice is paid and closed, so each commission settles at the final invoiced amount and refunded invoices cancel their commission automatically.
- Get your webhook URL from your ShopMy onboarding contact. The URL is unique to your brand account.
- Have your developer key ready. This is provided during onboarding, and you can also find it anytime in your ShopMy account under Settings → Developer Key — press Load to reveal it. Treat this key like a password.
- In Zenoti, create a new webhook (your Zenoti representative can help if you don't have access to webhook configuration):
- URL: the webhook URL provided during onboarding
- Secret / signing key: your ShopMy developer key. Zenoti uses this to sign each message, and ShopMy rejects anything that isn't signed correctly.
- Events: Invoice Closed (
Invoice.Closed)
- Press Save.
Note: if you ever rotate your developer key in ShopMy, update the webhook secret in Zenoti to match — otherwise ShopMy will stop accepting Zenoti's updates and commissions will no longer settle.
