foco
Back to Blog

Facebook CAPI Deduplication: Why Your Conversions Are Double-Counting

April 2, 2026 7 min read

You launch Meta CAPI. Conversion numbers jump 80%. ROAS looks incredible. You scale spend. Revenue doesn't follow.

This is the most common CAPI implementation failure I see. It is not a tracking breakthrough -- it is double-counting.

How Double-Counting Happens

When you run both the Meta Pixel (browser-side) and the Conversions API (server-side), Meta receives two separate signals for the same conversion event. One from the user's browser. One from your server.

Meta's system treats these as two independent events unless you explicitly tell it they are the same event. The mechanism for that is event_id -- a unique identifier attached to both the Pixel event and the CAPI event. If the IDs match, Meta deduplicates. If they don't match, or if event_id is missing from either side, Meta counts both.

Most implementations get one side right and miss the other.

Why This Is Hard to Catch

The conversion numbers go up, not down. Nobody investigates a number that looks good.

Marketing sees doubled ROAS and wants to scale. Finance sees the same period's revenue and it doesn't match. The gap gets attributed to "attribution modeling differences" or "platform overcounting" -- vague explanations that don't lead to a fix.

I audited a B2B SaaS account where Meta Ads Manager showed 340 conversions in a month. The CRM showed 185 closed deals from paid social. The 1.8x inflation wasn't random -- it was almost exactly what you'd expect from every conversion being counted twice, minus the fraction where the Pixel was blocked by ad blockers and only the CAPI event came through.

Double-counting compounds downstream

If your optimization signal is inflated, Meta's algorithm thinks it is performing better than it is. It allocates budget toward audiences and placements that look like they convert well but are actually just double-reporting. You end up optimizing toward a phantom signal.

How to Diagnose It

Step 1: Compare Platform vs CRM Numbers

Pull conversion counts from Meta Ads Manager for the last 30 days. Compare against your CRM or backend for the same event type and time period. If Meta is showing 1.5-2x your actual numbers, deduplication is the first thing to check.

Step 2: Check Meta Events Manager

Go to Events Manager → your Pixel → Overview. Look at the event you're tracking (Purchase, Lead, etc.). Meta shows a breakdown of events received via browser Pixel, server (CAPI), and deduplicated.

If the "Server" count is roughly equal to the "Browser" count, and the total is close to the sum of both, deduplication is not working.

Step 3: Use Test Events

Go to Events Manager → Test Events. Fire a test conversion on your site. You should see two events arrive -- one from the Pixel, one from CAPI. Check whether they share the same event_id.

If they have different event_id values, or if one is missing event_id entirely, that is the problem.

Check both the Pixel and the CAPI payload

A common failure mode: event_id is generated client-side and sent with the Pixel event, but the server-side implementation generates its own separate ID (or doesn't send one at all). Both sides must use the identical value for the same user action.

How to Fix It

The fix is straightforward in concept and easy to get wrong in execution.

Generate One ID, Use It Everywhere

When the conversion event happens (page load, form submit, purchase confirmation), generate a unique event_id on the client side. A UUID or timestamp-based ID works. The format doesn't matter as long as it is unique per event occurrence.

const eventId = crypto.randomUUID();

Pass It to the Pixel

Include the event_id in your Meta Pixel fbq() call:

fbq('track', 'Purchase', {
  value: 149.99,
  currency: 'USD'
}, { eventID: eventId });

Note: the Pixel uses eventID (camelCase) in the options object, not in the event data.

Pass It to Your Server

Send the same event_id to your server. If you're using a dataLayer and server-side GTM, push it into the dataLayer:

dataLayer.push({
  event: 'purchase',
  event_id: eventId,
  value: 149.99,
  currency: 'USD'
});

Your sGTM client picks this up, and the server-side Meta CAPI tag reads event_id from the event data.

Include It in the CAPI Payload

Whether you're using sGTM, a custom server, or a third-party integration, the CAPI request to Meta must include the same event_id:

{
  "event_name": "Purchase",
  "event_id": "same-uuid-from-client",
  "event_time": 1711987200,
  "user_data": { ... },
  "custom_data": {
    "value": 149.99,
    "currency": "USD"
  }
}

The event_id value in the CAPI payload must be byte-for-byte identical to the eventID passed to the Pixel.

Where It Breaks

sGTM Tag Doesn't Map event_id

The most common failure I see with server-side GTM setups. The web container sends event_id in the dataLayer. The sGTM client parses the request. But the Meta CAPI tag in the server container doesn't have event_id mapped -- either because the field name doesn't match or because the tag template doesn't automatically read it.

Fix: In your sGTM Meta CAPI tag, explicitly map the event_id parameter. Don't assume the tag reads it automatically.

Client-Side and Server-Side Generate Separate IDs

Some implementations generate event_id in JavaScript for the Pixel, then generate a different ID server-side for the CAPI payload. Both events have an event_id, but they don't match. Meta sees two events with two different IDs and counts both.

Fix: The ID must originate in one place and be passed to both destinations. Client-side generation is simplest because the Pixel fires from the client. Send that same value to the server.

Third-Party Integrations Skip event_id

If you're using a platform like Shopify, WooCommerce, or a CDP to send CAPI events, check whether the integration actually includes event_id. Some send the conversion event to Meta but don't implement deduplication at all.

Fix: Check the integration's documentation or inspect the outgoing CAPI requests. If event_id isn't being sent, you need to either configure it or build a custom workaround.

Timing Mismatches

Meta deduplicates events within a 48-hour window. If there's a significant delay between the Pixel event and the CAPI event (common with batch-processed or queued server implementations), the events still deduplicate as long as they're within that window and share the same event_id and event_name.

If your server-side events are delayed beyond 48 hours, deduplication fails silently.

How to Verify the Fix

After implementing event_id on both sides:

  1. Test Events: Fire a test conversion. Confirm both events arrive in Events Manager → Test Events with the same event_id. Meta should show one of them as deduplicated.
  2. Event Overview: Monitor the event counts over 48-72 hours. Browser event count plus server event count should now be greater than total event count (because deduplication is removing the overlap).
  3. CRM Comparison: Pull Meta conversion counts for the same period as your CRM data. The numbers should now be within 5-15% of each other, not 80-100% inflated.
Your conversion count will drop after fixing this

This is expected and correct. You are not losing conversions -- you are removing phantom ones. The algorithm will take a few days to recalibrate against the accurate signal. Short-term CPA may appear to rise. Long-term performance improves because Meta is now optimizing toward real conversions, not duplicates.

What Happens After

Once deduplication is working correctly, your Meta Ads data becomes usable again. ROAS reflects reality. Budget decisions are based on actual conversion rates, not inflated ones.

On the engagement where I found the 1.8x inflation, fixing deduplication dropped reported conversions by 45% overnight. The marketing team initially panicked. But CPA stabilized within a week, and actual revenue per dollar spent improved over the following month because Meta's algorithm was finally optimizing against a clean signal.

The hardest part is not the implementation. It is convincing stakeholders that the "drop" in conversions is the fix working, not something breaking.

Not sure if your CAPI events are deduplicating correctly?

Get a measurement review

Share this article