Facebook Conversions API (CAPI) Setup: Step-by-Step Server-Side Tracking
Facebook Conversions API (CAPI) Setup: Step-by-Step Server-Side Tracking
Meta's Conversions API (CAPI) sends conversion events directly from your server to Meta's servers, bypassing the browser entirely. In 2026, CAPI is no longer optional -- it is the primary way to maintain accurate conversion tracking for Meta advertising campaigns. Advertisers running Pixel-only tracking are missing 20-40% of their conversion data due to ad blockers, browser privacy features, and iOS restrictions.
This guide provides a complete, step-by-step implementation of CAPI, covering three different integration methods, event deduplication with the Pixel, Event Match Quality optimization, and real-world troubleshooting.
Why This Matters: Meta's ad delivery algorithm optimizes based on conversion signals. Fewer signals mean slower learning, higher CPAs, and worse campaign performance. CAPI recovers the signals that browser-side tracking loses, directly improving your ROAS.
Table of Contents
- Why CAPI Matters in 2026
- Prerequisites and Access Setup
- Method 1: Direct API Integration
- Method 2: Server-Side GTM
- Method 3: Partner Integrations
- Event Deduplication
- Event Match Quality Optimization
- Testing and Validation
- Production Monitoring
- FAQ
Why CAPI Matters in 2026
The Pixel-Only Problem
Browser-side Meta Pixel↗ tracking faces multiple failure points:
| Failure Point | Data Loss | CAPI Recovery |
|---|---|---|
| Ad blockers (desktop) | 15-25% | Full recovery |
| iOS ATT opt-out | 30-40% on iOS | Partial recovery via matching |
| Safari ITP (7-day cookies) | 10-20% long-cycle conversions | Full recovery |
| Browser crashes / tab closes | 3-5% | Full recovery |
| JavaScript errors | 2-5% | Full recovery |
| VPN / Private browsing | 5-10% | Partial recovery |
| Total potential loss | 30-60% | Recovers 25-40% |
CAPI Performance Impact
Advertisers who implement CAPI alongside Pixel typically see:
- 15-25% improvement in reported conversions
- 10-20% reduction in CPA (more signals = better optimization)
- 5-15% improvement in ROAS
- Higher Event Match Quality scores (better audience matching)
For the full picture on dual tracking architecture, see our Pixel + CAPI Dual Tracking Setup guide.
Prerequisites and Access Setup
Required Access Levels
Before implementing CAPI, ensure you have:
- Meta Business Manager admin access (or developer role on the Pixel)
- Pixel ID from Events Manager > Data Sources
- Access Token generated in Events Manager or via the Meta API
- Server infrastructure (your own server, cloud function, or server-side GTM↗ container)
Generating an Access Token
- Go to Events Manager > Select your Pixel
- Click Settings tab
- Scroll to Conversions API section
- Click Generate Access Token
- Copy and store the token securely (it will not be shown again)
Security note: The access token grants permission to send events to your Pixel. Store it as an environment variable, never hardcode it in client-side JavaScript.
# Store as environment variable
export META_CAPI_ACCESS_TOKEN="your_access_token_here"
export META_PIXEL_ID="your_pixel_id_here"
For a complete Meta Business Manager setup walkthrough, refer to our Meta Business Manager Setup Guide.
Method 1: Direct API Integration
The API Endpoint
CAPI uses Meta's Graph API. All events are sent via POST request:
POST https://graph.facebook.com/v19.0/{PIXEL_ID}/events
Sending a Purchase Event
Here is a complete, production-ready example of sending a purchase event:
// Node.js / Express server-side implementation
const crypto = require('crypto');
const fetch = require('node-fetch');
const PIXEL_ID = process.env.META_PIXEL_ID;
const ACCESS_TOKEN = process.env.META_CAPI_ACCESS_TOKEN;
// SHA-256 hashing function (required for all user data)
function hashSHA256(value) {
if (!value) return null;
return crypto
.createHash('sha256')
.update(value.trim().toLowerCase())
.digest('hex');
}
async function sendPurchaseEvent(orderData, userData, requestData) {
const eventId = `purchase_${orderData.orderId}_${Date.now()}`;
const payload = {
data: [
{
event_name: 'Purchase',
event_time: Math.floor(Date.now() / 1000),
event_id: eventId, // CRITICAL for deduplication
event_source_url: orderData.pageUrl,
action_source: 'website',
user_data: {
em: [hashSHA256(userData.email)],
ph: [hashSHA256(userData.phone)],
fn: [hashSHA256(userData.firstName)],
ln: [hashSHA256(userData.lastName)],
ct: [hashSHA256(userData.city)],
st: [hashSHA256(userData.state)],
zp: [hashSHA256(userData.zipCode)],
country: [hashSHA256(userData.country)],
external_id: [hashSHA256(userData.externalId)],
client_ip_address: requestData.ipAddress,
client_user_agent: requestData.userAgent,
fbc: requestData.fbc, // Facebook click ID from cookie
fbp: requestData.fbp // Facebook browser ID from cookie
},
custom_data: {
value: orderData.value,
currency: orderData.currency,
content_type: 'product',
contents: orderData.items.map(item => ({
id: item.sku,
quantity: item.quantity,
item_price: item.price
})),
order_id: orderData.orderId,
num_items: orderData.items.length
}
}
],
access_token: ACCESS_TOKEN
};
try {
const response = await fetch(
`https://graph.facebook.com/v19.0/${PIXEL_ID}/events`,
{
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload)
}
);
const result = await response.json();
if (result.events_received) {
console.log(`CAPI: ${result.events_received} events received`);
} else {
console.error('CAPI Error:', result.error);
}
return result;
} catch (error) {
console.error('CAPI Request Failed:', error);
// Implement retry logic for production
}
}
Sending Standard Events
Implement these standard events for optimal Meta optimization:
// PageView event
async function sendPageView(pageUrl, userData, requestData) {
return sendEvent('PageView', {
event_source_url: pageUrl,
user_data: buildUserData(userData, requestData),
custom_data: {}
});
}
// ViewContent event
async function sendViewContent(product, userData, requestData) {
return sendEvent('ViewContent', {
event_source_url: product.url,
user_data: buildUserData(userData, requestData),
custom_data: {
content_name: product.name,
content_category: product.category,
content_ids: [product.id],
content_type: 'product',
value: product.price,
currency: 'USD'
}
});
}
// AddToCart event
async function sendAddToCart(item, userData, requestData) {
return sendEvent('AddToCart', {
event_source_url: item.pageUrl,
user_data: buildUserData(userData, requestData),
custom_data: {
content_ids: [item.id],
content_type: 'product',
value: item.price * item.quantity,
currency: 'USD',
contents: [{ id: item.id, quantity: item.quantity }]
}
});
}
// Lead event
async function sendLead(formData, userData, requestData) {
return sendEvent('Lead', {
event_source_url: formData.pageUrl,
user_data: buildUserData(userData, requestData),
custom_data: {
content_name: formData.formName,
content_category: formData.leadType,
value: formData.estimatedValue,
currency: 'USD'
}
});
}
// Reusable user data builder
function buildUserData(userData, requestData) {
return {
em: userData.email ? [hashSHA256(userData.email)] : undefined,
ph: userData.phone ? [hashSHA256(userData.phone)] : undefined,
fn: userData.firstName ? [hashSHA256(userData.firstName)] : undefined,
ln: userData.lastName ? [hashSHA256(userData.lastName)] : undefined,
external_id: userData.externalId
? [hashSHA256(userData.externalId)]
: undefined,
client_ip_address: requestData.ipAddress,
client_user_agent: requestData.userAgent,
fbc: requestData.fbc,
fbp: requestData.fbp
};
}
Method 2: Server-Side GTM
Server-Side GTM Architecture
Server-side GTM places a GTM container on your server (or cloud run instance), acting as a proxy between your website and Meta:
Client-Side GTM → HTTP Request → Server-Side GTM → Meta CAPI
→ [GA4](https://developers.google.com/analytics)
→ Other platforms
Setup Steps
Step 1: Create a server-side GTM container
- In GTM, create a new container with type "Server"
- Deploy to Google Cloud Run (recommended) or your own server
- Configure a custom domain (e.g.,
track.yourdomain.com)
Step 2: Configure the GA4 client
The GA4 client in server-side GTM receives events from your client-side GA4 tag and makes them available for processing.
Step 3: Add the Meta CAPI tag
In your server-side GTM container:
- Add the "Facebook Conversions API" tag template from the Community Template Gallery
- Configure with your Pixel ID and Access Token
- Map event names and parameters
- Set user data parameters from the incoming request
Server-Side GTM Tag Configuration:
├── Tag Type: Facebook Conversions API
├── Pixel ID: {{Meta Pixel ID}}
├── API Access Token: {{Meta Access Token}}
├── Event Name: {{Event Name}} (mapped from GA4 event)
├── Event ID: {{Event ID}} (for deduplication)
├── User Data:
│ ├── Email: {{User Email Hashed}}
│ ├── Phone: {{User Phone Hashed}}
│ ├── IP Address: {{Client IP}}
│ ├── User Agent: {{Client User Agent}}
│ ├── fbc: {{FBC Cookie}}
│ └── fbp: {{FBP Cookie}}
└── Custom Data:
├── Value: {{Event Value}}
├── Currency: {{Event Currency}}
└── Content IDs: {{Content IDs}}
Advantage of server-side GTM: You manage all your server-side tags (Meta CAPI, Google Ads↗ Enhanced Conversions, TikTok Events API) from one interface, using the same GTM workflow your team already knows.
Method 3: Partner Integrations
Platform-Specific CAPI Integrations
Many platforms offer built-in CAPI integrations that require minimal technical setup:
| Platform | CAPI Integration | Setup Complexity | Customization |
|---|---|---|---|
| Shopify | Native (automatic) | Low | Limited |
| WooCommerce | Plugin (PixelYourSite) | Low | Moderate |
| WordPress | Plugin (PixelYourSite, JEEB) | Low | Moderate |
| Magento | Extension | Medium | High |
| Next.js / Custom | Direct API or Server GTM | High | Full |
| BigCommerce | Native | Low | Limited |
| Zapier | Webhook integration | Low | Moderate |
For custom Next.js applications (like RedClaw's platform), the direct API integration (Method 1) or server-side GTM (Method 2) provides the most control and reliability.
Event Deduplication
Why Deduplication Is Critical
When running both Pixel (browser-side) and CAPI (server-side), the same conversion event is sent twice. Without deduplication, Meta counts each purchase twice, inflating your conversion numbers and corrupting campaign optimization.
How Deduplication Works
Meta deduplicates events using two fields:
- event_id: A unique identifier for each event instance
- event_name: The event type (Purchase, Lead, etc.)
If Meta receives two events with the same event_name AND same event_id within a 48-hour window, it keeps only one.
// CRITICAL: Generate event_id ONCE and use it for BOTH Pixel and CAPI
// Step 1: Generate unique event ID on your server or in the data layer
const eventId = `purchase_${orderId}_${Date.now()}`;
// Step 2: Pass to browser-side Pixel
// In your page HTML or data layer:
window.dataLayer.push({
'event': 'purchase',
'event_id': eventId, // Pass to GTM for Pixel tag
'transaction_id': orderId,
'value': 99.99
});
// The GTM Pixel tag should include eventID:
// fbq('track', 'Purchase', {...}, { eventID: eventId });
// Step 3: Pass the SAME event_id to CAPI
await sendCAPIEvent({
event_name: 'Purchase',
event_id: eventId, // MUST match the Pixel eventID
// ... rest of payload
});
Common Deduplication Mistakes
// MISTAKE 1: Different event_id values for Pixel and CAPI
// Pixel sends: eventID: 'pixel_abc123'
// CAPI sends: event_id: 'capi_abc123'
// Result: Both counted (no deduplication)
// MISTAKE 2: No event_id at all
// fbq('track', 'Purchase', { value: 99.99 }); // No eventID
// Result: No deduplication possible
// MISTAKE 3: Reusing event_id across different purchases
// Two different orders using event_id: 'purchase_event'
// Result: Second purchase is deduplicated (lost)
// CORRECT: Unique per event instance, same across Pixel and CAPI
const eventId = `purchase_${orderId}_${timestamp}`;
Event Match Quality Optimization
Understanding EMQ
Event Match Quality (EMQ) measures how well Meta can match your server events to Meta user profiles. A higher EMQ means better conversion attribution and campaign optimization.
EMQ scoring:
- 1-3: Poor (limited matching ability)
- 4-6: Moderate (basic matching, significant gaps)
- 7-8: Good (most events matched)
- 9-10: Excellent (near-complete matching)
Improving Your EMQ Score
Each user data parameter you send increases match potential:
// Minimum viable user data (EMQ ~4-5)
user_data: {
client_ip_address: ipAddress,
client_user_agent: userAgent
}
// Good user data (EMQ ~6-7)
user_data: {
client_ip_address: ipAddress,
client_user_agent: userAgent,
fbc: fbcCookie, // Facebook click ID
fbp: fbpCookie, // Facebook browser ID
external_id: [hashedUserId]
}
// Excellent user data (EMQ ~8-10)
user_data: {
em: [hashSHA256(email)], // Hashed email
ph: [hashSHA256(phone)], // Hashed phone
fn: [hashSHA256(firstName)], // Hashed first name
ln: [hashSHA256(lastName)], // Hashed last name
ct: [hashSHA256(city)], // Hashed city
zp: [hashSHA256(zipCode)], // Hashed zip
country: [hashSHA256(country)], // Hashed country code
external_id: [hashSHA256(userId)], // Hashed external ID
client_ip_address: ipAddress,
client_user_agent: userAgent,
fbc: fbcCookie,
fbp: fbpCookie
}
Capturing fbc and fbp
The fbc (click ID) and fbp (browser ID) cookies are set by the Meta Pixel. Pass them to your server for CAPI events:
// Client-side: Read Meta cookies and include in form/API calls
function getMetaCookies() {
const cookies = document.cookie.split(';').reduce((acc, cookie) => {
const [key, value] = cookie.trim().split('=');
acc[key] = value;
return acc;
}, {});
return {
fbc: cookies['_fbc'] || null,
fbp: cookies['_fbp'] || null
};
}
// Include in checkout/form submission
const metaCookies = getMetaCookies();
fetch('/api/purchase', {
method: 'POST',
body: JSON.stringify({
orderData: { /* ... */ },
fbc: metaCookies.fbc,
fbp: metaCookies.fbp
})
});
For a deeper dive into how CAPI integrates with the broader Meta advertising ecosystem, see our Meta Ads Complete Guide.
Testing and Validation
Using Meta's Test Events Tool
- Go to Events Manager > Your Pixel > Test Events tab
- Copy the Test Event Code (e.g.,
TEST12345) - Add it to your CAPI payload:
const payload = {
data: [{ /* your event */ }],
access_token: ACCESS_TOKEN,
test_event_code: 'TEST12345' // Only for testing!
};
- Send a test event and verify it appears in the Test Events tab
- Remove the test_event_code before going to production -- test events are not counted for optimization
Validation Checklist
Before going live with CAPI:
- Events appear in Events Manager with correct event names
- Event Match Quality is 6+ (target 8+)
- Deduplication is working (check Events Manager > Overview for "Deduplicated" column)
- All standard events are mapped correctly (Purchase, Lead, AddToCart, etc.)
- Currency and value parameters are correct data types
- User data is properly hashed (SHA-256, lowercase, trimmed)
- fbc and fbp cookies are captured and forwarded
- Test event code is removed from production payload
- Error handling and retry logic is in place
For verification of your entire conversion tracking stack, see our Conversion Tracking Complete Guide.
Production Monitoring
Monitoring CAPI Health
Set up ongoing monitoring to catch issues before they impact campaign performance:
// Server-side CAPI health monitoring
async function monitorCAPIHealth() {
const last24h = await getCAPIEventCounts('24h');
const previous24h = await getCAPIEventCounts('previous_24h');
const dropRate = (previous24h - last24h) / previous24h * 100;
if (dropRate > 30) {
alert('CRITICAL: CAPI events dropped by ' + dropRate.toFixed(1) + '%');
} else if (dropRate > 15) {
alert('WARNING: CAPI events dropped by ' + dropRate.toFixed(1) + '%');
}
// Check error rate
const errorRate = await getCAPIErrorRate('24h');
if (errorRate > 5) {
alert('WARNING: CAPI error rate at ' + errorRate.toFixed(1) + '%');
}
}
Key Metrics to Track
- Events received (daily/hourly trend)
- Event Match Quality (weekly check)
- Deduplication rate (should be 40-60% if both Pixel and CAPI are working)
- API error rate (target under 1%)
- Response time (target under 2 seconds)
CAPI implementation getting complex? RedClaw's tracking team handles the full setup -- from API integration to deduplication to EMQ optimization. We have implemented CAPI for 50+ advertisers across e-commerce, SaaS, and lead generation. Get a free tracking audit
FAQ
Can I use CAPI without the Meta Pixel?
Technically yes, but it is not recommended. CAPI alone misses real-time behavioral signals like page browsing patterns and scroll depth that only browser-side tracking can capture. The best approach is running both Pixel and CAPI together with proper deduplication. The Pixel captures real-time browser interactions while CAPI ensures high-priority conversion events are never lost.
How do I handle CAPI for sites without user login?
For anonymous visitors, rely on fbp (Facebook browser ID cookie), fbc (click ID cookie), IP address, and user agent for matching. These parameters alone can achieve an EMQ of 5-6. For purchase events, the checkout form provides email and phone which boost EMQ to 8+. For lead forms, capture email early in the form flow and send it with the Lead event.
What happens if my CAPI server goes down?
If CAPI events fail to send, those conversions are not recorded server-side. Implement these safeguards: retry logic with exponential backoff (retry 3 times over 5 minutes), a dead letter queue for failed events (resend when service recovers), and health monitoring with alerts. The Pixel still captures browser-side events as a fallback, but you lose the recovery benefit of CAPI during downtime.
Should I send PageView events via CAPI?
It depends on your goals. Sending PageView via CAPI increases the total data Meta receives and can improve audience matching for retargeting. However, it also increases your API call volume significantly (every page load = an API call). For most advertisers, sending only high-value events (ViewContent, AddToCart, Purchase, Lead) via CAPI is sufficient. Add PageView if you have high-traffic campaigns where audience quality is critical.
How does CAPI work with Google Consent Mode?
CAPI operates server-side and is separate from Google Consent Mode, which applies to client-side Google tags. However, you must still respect user consent for CAPI. If a user denies advertising consent, do not send their data via CAPI. Your server should check the user's consent status (passed from the browser or CMP) before firing CAPI events. This is a legal requirement under GDPR and similar regulations.
Stop losing 30% of your Meta conversion data. Server-side tracking is not optional anymore. Let our experts implement CAPI correctly the first time. Check your ROAS
Related reading: Pixel + CAPI Dual Tracking Setup | Meta Ads Complete Guide | Conversion Tracking Complete Guide | GA4 Setup Complete Guide | UTM Parameters Usage Guide
Related Posts
iGaming Social Media Marketing: Complete Guide to Brand Growth & Player Engagement in 2026
Master iGaming social media marketing with proven strategies for Facebook, Instagram, Twitter, Telegram & more. Learn content creation, engagement tactics, compliance guidelines & build a high-converting social media system.
iGaming Audience Targeting: Advanced Lookalike Strategies for 2026
Master advanced Lookalike Audience strategies for iGaming advertising. Learn seed audience optimization, layering techniques, and signal-based targeting to maximize player acquisition ROAS.
Meta Ads Trends 2026: 7 Changes Every Advertiser Must Know
2026 Meta Ads trends: AI automation, Advantage+ expansion, Threads Ads, privacy-first attribution. Actionable strategies for e-commerce, iGaming & SaaS.