Tracking & Attribution

How Do You Deduplicate Microsoft UET and CAPI Events?

Microsoft counts a conversion twice when UET and the Conversions API disagree on the event ID or event name. The causes, and how to fix each one.

Quick answer

Microsoft pairs a browser UET event with a Conversions API event only when both carry the same event ID and the same event name. If either differs, or if one side omits the ID, Microsoft has no way to know the two events describe one conversion and counts both. The Microsoft-specific trap is structural: the Conversions API posts into the same UET tag your browser script already uses, so a double count lands directly inside the conversion goal your bidding optimises against.

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Microsoft deduplicates a browser UET (Universal Event Tracking) event against a Conversions API event only when both carry the same event ID and the same event name. Miss either half and Microsoft has no basis for treating them as one conversion, so both count. That is the whole mechanism.

What makes the Microsoft version different is where the damage lands. Microsoft’s Conversions API is addressed by your UET tag ID, so both paths write into the same tag, and your conversion goals sit on top of it. A double count does not appear in some separate server column you reconcile later. It lands inside the number your bid strategy optimises against.


What does a Microsoft UET and CAPI double-count look like?

It looks like one conversion goal in Microsoft Advertising reporting roughly twice the records your CRM or order table holds, while your other goals look correct. The ratio is the tell: a clean multiple near 2× on the events you send both ways.

Check the shape before you check any code. Say your database shows 120 leads for a week and the goal shows 228, while a second goal fed only by the browser tag reconciles fine. That is a pairing failure — arithmetic, not modelling. Those figures are illustrative; the pattern is the point. Inflated platform numbers do have a second, unrelated cause — attribution windows and view-through credit — but that mismatch wanders with campaign mix, where a dedup failure holds the same multiple every day.

The cost goes past reporting. Automated bidding treats a doubled goal as real volume, so it learns from a conversion rate that does not exist and pays accordingly.


How does Microsoft decide a UET event and a CAPI event are the same conversion?

On two fields: the event ID and the event name. Microsoft looks for a browser event and a server event that agree on both, and when it finds the pair it keeps one. It does not compare timestamps, values or hashed emails — identity data is what a platform uses to match an event to a person, not to spot duplicates.

That separation is the most common misconception in server-side tracking. Deduplication and match rate are independent systems that happen to share a payload: an event can resolve to a real user perfectly and still double count, and it can match nobody and still deduplicate cleanly.

So a lead that should report once needs both halves to emit the same event name and the same event ID, derived from something both sides can compute independently. Every platform running the Conversions API pattern uses this contract — Meta, TikTok, Pinterest, Microsoft. The field names differ. The logic does not.

Diagram of Microsoft deduplication: a browser UET event and a Conversions API event posting into the same UET tag, pairing into one counted conversion when the event ID and event name agree, counting twice when they do not


Why does one tag ID for both paths change the problem?

Because it removes the reconciliation surface other platforms give you. With Meta you have a Pixel and a server dataset and can read each side’s volume separately. Microsoft’s Conversions API endpoint takes your UET tag ID in the path, so both writers land in one tag and the goal built on it reports their sum.

Two consequences follow. You cannot diff the two paths inside Microsoft’s reporting to find the duplicate: the only independent number is your own, in your CRM or order table. And one misconfiguration damages a goal you already trusted, because adding server-side sending to a tag that has fed a goal for a year gives you no isolated goal to test in the corner — it changes the meaning of the existing one from the first event onward. If you are standing this path up for the first time, the groundwork for tracking Bing Ads conversions accurately is worth settling first.


Why doesn’t my UET tag send an event ID?

Because classic UET never needed one. Conversion goals were defined by page-load rules or by an event’s category, action and label, and none of that required a unique identifier per conversion. The ID is a deduplication primitive, and deduplication only became relevant when a second path appeared.

This is the most common cause of Microsoft double counting, and it is nobody’s bug. The server half was built to a modern API spec and carries an ID because the spec asks for one; the browser half is whatever was installed years ago and carries none. A missing ID is not a wildcard that matches anything — it removes that event from pairing consideration entirely, so it counts on its own.

The fix is to make the browser tag emit the same derived value your server computes. Read the exact property and its position from Microsoft’s current UET documentation rather than a blog post, because that detail has changed across versions. The harder half is not the syntax anyway — what the event ID should actually be is worth settling before you touch either side.

Two failure modes hide behind an ID that is present on both sides. A random value on each side — a UUID at fire time in the browser, a different one at send time on the server — is valid, unique and will never match. So is the same idea over different inputs: the browser hashing a session ID and timestamp while the server hashes the order number. Determinism only helps when both sides run the same recipe over the same source of truth.


Why does the event name break Microsoft dedup so often?

Because on Microsoft the event name does double duty: it is half the dedup key, and it is also what your conversion goal listens for. A name that is wrong for one job is wrong for both.

