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.
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
- Go to Meta Events Manager → your Pixel → Test Events tab
- Submit a test conversion
- Look for the event under "Server" and "Browser": they should show as one combined event with a dedup indicator
- The "Received" count in the main Events view should show 1, not 2
Common mistakes
- Generating a new
event_idon 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_idfor 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 →