Guide · Tracking & analytics
Pass UTM parameters and gclid into Microsoft Bookings
Last updated 9 min read10 sources
This guide shows how to get campaign data from your ads and emails into Microsoft Bookings, so you can see which campaigns produce booked appointments, without the query strings that have broken Bookings links before. It's for marketers and web teams who send traffic to a shared Microsoft Bookings page. Setup takes about an hour: planning codes, adding two small scripts to your site, and making a test booking.
The other tracking guides use the same mechanism for other purposes. The GA4 guide carries a GA4 client_id, the Google Ads guide carries a gclid, and the Meta guide carries a lookup key. This one covers campaign codes and how to combine them with the others.
What Microsoft's page does with query strings#
Microsoft documents exactly one parameter for booking page URLs: a campaign ID called RefID. Its documentation gives this example and warning:
https://outlook.office365.com/owa/calendar/TailspinToys@contosopetscom.onmicrosoft.com/bookings/?RefID=TwitterThe docs say "characters in the campaign ID must be one of the following: alphanumeric characters, underscore or hyphen" and "make sure you test your Campaign ID url". The example still uses the older /owa/calendar/ address. Current pages use outlook.office.com/book/..., and Microsoft says the old form redirects.
Everything else is undocumented. This is what people have reported:
| When | What was added to the link | What happened |
|---|---|---|
| 2020 to 2023 | utm_source and other UTM parameters | Bad Request page. The only workaround offered was to embed the page on your own site. A 2023 reply said email UTMs still broke it. |
| 2021 | ?odSkipAutoFocus=true, appended by SharePoint to an embed | Bad Request inside the SharePoint page |
| Nov 2023 | ?refID= | Bad Request on 79 of one organisation's 80 pages, including Microsoft's own example. No fix posted. |
| 2024 and 2025 | ?RefId= | The page loaded and the booking worked, but Tracking data was blank in the export |
| Jan 2026 | UTM values with %20 (encoded spaces) | The Bookings page failed to load. The accepted answer was to replace spaces with _ or -, use a URL shortener, or open a support ticket. |
The 2026 thread suggests that plain UTM values now load and encoded characters don't, but Microsoft hasn't said so. Bookings also has nowhere to keep UTM values: there's no URL prefill (Microsoft calls pre-filled fields "not feasible") and no redirect back to your site. So the safe approach is:
- Put full UTMs on your own landing pages, where analytics reads them.
- Store them in the visitor's browser on your site.
- Send Bookings one short RefID, built only from letters, digits,
_and-.
The short version of the Bad Request problem is in why UTM parameters break Bookings links.
Step 1: Decide what RefID should carry#
Each link has room for one value, so choose one of these:
| Strategy | RefID looks like | Good for | Trade-off |
|---|---|---|---|
| Campaign code | gads_brand_oct26 | Counting bookings per campaign in the export | No click-level matching |
| Prefixed ID | ga-123456789-1712345678 or gc-Cj0KCQ... | One integration: GA4 or Google Ads | Only one kind of ID per booking |
| Lookup key | k3f9a2c71d0e4b8a | Everything: UTMs, gclid, client_id, Meta cookies | Needs a small store on your server |
With a lookup key, your site saves the full attribution record (all UTMs, click IDs, landing page, GA4 client and session IDs) under a random key on your own server, and RefID carries only the key. A Power Automate flow or your CRM then looks the key up when the booking arrives. The Meta guide has a working example of the storage endpoint.
If you only need to know which campaigns book, use campaign codes. The rest of this guide sets those up.
Step 2: Plan your campaign codes#
Pick a naming scheme that is short and safe for the RefID character rules:
| utm_source / utm_medium | utm_campaign | RefID |
|---|---|---|
| google / cpc | Brand - October 2026 | google_cpc_brand-october-2026 |
| linkedin / paid_social | Webinar invite | linkedin_paid_social_webinar-invite |
| newsletter / email | 26 Sept issue | newsletter_email_26-sept-issue |
Rules we suggest:
- Lowercase only. Microsoft doesn't say whether values are case-sensitive, and lowercase avoids duplicates such as
Springandspringin your pivot table. - No spaces, dots,
%,+or=. - Keep codes to about 40 characters. There's no published limit, so test your longest code.
- Keep a sheet that maps each code to the full UTM set.
Step 3: Store first-touch UTMs on your site#
Visitors rarely book on the page they land on. This script keeps the first campaign touch for 90 days, so a visitor who arrives from an ad on Monday and books from your pricing page on Thursday is still credited. Load it on every page, and only after consent if your consent policy covers it.
(function () {
var KEY = "bk_first_touch";
var TTL = 90 * 24 * 60 * 60 * 1000;
var fields = ["utm_source", "utm_medium", "utm_campaign", "utm_term", "utm_content",
"gclid", "gbraid", "wbraid", "fbclid", "msclkid", "li_fat_id"];
var existing = JSON.parse(localStorage.getItem(KEY) || "null");
if (existing && Date.now() - existing.ts < TTL) return; // keep the first touch
var params = new URLSearchParams(location.search);
var touch = { ts: Date.now(), landing: location.pathname, referrer: document.referrer };
var found = false;
fields.forEach(function (f) {
var v = params.get(f);
if (v) { touch[f] = v; found = true; }
});
if (found) localStorage.setItem(KEY, JSON.stringify(touch));
})();If you'd rather credit the most recent campaign (last touch), drop the early return and overwrite each time a campaign URL is seen.
Step 4: Build the RefID and add it to Bookings links#
This is the script that actually talks to Microsoft's page. It builds a code from the stored UTMs, reduces it to the allowed characters, removes any other query string from the link, and sets RefID.
(function () {
var touch = JSON.parse(localStorage.getItem("bk_first_touch") || "null");
if (!touch || !touch.utm_campaign) return;
function clean(v) {
return String(v || "")
.toLowerCase()
.replace(/[^a-z0-9_-]+/g, "-") // anything else becomes a hyphen
.replace(/-+/g, "-")
.replace(/^-|-$/g, "");
}
var refId = [touch.utm_source, touch.utm_medium, touch.utm_campaign]
.map(clean)
.filter(Boolean)
.join("_")
.slice(0, 40);
if (!/^[A-Za-z0-9_-]+$/.test(refId)) return;
document.querySelectorAll('a[href*="outlook.office"], iframe[data-bookings-src]').forEach(function (el) {
var isFrame = el.tagName === "IFRAME";
var url = new URL(isFrame ? el.dataset.bookingsSrc : el.href);
if (!/\/(book|owa\/calendar)\//.test(url.pathname)) return;
url.search = "";
url.searchParams.set("RefID", refId);
if (isFrame) el.src = url.toString(); else el.href = url.toString();
});
})();For an embedded page, put the Bookings URL in a data-bookings-src attribute and leave src empty. The script then sets src once, with the RefID, and the iframe doesn't load twice. Removing the existing query string also drops the ismsaljsauthenabled flag that Microsoft's embed snippet includes. If your iframe needs it, add it back after RefID and test both together.
If a campaign has no utm_campaign, the links stay clean. That's intentional: an untagged booking is better than a broken page.
Step 5: Read the results#
In the export. From the Shared Bookings home page, select Export, choose a date range and download the TSV file. The Tracking data column holds the RefID. Microsoft's reporting doc describes four-month periods, while the FAQ says "the past 120 days" and that only staff with the Administrator role can download it. Pivot on Tracking data to count bookings per code. The Excel reporting guide covers the clean-up.
As bookings arrive. The Power Automate trigger When a appointment is Created includes TrackingData ("Campaign tracking Data"). A flow can write it next to the customer in a SharePoint list or CRM, and can look up your code sheet to add the full UTM set. See sending bookings to a CRM with Power Automate. The connector is in Preview, only Bookings admins can create these flows, and each mailbox allows 5.
For staff in the appointment. RefID doesn't appear on the calendar item. If staff need to see the source, add a custom question such as "How did you hear about us?". It relies on the customer's answer, because Bookings can't prefill fields from the URL.
The gclid#
A gclid is too long to combine with a campaign code in one RefID, and it has to be uploaded exactly as Google issued it. Use the prefixed-ID or lookup-key strategy, and follow the Google Ads guide for the capture script, the flow and the upload. Don't send it through the clean() function above, because lowercasing breaks it.
Testing#
- Open your booking page with no parameters and confirm it loads.
- Open it with
?RefID=test_01. If this gives Bad Request, the rest of this guide can't work on that page. See RefID not working. - Visit your site with
?utm_source=Test&utm_medium=CPC&utm_campaign=Spring%20Sale. Then hover a Book link. It should end in?RefID=test_cpc_spring-sale, with no other parameters. - Open that link and complete a test booking.
- Export the bookings and check the Tracking data column shows
test_cpc_spring-sale. If you have a flow, check its run history as well. - Clear
bk_first_touchfrom local storage in your browser's dev tools before you test again, or the first touch will stick.
Common problems#
Bad Request after adding RefID. Remove every other parameter and retry with a plain value. If the plain value also fails, raise a Microsoft support ticket with the exact URL.
The email platform adds UTMs to every link. Many email tools tag links automatically, which is how raw UTMs (and %20) end up on Bookings URLs. Switch off auto-tagging for Bookings links, or link to your own booking page and let the script above build the RefID.
Tracking data is blank even though the link was right. This is the reported bug. Test on a second booking page and with the older URL format, and keep your own record (the lookup key) so you aren't relying only on Microsoft's column.
Codes split into near-duplicates. Campaign names changed during a campaign, or someone used mixed case. Fix it in the mapping sheet rather than in Bookings.
Shortened links lose the parameter. Some shorteners and redirects drop query strings. Link straight to the Bookings URL the script produced, or to your own page.
Doing this with BookingsXP#
BookingsXP skips the RefID round trip. Its widget runs on your own page, in front of your existing Bookings page, and captures UTM parameters, click IDs (gclid, gbraid, wbraid, fbclid, msclkid, li_fat_id, ttclid), the referrer and the landing page. It keeps the first touch for 90 days. When someone books, it writes the source and campaign into the booking notes, and optionally into a Bookings custom question you choose, so staff see it on the appointment in Bookings and Outlook, and it appears in Microsoft's own export. It respects Global Privacy Control and a window.bxpConsent = false flag, and the dashboard shows the booking funnel by source. See analytics and attribution. BookingsXP is independent and not affiliated with or endorsed by Microsoft.
Questions people also ask
Sources
- Microsoft Learn: Customize booking page (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Reporting info (opens in a new tab) · learn.microsoft.com
- Microsoft Learn: Bookings faq (opens in a new tab) · learn.microsoft.com
- Microsoft Learn (opens in a new tab) · learn.microsoft.com
- Microsoft Tech Community: Office bookings do not work with query paramaters (opens in a new tab) · techcommunity.microsoft.com
- Microsoft Learn: 20 in utm is breaking functionality of bookings pa (opens in a new tab) · learn.microsoft.com
- Microsoft Tech Community: Ms bookings tracking data refid generate bad request page (opens in a new tab) · techcommunity.microsoft.com
- Microsoft Tech Community: Track conversions for appointments booked through microsoft bookings on gt4 (opens in a new tab) · techcommunity.microsoft.com
- Microsoft Tech Community: Booking page embed in sharepoint iframe gives quot bad request (opens in a new tab) · techcommunity.microsoft.com
- Microsoft Community: Microsoft bookings page with pre filled fields (opens in a new tab) · answers.microsoft.com