The usual story: a team ports a working Meta integration across and sends Purchase and Lead, because that is what their Meta payload builder emits. The API accepts them, and Microsoft’s goal — configured against the name the browser tag has always fired — never sees a match. The server events then either do not count at all, a silent under-count that looks like a delivery problem, or they land as a separate counted path, which is an over-count.

Check this before anything else: open the goal in Microsoft Advertising, read the exact name or the category and action it listens for, then read what your server actually sends. Mapping internal event names onto goal names is a per-integration job, not a default — and it is the five-minute check that saves the week-long investigation. Adding more identifiers to the payload, by contrast, changes nothing about duplicates.


Why do matching IDs still count twice sometimes?

Usually timing. Pairing works on events the platform can consider together, so the two halves of one conversion have to arrive close enough together to be treated as a pair. A webhook retried hours later, a nightly batch, a catch-up worker draining a backlog — each delivers a server event long after its browser twin was counted and settled.

Check Microsoft’s own documentation for the window that applies to your setup rather than a number from a third party. The practical rule holds regardless: send both events as close together as your stack allows, and treat a large gap as a design problem rather than acceptable lag.

This is also why backfills are risky. Replaying three weeks of history will not pair those conversions with browser events counted weeks ago — it adds them. Platforms apply event-time windows too, so old events can be clamped or rejected whatever your dedup key says.


Should you just turn off UET and send everything server-side?

No. It fixes the count and damages the matching, which is the more expensive half.

The browser tag captures what your server cannot see on its own: Microsoft’s own cookies as they are set in the browser, and the msclkid click ID as it lands on your page. Strip it out and your server events survive on hashed email, hashed phone, IP and user agent — your match rate falls exactly as you remove the signal holding it up. You have traded an inflated number for an under-matched one, and the second problem is harder to see.

The supported architecture is both paths running with a shared ID: browser events carry cookie-scoped identifiers the server never sees, server events survive ad blockers, closed tabs and payment redirects. Deduplication is what lets you run both without paying for it twice.


How do you tell a dedup failure from real over-reporting?

Count from the ground truth first: your CRM or order table is the only number here that is not a model. Pull three figures for the same range in the same timezone — conversions on the specific goal, events your server logged as sent, and real records in your database — then read the pattern. Sends roughly equal to records with the goal near double both is a pairing failure. Sends already double your records means your own dispatch is firing twice and Microsoft is reporting faithfully. A goal higher than both by an inconsistent margin is attribution modelling, and there is nothing here to repair.

Then trace one conversion end to end: find a single lead’s browser event and server event and read the two IDs and the two names side by side. It is the same walk used to debug a Meta CAPI that is not deduplicating or to work out why TikTok conversions are not deduplicating — the tooling differs per platform, the trace does not.


How does PartialLeads deduplicate Microsoft UET and CAPI events?

By deriving every event ID from the record instead of inventing one at send time, and by refusing the second write at the database. PartialLeads builds each outbound event ID as a SHA-256 hash of the tag, the event name and the record ID, and every dispatch table is keyed UNIQUE(config_id, event_id). Because the event name is a hash input, a lead event and a purchase event for one record stay distinct rather than colliding. Microsoft pairs UET and Conversions API events on a shared event ID and event name, so that derivation makes retries idempotent at both ends.

Three Microsoft-specific details matter more than the hashing.

Event names are mapped per config, not assumed. Each Microsoft configuration carries its own lead event name and purchase event name, so internal events are translated into whatever the merchant’s goals actually listen for. That is the name-mismatch failure handled as configuration rather than convention.

Configs are per client and per domain, keyed on the UET tag. An agency’s domains can belong to different Microsoft accounts, and each domain’s config points at its own tag with its own token, so sends never cross into the wrong tag.

Late events are clamped, not silently mangled. Event time is floored at seven days ago and capped at now, so a historical send still delivers, with a clamped timestamp. That is a delivery guarantee, not a backfill feature: a clamped event will not pair with a browser event that settled three weeks ago.

The Microsoft CAPI activity log is where you watch all of this. Rows carry the event name, the status and Microsoft’s own response — including the partial-success case where it returns HTTP 200 with an optional field removed and a validation warning, which surfaces as a dropped-identifier warning instead of passing as a clean send. Above the log, the health strip shows sends over the last seven days, a sparkline, the delivery rate and the warning count, with test sends excluded from real volume.

PartialLeads Microsoft CAPI activity log showing a sent lead event and its deduped twin sharing one event ID, a dropped-identifier validation warning row, and the health strip with delivery rate and warning count

Three honest constraints.

PartialLeads controls its own sends, not your UET tag’s. If your browser tag fires without an event ID, or with a random one, the two events have nothing to pair on. Feed the same derived ID into the browser call, or accept that the two paths count separately.

