Skip to main content
AnalyticsLearner

Module 8

Meta CAPI and browser Pixel deduplication via shared event_id

Prevent double-counting Meta conversions when you run both the browser Pixel and CAPI. The event_id must be identical on both sides.

meta-capimeta-pixeldeduplicationevent_id

The deduplication problem

When you run the Meta browser Pixel AND the Conversions API for the same event, Meta receives two separate events. Without deduplication, both count as conversions, double-reporting ad performance and inflating your cost-per-conversion.

Meta deduplicates based on a matching eventID (browser Pixel) / event_id (CAPI). If both values match, Meta counts it as one event.

Step 1: Generate a unique event ID

Generate the ID before either the browser or server sends the event. The best place is the moment the conversion action occurs (form submit, checkout complete).

// Generate a stable, unique event ID
// UUID v4 is sufficient - just needs to be unique per conversion
function generateEventId(): string {
  return crypto.randomUUID();
}
 
// Or with a timestamp prefix for easier debugging:
function generateEventId(): string {
  return `evt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
}

Step 2: Browser Pixel (GTM dataLayer)

Push the eventID into the dataLayer so GTM can pass it to the browser Pixel tag:

// Client-side - fire when the conversion occurs
const eventId = generateEventId();
 
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
  event: 'lead_submit',
  meta_event_name: 'Lead', // Maps to Meta standard event
  meta_event_id: eventId, // This is the dedup key
  // ... other parameters
});

In GTM, configure the Meta Pixel tag to read meta_event_id from the dataLayer as the eventID parameter.

Step 3: CAPI (server-side)

Pass the same eventId to your server action or route handler:

// In your form submission handler (client component)
async function handleSubmit(formData: FormData) {
  const eventId = generateEventId();
 
  // Store for the CAPI call
  formData.append('meta_event_id', eventId);
 
  // Also push to GTM dataLayer immediately
  window.dataLayer.push({
    event: 'lead_submit',
    meta_event_name: 'Lead',
    meta_event_id: eventId,
  });
 
  await submitForm(formData); // Server action
}

On the server:

// app/actions/submit.ts
'use server';
 
export async function submitForm(formData: FormData) {
  const eventId = formData.get('meta_event_id') as string;
 
  await fetch(`https://graph.facebook.com/v19.0/${DATASET_ID}/events...`, {
    method: 'POST',
    body: JSON.stringify({
      data: [
        {
          event_name: 'Lead',
          event_id: eventId, // Must match browser Pixel eventID
          event_time: Math.floor(Date.now() / 1000),
          action_source: 'website',
          // ...
        },
      ],
    }),
  });
}

Verifying deduplication in Meta Events Manager

  1. Go to Meta Events Manager → your Pixel → Test Events tab
  2. Submit a test conversion
  3. Look for the event under "Server" and "Browser": they should show as one combined event with a dedup indicator
  4. The "Received" count in the main Events view should show 1, not 2

Common mistakes

  • Generating a new event_id on the server instead of using the one from the browser
  • Sending the CAPI event significantly later than the browser event (Meta accepts dedup within ~7 days but real-time is more reliable)
  • Using the same event_id for multiple conversions (must be unique per conversion instance)

The free Meta CAPI Base Container includes a pre-built GTM container for the browser Pixel side of this setup.

Free container for this module

Meta Conversions API Base Container

GTM container for Meta CAPI with browser-Pixel deduplication via shared event_id. Includes the server-side tag, user data variables, and the deduplication strategy that prevents double-counting in Meta Events Manager.

Get the container →