Audit pixel and Conversions API (CAPI) implementations across all ad platforms. Use when verifying tracking setup, diagnosing conversion data discrepancies, optimizing Event Match Quality, or auditing deduplication logic.
81%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Passed
No findings from the security scan
Comprehensive audit framework for client-side pixels and server-side Conversions APIs across Meta, Google, TikTok, LinkedIn, Reddit, and Microsoft Ads. Covers setup verification, Event Match Quality optimization, deduplication validation, and diagnostic commands.
| Check | How to Verify | Pass Criteria |
|---|---|---|
| Pixel installed | Meta Pixel Helper extension | Pixel fires on all pages |
| Base code present | View page source → search "fbq" | fbq('init', 'PIXEL_ID') found |
| PageView fires | Pixel Helper → PageView event | Fires on every page load |
| Standard events | Pixel Helper → event list | Purchase, Lead, AddToCart as needed |
| CAPI events | Events Manager → Test Events | Server events appear |
| Deduplication | Events Manager → event details | event_id matches pixel + CAPI |
| EMQ score | Events Manager → Event Overview | Score ≥ 6.0 (Good) |
| Domain verification | Business Settings → Brand Safety | Domain verified |
| iOS 14+ setup | Events Manager → AEM | 8 events configured, prioritized |
What is EMQ? A 0-10 score measuring how well Meta can match server events to Facebook users.
| EMQ Score | Rating | Impact on Attribution |
|---|---|---|
| 0-3 | Poor | Significant attribution loss (30-50%) |
| 3-5 | Fair | Moderate attribution loss (15-30%) |
| 5-7 | Good | Minor attribution loss (5-15%) |
| 7-9 | Great | Minimal loss (< 5%) |
| 9-10 | Excellent | Near-perfect matching |
Parameters that improve EMQ (in order of impact):
| Parameter | EMQ Impact | Implementation |
|---|---|---|
em (email, hashed) | +3-4 points | SHA-256 hash of lowercase, trimmed email |
ph (phone, hashed) | +1-2 points | SHA-256 hash of digits only (with country code) |
fn + ln (name, hashed) | +1 point | SHA-256 hash of lowercase first/last name |
external_id | +1 point | Your internal user ID (hashed) |
fbp (browser ID) | +1-2 points | _fbp cookie value |
fbc (click ID) | +2-3 points | _fbc cookie value or fbclid from URL |
client_ip_address | +0.5 points | User's IP (not server IP) |
client_user_agent | +0.5 points | Browser user agent string |
CAPI event payload for maximum EMQ:
{
"data": [{
"event_name": "Purchase",
"event_time": 1706745600,
"event_id": "evt_unique_123",
"event_source_url": "https://example.com/thank-you",
"action_source": "website",
"user_data": {
"em": ["a1b2c3...sha256..."],
"ph": ["d4e5f6...sha256..."],
"fn": ["ab12cd...sha256..."],
"ln": ["ef34gh...sha256..."],
"external_id": ["user_789_hashed"],
"fbc": "fb.1.1706745600.ABCdef",
"fbp": "fb.1.1706745600.1234567890",
"client_ip_address": "203.0.113.1",
"client_user_agent": "Mozilla/5.0..."
},
"custom_data": {
"value": 99.99,
"currency": "USD",
"content_ids": ["SKU-123"],
"content_type": "product",
"num_items": 1
}
}]
}How deduplication works:
Both pixel and CAPI send the same event. Meta deduplicates using event_id + event_name. If both have the same event_id, only one is counted.
Verification steps:
Generate a unique event_id on the server at conversion time:
event_id = "evt_" + UUID or transaction_idPass to both pixel and CAPI:
fbq('track', 'Purchase', {value: 99.99}, {eventID: 'evt_abc123'})"event_id": "evt_abc123" in the server payloadVerify in Events Manager:
Common deduplication failures:
| Symptom | Cause | Fix |
|---|---|---|
| Double-counted conversions | Missing event_id on pixel or CAPI | Add event_id to both |
| Conversion count too low | event_id mismatched (different format) | Ensure identical string on both |
| Intermittent doubles | Race condition, CAPI fires before pixel | Use same server-generated ID |
| CAPI events not matching | Wrong event_name casing | Use exact standard event names |
Test CAPI endpoint:
curl -X POST "https://graph.facebook.com/v21.0/PIXEL_ID/events?access_token=TOKEN" \
-H "Content-Type: application/json" \
-d '{
"data": [{
"event_name": "PageView",
"event_time": '$(($(date +%s)))',
"event_id": "test_'$(uuidgen)'",
"action_source": "website",
"event_source_url": "https://example.com",
"user_data": {
"client_ip_address": "1.2.3.4",
"client_user_agent": "test"
}
}],
"test_event_code": "TEST12345"
}'Check pixel on website:
curl -s "https://example.com" | grep -oP 'fbq\([^)]+\)' | head -20Verify domain:
curl -s "https://example.com" | grep -oP 'meta-tag.*facebook-domain-verification'| Check | How to Verify | Pass Criteria |
|---|---|---|
| Conversion tracking tag | GTM → Tags → Google Ads Conversion | Tag fires on conversion page |
| Enhanced conversions | Tag settings → Enhanced conversions | Enabled with user data |
| Consent mode | GTM → Consent Overview | ad_storage and analytics_storage configured |
| Conversion linker | GTM → Tags → Conversion Linker | Fires on all pages |
| GCLID parameter | Landing page URL | gclid captured and stored |
| Server-side tagging | GTM → Server container | Optional but recommended |
Method 1: Google tag (gtag.js)
gtag('set', 'user_data', {
"email": "user@example.com", // Hashed automatically
"phone_number": "+11234567890",
"address": {
"first_name": "John",
"last_name": "Doe",
"street": "123 Main St",
"city": "New York",
"region": "NY",
"postal_code": "10001",
"country": "US"
}
});Method 2: GTM with CSS selectors
Method 3: Enhanced Conversions API (server-side)
curl -X POST "https://googleads.googleapis.com/v17/customers/CUSTOMER_ID:uploadConversionAdjustments" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "developer-token: DEV_TOKEN" \
-d '{
"conversionAdjustments": [{
"adjustmentType": "ENHANCEMENT",
"conversionAction": "customers/CUSTOMER_ID/conversionActions/ACTION_ID",
"adjustmentDateTime": "2026-01-15 12:00:00-05:00",
"orderId": "ORDER-123",
"userIdentifiers": [{
"hashedEmail": "a1b2c3...sha256..."
}]
}]
}'Required signals for EEA/UK:
| Signal | Purpose | Default (no consent) |
|---|---|---|
ad_storage | Google Ads cookies | denied |
ad_user_data | Sending user data to Google | denied |
ad_personalization | Remarketing | denied |
analytics_storage | GA4 cookies | denied |
Implementation:
// Before gtag/GTM loads:
window.dataLayer = window.dataLayer || [];
function gtag(){dataLayer.push(arguments);}
gtag('consent', 'default', {
'ad_storage': 'denied',
'ad_user_data': 'denied',
'ad_personalization': 'denied',
'analytics_storage': 'denied',
'wait_for_update': 500
});
// After user grants consent:
gtag('consent', 'update', {
'ad_storage': 'granted',
'ad_user_data': 'granted',
'ad_personalization': 'granted',
'analytics_storage': 'granted'
});| Check | How to Verify | Pass Criteria |
|---|---|---|
| Pixel installed | TikTok Pixel Helper extension | Pixel fires on pages |
| Base code | View source → search "ttq" | ttq.load('PIXEL_CODE') found |
| Standard events | Pixel Helper → event list | CompleteRegistration, Purchase, etc. |
| Events API | Events Manager → Diagnostics | Server events arriving |
| Match quality | Events Manager → Match Rate | ≥ 50% match rate |
ttclid capture | URL parameter check | Captured and stored in cookie |
{
"pixel_code": "PIXEL_CODE",
"event": "CompletePayment",
"event_id": "evt_unique_456",
"timestamp": "2026-01-15T12:00:00-05:00",
"context": {
"ad": {
"callback": "ttclid_value_from_url"
},
"page": {
"url": "https://example.com/thank-you",
"referrer": "https://example.com/checkout"
},
"user": {
"external_id": "user_789_sha256",
"email": "sha256_hashed_email",
"phone_number": "sha256_hashed_phone"
},
"user_agent": "Mozilla/5.0...",
"ip": "203.0.113.1"
},
"properties": {
"value": 99.99,
"currency": "USD",
"contents": [{"content_id": "SKU-123", "quantity": 1, "price": 99.99}],
"content_type": "product"
}
}Endpoint:
curl -X POST "https://business-api.tiktok.com/open_api/v1.3/event/track/" \
-H "Content-Type: application/json" \
-H "Access-Token: SERVER_ACCESS_TOKEN" \
-d '{ ... payload ... }'| Check | How to Verify | Pass Criteria |
|---|---|---|
| Insight Tag | View source → search "linkedin" | _linkedin_partner_id or tag present |
| Partner ID | LinkedIn Campaign Manager → Analyze | Matches installed tag |
| Conversion tracking | Campaign Manager → Conversions | Events configured |
| CAPI setup | API test call | Events accepted |
li_fat_id capture | Cookie check | First-party cookie stored |
{
"conversion": "urn:lla:llaPartnerConversion:CONVERSION_RULE_ID",
"conversionHappenedAt": 1706745600000,
"conversionValue": {
"currencyCode": "USD",
"amount": "99.99"
},
"eventId": "evt_unique_789",
"user": {
"userIds": [
{
"idType": "SHA256_EMAIL",
"idValue": "sha256_hashed_email"
},
{
"idType": "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID",
"idValue": "li_fat_id_value"
}
],
"userInfo": {
"firstName": "John",
"lastName": "Doe",
"companyName": "Acme Inc",
"title": "VP Marketing",
"countryCode": "US"
}
}
}Endpoint:
curl -X POST "https://api.linkedin.com/rest/conversionEvents" \
-H "Authorization: Bearer ACCESS_TOKEN" \
-H "LinkedIn-Version: 202510" \
-H "X-Restli-Protocol-Version: 2.0.0" \
-H "Content-Type: application/json" \
-d '{"elements": [{ ... payload ... }]}'| Check | How to Verify | Pass Criteria |
|---|---|---|
| Reddit Pixel | View source → search "rdt" | rdt('init', 'PIXEL_ID') found |
| PageVisit event | Pixel fires | rdt('track', 'PageVisit') on all pages |
rdt_cid capture | URL parameter | Reddit click ID stored in cookie |
_rdt_uuid cookie | Cookie check | UUID cookie present |
| CAPI events | Reddit Events Manager | Server events in diagnostics |
{
"events": [{
"event_at": "2026-01-15T12:00:00Z",
"event_type": {
"tracking_type": "Purchase"
},
"click_id": "rdt_cid_value",
"event_metadata": {
"item_count": 1,
"value_decimal": 99.99,
"currency": "USD",
"conversion_id": "evt_unique_012"
},
"user": {
"email": "sha256_hashed_email",
"external_id": "user_789",
"uuid": "_rdt_uuid_value",
"ip_address": "203.0.113.1",
"user_agent": "Mozilla/5.0..."
}
}]
}Endpoint:
curl -X POST "https://ads-api.reddit.com/api/v2.0/conversions/events/PIXEL_ID" \
-H "Authorization: Bearer CAPI_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{ ... payload ... }'| Check | How to Verify | Pass Criteria |
|---|---|---|
| UET tag | View source → search "bat.bing.com" | UET tag fires |
| Tag ID | Microsoft Ads → Conversion Tracking | Matches installed tag |
msclkid auto-tagging | URL parameter check | Enabled in account settings |
| Offline conversions | API test call | Events accepted |
| Revenue tracking | Conversion goal settings | Revenue values reported |
{
"offlineConversions": [{
"conversionName": "Purchase",
"conversionTime": "2026-01-15T12:00:00.000Z",
"conversionValue": 99.99,
"conversionCurrencyCode": "USD",
"microsoftClickId": "msclkid_value",
"hashedEmailAddress": "sha256_hashed_email",
"hashedPhoneNumber": "sha256_hashed_phone"
}]
}| Platform | URL Parameter | Cookie Name | Lifetime |
|---|---|---|---|
| Meta | fbclid | _fbc, _fbp | 90 days, 2 years |
gclid | _gcl_aw | 90 days | |
| TikTok | ttclid | _ttp | 13 months |
li_fat_id | li_fat_id | 30 days | |
rdt_cid | _rdt_uuid | 90 days | |
| Microsoft | msclkid | _uetmsclkid | 90 days |
| X (Twitter) | twclid | N/A | Session |
Capture all click IDs in a single middleware:
On every request:
1. Check URL params for: fbclid, gclid, ttclid, li_fat_id, rdt_cid, msclkid, twclid
2. Store each found ID in a first-party cookie
3. On conversion event, read all cookies
4. Send to each platform's CAPI with matching click ID
5. Use single event_id/conversion_id for deduplicationWhen a conversion happens, fire to all platforms simultaneously:
Event: Purchase ($99.99)
event_id: "evt_txn_abc123"
→ Meta CAPI: event_id="evt_txn_abc123", fbc="{_fbc}", fbp="{_fbp}"
→ Google Enhanced Conversions: order_id="ORDER-123", gclid="{gclid}"
→ TikTok Events API: event_id="evt_txn_abc123", ttclid="{ttclid}"
→ LinkedIn CAPI: eventId="evt_txn_abc123", li_fat_id="{li_fat_id}"
→ Reddit CAPI: conversion_id="evt_txn_abc123", click_id="{rdt_cid}"
→ Microsoft Offline: microsoftClickId="{msclkid}"Before: EMQ Score 3.2 (Poor)
client_ip_address, client_user_agentAfter adding all parameters: EMQ Score 8.1 (Great)
Symptom: Google Ads reporting 200 conversions, GA4 reporting 130.
Root cause: Enhanced conversions firing without order_id, so duplicate events from gtag + server-side upload counted twice.
Fix: Added transaction_id / order_id to both gtag conversion tag and server-side upload. Google deduplicates on order_id.
Result: Google Ads now reports 135 conversions (matches GA4 within 4%).
Audit results for an e-commerce site:
| Platform | Pixel | CAPI | Dedup | EMQ/Match | Action Needed |
|---|---|---|---|---|---|
| Meta | ✅ | ✅ | ❌ | 4.2 | Add event_id, email hash |
| ✅ | ⚠️ | ✅ | N/A | Enable enhanced conversions | |
| TikTok | ✅ | ❌ | N/A | N/A | Implement Events API |
| ❌ | ❌ | N/A | N/A | Install Insight Tag + CAPI | |
| ✅ | ❌ | N/A | N/A | Implement CAPI | |
| Microsoft | ✅ | ❌ | N/A | N/A | Enable auto-tagging + offline |
Priority order: Meta CAPI fix → Google Enhanced → TikTok CAPI → LinkedIn full setup → Reddit CAPI → Microsoft offline
Part of the Synter free skills collection — open-source agent skills for advertising, PPC, and marketing automation.
Want this skill (and 40+ more) running on autopilot against your live ad accounts? Try Synter — AI Agent Media Buyers that connect to Google, Meta, LinkedIn, TikTok, Reddit, Amazon, and 7+ more platforms.
Synter-Media-AI/free-skills
3df48f1
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.