Keep the browser tag installed. Server-side dispatch does not replace UET. Match quality depends on cookie-scoped identifiers captured client-side, which no server can synthesise.

Microsoft has to approve Conversions API access for your advertiser account before a token exists to paste. That is an external process on Microsoft’s timeline, and no vendor accelerates it.

The split worth internalising: dispatch is yours, matching is the platform’s. Server-side sending does not depend on browser cooperation, so every record in your system can produce an event — but whether Microsoft resolves it to a user and a click depends on its own graph. Deduplication sits entirely on the side you control, which is why it is worth getting exactly right.

What breaks The mechanism Where you see it in the dashboard
Random or missing event IDs across the two paths Event ID derived as a SHA-256 of tag, event name and record ID Microsoft CAPI activity log, event rows
A redelivered webhook firing a second conversion Dispatch tables keyed UNIQUE(config_id, event_id) Activity log, no second row for one record
Server event names your Microsoft goal isn’t listening for Per-config lead and purchase event-name mapping Microsoft CAPI config, event names
An agency’s domains pointing at different Microsoft accounts Per-client, per-domain configs keyed on the UET tag CAPI config list grouped by domain
Historical sends arriving outside the event-time window Event time floored at seven days ago, capped at now Activity log timestamps
A “successful” send that quietly lost an identifier HTTP 200 validation warnings surfaced as dropped-identifier rows Activity log warnings, health strip warning count
No idea whether a given lead was sent at all Per-lead dispatch record across every connected API Leads list, API column

Tell us what's broken. We'll fix your tracking — free.

Describe the tracking/attribution problem you're stuck on and we'll map it to a fix: server-side conversions to Meta, Google, TikTok and Pinterest, plus first-party tracking that survives Safari. No code required.

Sources

  1. Microsoft Learn — Universal Event Tracking (UET): https://learn.microsoft.com/en-us/advertising/guides/universal-event-tracking
  2. Meta for Developers — Deduplicate Pixel and server events: https://developers.facebook.com/docs/marketing-api/conversions-api/deduplicate-pixel-and-server-events
  3. Meta for Developers — Conversions API: https://developers.facebook.com/docs/marketing-api/conversions-api

Frequently asked questions

QDoes Microsoft deduplicate on email or phone if the event IDs don't match?
No. Identity fields are what Microsoft uses to match an event to a user, not to recognise two events as one conversion. Deduplication compares the event ID and the event name only. An event carrying a perfectly hashed email and a mismatched ID still counts separately, which is why adding more identifiers never fixes a double-counting problem.
QMy UET tag doesn't send an event ID at all. Is that the problem?
Almost certainly. A missing ID is not treated as a wildcard that matches anything — it removes that event from pairing consideration entirely, so it counts on its own. Classic UET never needed an ID, because conversion goals were defined by page rules or event category and action. Both halves have to emit one for deduplication to happen at all.
QDo UET and the Conversions API write into the same tag?
Yes. Microsoft's Conversions API endpoint is addressed by your UET tag ID, so the browser path and the server path feed the same tag and the same conversion goals. That is why a double count lands inside a goal you already optimise against, and why you cannot diff the two paths inside Microsoft's own reporting to find it.
QShould the event ID be random or derived?
Derived. Two systems have to compute the same string independently, which randomness makes impossible. Hash something both sides can see — the order ID or the lead record ID — together with the event name, so retries and replays produce a stable value. A UUID generated at fire time is unique and useless for this purpose.
QCan I just remove the UET tag and send everything server-side?
You can, and your counts will be correct, but your match rate will fall. The browser tag captures cookie-scoped identifiers your server cannot produce, including the msclkid click ID as it lands on the page. Running both paths with a shared event ID is the supported architecture; deduplication exists precisely so you do not have to choose.
QWhy did my historical upload inflate my conversion numbers?
Because pairing only works on events the platform can consider together, and a browser event counted weeks ago is settled. Replaying old conversions adds them rather than pairing them. Platforms also apply event-time windows that clamp or reject old events, so a backfill is safest for conversions that never had a browser event at all.
QMy server logs say every event was accepted. Doesn't that prove deduplication worked?
No. Acceptance means the payload was valid, nothing more — deduplication happens after acceptance, so a paired event and a duplicated one both return success. Microsoft can even return a 200 with an optional field stripped and a validation warning attached. Confirm pairing by reconciling the goal against your own records, or by tracing one conversion's two events.
QDoes Microsoft need to approve Conversions API access before any of this applies?
Yes. Microsoft must enable Conversions API access for the advertiser account before the token you authenticate with exists. That approval is an external Microsoft process with its own timeline, and no tracking vendor grants or accelerates it. Until then, UET alone is your only path and there is nothing to deduplicate.

Find the qualified leads your forms are currently throwing away.

Install PartialLeads on one landing page, send traffic, and compare what your CRM captured against what PartialLeads recovered and qualified.