Microsoft Ads Server-Side Tracking: The Conversions API Guide Nobody Wrote
Table of contents
The short answer. Microsoft Advertising now has a Conversions API that accepts UET events from your server, so conversions can reach Microsoft even when the browser tag never fires. It is not a drop-in pixel replacement. Remarketing and audience building still need a client-side ID sync, so on Microsoft the correct setup is both, deduplicated.
Search “Microsoft Ads conversion tracking” and every result on page one tells you the same thing: install the UET tag, ideally through Google Tag Manager. Microsoft’s own help pages, Analytics Mania, MeasureSchool, a HubSpot community thread, a BigCommerce doc. All of them stop at the tag.
That advice was complete three years ago. It is not complete now, and the gap shows up in the same SERP: two results are questions on Microsoft’s own Q&A forum from people asking how to send Bing conversions through a server container, and not getting a straight answer. Here it is.
Why the tracking gap is widest on the account you care least about
Microsoft Ads is usually the third or fourth line in a paid media budget. It is also, structurally, the account where browser-based tracking loses the most.
Bing’s traffic skews desktop, because Windows and Edge default to it, and desktop is exactly where extension-based ad blockers live. Mobile in-app browsing, where blocker extensions barely reach, is Google and Meta territory. So the platform whose audience is most likely to strip your tag is the one getting the least measurement attention, purely because it is the smallest budget line.
That produces a loop worth naming, because it looks like a performance problem and is actually a conversion data problem. Microsoft conversions are under-reported more heavily than Google or Meta conversions, so measured ROAS comes in low, so budget shifts away, so nobody invests an afternoon fixing measurement on a channel that “does not work.” The fix is a couple of hours. It never gets scheduled because of the last step.
What UET is, and the four places it breaks
Universal Event Tracking is Microsoft’s equivalent of the Meta pixel. One JavaScript snippet, window.uetq calls for events, and Microsoft handles the rest. When auto-tagging is on, Microsoft appends an msclkid parameter to your landing page URL, the UET script writes it to a first-party cookie named _uetmsclkid, and every later event on that browser carries the click ID so the conversion attributes back to the ad.
Elegant, and dependent on four things that are no longer reliable:
The script has to load. UET requests go to bat.bing.com, which sits on standard filter lists, and a blocked request is an event that never existed. Mechanics in the guide to ad blocker conversion tracking.
The cookie has to survive. _uetmsclkid is written by JavaScript, so on Safari it inherits ITP’s seven-day cap on script-written storage. Microsoft’s own recommended click ID retention is 90 days. Those numbers do not agree, and the browser wins.
The page has to render. Off-site payment flows, headless checkouts, embedded booking widgets and app-to-web handoffs all break the assumption that a confirmation page loads in the browser context that clicked the ad.
The user has to consent. UET Consent Mode has been mandatory since May 2025 for any campaign reaching users in the EEA, the UK or Switzerland. Denied consent is denied server-side too, but a meaningful slice of traffic never reaches a measurable state at all.
None of this is exotic. It is the erosion described in what server-side tracking is, running hardest on the platform nobody is watching.
Microsoft’s Conversions API, plainly
The Conversions API (Microsoft calls it CAPI, same as Meta) accepts UET events as JSON POSTs from your backend:
POST https://capi.uet.microsoft.com/v1/{tagId}/events
Authorization: Bearer <ApiToken>
Two kinds of events. pageLoad events, one per page view or SPA navigation, carrying the URL so destination goals and remarketing segments still work. And custom events, which carry the interesting payload: event name, revenue, currency, transaction ID, product items, and a userData object holding the hashed email, hashed phone, IP, user agent, anonymous ID and, critically, the msclkid. Custom events link back to their page load through a pageLoadId UUID, batch up to 1,000 at a time, and support restate and retract for revenue corrections, but only when you send a transactionId. Send one on every purchase whether you think you need it or not.
If you have already implemented the Google Ads Conversions API path or Meta CAPI, the shape will feel familiar. The differences are where it gets interesting.
What you keep, what you lose, and the take nobody publishes
Here is the part the vendor blogs skip, and it comes from Microsoft’s own documentation rather than anyone’s marketing page. Microsoft states plainly that the UET JavaScript “will serve as the primary path for supporting Privacy Sandbox APIs in the future,” that CAPI integration effort is “significantly greater,” and, in the don’t-do-this list, that you must never fire the ID sync pixel from your server.
That last one matters more than it reads. ID sync is the client-side beacon mapping your anonymous visitor ID to Microsoft’s cookie IDs, and Microsoft is explicit that remarketing, audience targeting and audience building “rely on user identity resolution and won’t function without it.”
| Capability | UET JavaScript only | Conversions API only | Both, deduplicated |
|---|---|---|---|
| Conversion measurement | Partial, blocked where the tag is | Strong, survives blockers | Strongest |
| Enhanced conversions (hashed email, phone) | Yes | Yes | Yes |
| Remarketing and audience building | Yes | Only with a client-side ID sync beacon | Yes |
| Dynamic remarketing product data | Yes | Yes, via the items array | Yes |
| UET Insights and Clarity integration | Yes | No | Yes |
| Stated Privacy Sandbox path | Yes | No | Yes |
| Implementation effort | Low | High, per Microsoft’s own wording | High |
So here is the opinionated part, and it cuts against how our own category usually sells itself. On Microsoft Ads, server-side is not a pixel replacement. It is a second, more durable delivery path for the same events. Anyone telling you to rip out the UET tag and go pure server-to-server either has not read Microsoft’s documentation or is selling you something that costs you your audience lists. Run both, deduplicate, and note that this is different from Meta, where a CAPI-only setup is genuinely viable.
The msclkid is now your problem
With the UET JavaScript in place, Microsoft captures and persists the click ID for you. Drop the JavaScript and that job transfers to you in full: capture msclkid from the landing page query string, store the most recent value per user, overwrite on every new click, retain for 90 days, attach it to every event. And the blunt version, again from Microsoft’s don’t-do-this list: do not omit the msclkid from conversion events, because the visitor ID alone is not sufficient for attribution.
// capture-msclkid.mjs
import { createHash } from 'node:crypto'
const NINETY_DAYS = 90 * 24 * 60 * 60
// 1. Capture on the landing request and set a SERVER-side first-party cookie.
// A cookie written by JavaScript is capped at 7 days under Safari ITP.
// A cookie written by your origin in a Set-Cookie header is not.
export function captureClickId(req, res) {
const msclkid = new URL(req.url, `https://${req.headers.host}`)
.searchParams.get('msclkid')
if (!msclkid) return
res.setHeader(
'Set-Cookie',
`mskid=${msclkid}; Max-Age=${NINETY_DAYS}; Path=/; Secure; HttpOnly; SameSite=Lax`
)
}
// 2. Normalize exactly as Microsoft specifies, or the API rejects the field.
const hashEmail = (raw) => {
const [user, domain] = raw.trim().toLowerCase().split('@')
const clean = user.split('+')[0].replace(/\./g, '')
return createHash('sha256').update(`${clean}@${domain}`).digest('hex')
}
const hashPhone = (e164) =>
createHash('sha256').update(e164.trim()).digest('hex')
// 3. Send the conversion.
export async function sendPurchase({ tagId, token, order, msclkid, visitorId, ip, ua }) {
const body = {
data: [{
eventType: 'custom',
eventId: order.id, // same value the browser tag sends
eventName: 'purchase',
eventTime: Math.floor(Date.now() / 1000), // seconds, and within the last 7 days
userData: {
msclkid,
anonymousId: visitorId, // must match the ID sync vid
clientIpAddress: ip,
clientUserAgent: ua,
em: hashEmail(order.email),
ph: hashPhone(order.phoneE164)
},
customData: {
transactionId: order.id, // required for restate and retract
value: order.total,
currency: order.currency,
pageType: 'purchase'
}
}]
}
const res = await fetch(`https://capi.uet.microsoft.com/v1/${tagId}/events`, {
method: 'POST',
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: JSON.stringify(body)
})
// Only mark the order as sent on a 2xx, so a timeout stays retryable.
return res.ok
}
One detail worth calling out. Microsoft’s email rule strips dots from the user portion and any +alias, which is more aggressive than Google’s enhanced conversions normalization, so a hashing helper shared across platforms will quietly mismatch. Give Microsoft its own.
The seven-day trap that will eat your B2B conversions
Buried in Microsoft’s validation errors is a constraint with real consequences:
'eventTime' must be a valid UNIX UTC timestamp in seconds within last 7 days.
The Conversions API will not accept an event older than seven days. If you sell anything with a sales cycle, that is most of your revenue. A lead that clicks on the 1st and signs on the 20th cannot go through CAPI at all. The request fails validation, and if you batched it without continueOnValidationError, it takes the whole batch down with it.
The fix is a different endpoint. Microsoft’s Campaign Management API exposes ApplyOfflineConversions, which accepts backdated conversions matched on msclkid within the goal’s window. Separate integration, separate goal type (OfflineConversion), and nothing in the CAPI documentation points you there.
Route by conversion latency, not by preference:
| Time from click to conversion | Endpoint | Goal type | Matching key |
|---|---|---|---|
| Same session | UET JavaScript | Standard UET goal | Automatic, via _uetmsclkid |
| Minutes to 7 days | Conversions API | Standard UET goal | msclkid you stored |
| 7 days to 90 days | ApplyOfflineConversions | Offline conversion goal | msclkid you stored |
| Value correction, any time | OnlineConversionAdjustment | Existing goal | transactionId |
Note what every row after the first has in common: a click ID you captured and kept. Click ID persistence is the whole ballgame on Microsoft, the same lesson as Google Ads enhanced conversions with less margin for error, because Microsoft offers no server-side fallback identifier.
Deduplication, in one paragraph
If you run both paths (and you should), use the same UET tag ID on both and send the same eventId from browser and server. Microsoft collapses the pair. In the browser that is event_id in your uetq push; on the server it is the top-level eventId. Use something already unique and stable, like the order ID. Generate a random UUID at fire time on each side and nothing matches, so you report every purchase twice.
What server-side does not fix
Worth being straight about, since most posts in this category are not:
- Consent. An event sent with
adStorageConsent: "D"is not used for attribution or retargeting. Server-side changes the delivery path, not the legal basis. - A click ID you never captured. If auto-tagging was off, or a landing page redirect strips query parameters, there is nothing to send. Check your redirects first.
- Audience building without ID sync. Covered above. This one surprises people.
- Bad goal configuration. A goal counting every page view just counts wrong faster.
- Attribution beyond the window. Conversion goals default to 30 days in the API. Nothing outside it counts.
Where Convultra fits
Convultra captures msclkid on the landing request alongside gclid, fbclid and ttclid, keeps a click history with 90-day retention matching Microsoft’s own recommendation, restores click IDs across domains through a linker so an off-site checkout does not orphan the attribution, and delivers conversions on both the real-time and offline paths without you writing any of the code above. The browser tag keeps running for audiences and Clarity. The server path carries what the browser loses. Deduplication runs on the transaction ID.
If you would rather build it yourself, everything you need is in this post and in Microsoft’s documentation. If you would rather not maintain per-platform hash normalization rules forever, that is what the product is for. Architecture detail is in our server-side conversion tracking overview.
FAQ
Does Microsoft Ads have a Conversions API?
Yes. Microsoft’s Conversions API accepts UET events server-to-server at https://capi.uet.microsoft.com/v1/{tagId}/events, authenticated with a bearer token from the UET tag section of your Microsoft Advertising account. It supports page load events, custom events, hashed identifiers, revenue values and product data.
Can I replace the UET tag with server-side tracking?
Not fully. Conversion measurement works server-side, but remarketing, audience targeting and audience building depend on a client-side ID sync beacon that Microsoft explicitly says you cannot fire from your server. UET Insights and Clarity integration also require the JavaScript. Run both and deduplicate.
What is msclkid and why does it matter for server-side tracking?
msclkid is Microsoft’s click ID, appended to your landing page URL when auto-tagging is enabled. Without the UET JavaScript, capturing and storing it becomes your job. Microsoft recommends 90-day retention and states that a visitor ID alone is not sufficient to attribute a conversion.
Why did my Microsoft conversion get rejected by the API?
The most common causes are an eventTime outside the last seven days, an unnormalized email or phone hash, or a missing bearer token. Conversions older than seven days must go through ApplyOfflineConversions in the Campaign Management API instead.
Will server-side tracking double count my Microsoft conversions?
Only if you skip deduplication. Send the same eventId from both the browser tag and the server, using a value that is already unique in your system such as the order ID, and Microsoft collapses the pair into one conversion.
Is Microsoft Ads server-side tracking worth it on a small budget?
Usually yes, and disproportionately so. Bing traffic skews desktop, which is where extension ad blockers are most common, so the measured-versus-actual gap tends to be wider on Microsoft than on Google or Meta. Under-reported conversions suppress measured ROAS, which suppresses budget, on a channel that is often cheaper per click in the first place.
Get the conversions you are already paying for
Microsoft Ads is the account most likely to be quietly under-reporting and the least likely to get an afternoon of engineering attention. Start free and see the gap on your own account.
Written by Marcus Johnson
Technical Writer
Contributing author at Convultra. Sharing insights on conversion tracking, marketing attribution, and growth strategies.