Skip to main content
Back to Blog
analytics

Facebook Conversions API (CAPI) Setup: Step-by-Step Server-Side Tracking

RedClaw Performance Team
3/9/2026
14 min read

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

  1. Why CAPI Matters in 2026
  2. Prerequisites and Access Setup
  3. Method 1: Direct API Integration
  4. Method 2: Server-Side GTM
  5. Method 3: Partner Integrations
  6. Event Deduplication
  7. Event Match Quality Optimization
  8. Testing and Validation
  9. Production Monitoring
  10. FAQ

Why CAPI Matters in 2026

The Pixel-Only Problem

Browser-side Meta Pixel tracking faces multiple failure points:

Failure PointData LossCAPI Recovery
Ad blockers (desktop)15-25%Full recovery
iOS ATT opt-out30-40% on iOSPartial recovery via matching
Safari ITP (7-day cookies)10-20% long-cycle conversionsFull recovery
Browser crashes / tab closes3-5%Full recovery
JavaScript errors2-5%Full recovery
VPN / Private browsing5-10%Partial recovery
Total potential loss30-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:

  1. Meta Business Manager admin access (or developer role on the Pixel)
  2. Pixel ID from Events Manager > Data Sources
  3. Access Token generated in Events Manager or via the Meta API
  4. Server infrastructure (your own server, cloud function, or server-side GTM container)

Generating an Access Token

  1. Go to Events Manager > Select your Pixel
  2. Click Settings tab
  3. Scroll to Conversions API section
  4. Click Generate Access Token
  5. 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

  1. In GTM, create a new container with type "Server"
  2. Deploy to Google Cloud Run (recommended) or your own server
  3. 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:

  1. Add the "Facebook Conversions API" tag template from the Community Template Gallery
  2. Configure with your Pixel ID and Access Token
  3. Map event names and parameters
  4. 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:

PlatformCAPI IntegrationSetup ComplexityCustomization
ShopifyNative (automatic)LowLimited
WooCommercePlugin (PixelYourSite)LowModerate
WordPressPlugin (PixelYourSite, JEEB)LowModerate
MagentoExtensionMediumHigh
Next.js / CustomDirect API or Server GTMHighFull
BigCommerceNativeLowLimited
ZapierWebhook integrationLowModerate

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:

  1. event_id: A unique identifier for each event instance
  2. 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

  1. Go to Events Manager > Your Pixel > Test Events tab
  2. Copy the Test Event Code (e.g., TEST12345)
  3. Add it to your CAPI payload:
const payload = {
  data: [{ /* your event */ }],
  access_token: ACCESS_TOKEN,
  test_event_code: 'TEST12345'  // Only for testing!
};
  1. Send a test event and verify it appears in the Test Events tab
  2. 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

  1. Events received (daily/hourly trend)
  2. Event Match Quality (weekly check)
  3. Deduplication rate (should be 40-60% if both Pixel and CAPI are working)
  4. API error rate (target under 1%)
  5. 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


Explore our tracking & analytics services →

Share:

Maximize Your Ad Budget ROI

From account setup to full-funnel tracking, we handle it all.

  • Dedicated account manager with real-time optimization
  • Full tracking infrastructure — every dollar accounted for
  • Cross-platform expertise: Meta, Google, TikTok

📬 Subscribe to Our Newsletter

Weekly insights on ad strategies, industry trends, and practical tips. No fluff.

We never share your email. Unsubscribe anytime.