Meta Pixel on Commercetools | OpsBlu Docs

Meta Pixel on Commercetools

Implementing Facebook/Instagram Pixel on Commercetools headless commerce storefronts

This guide covers implementing Meta Pixel (Facebook Pixel) on Commercetools headless commerce for Facebook and Instagram advertising optimization.

Prerequisites

  1. Create Meta Pixel

    • Go to Meta Business Suite
    • Events Manager → Connect Data Sources → Web → Meta Pixel
    • Copy your Pixel ID (15-16 digit number)
  2. Conversions API Access Token (for server-side)

    • Events Manager → Settings → Generate Access Token

Client-Side Implementation

Next.js / React

// components/MetaPixel.tsx
'use client';

import Script from 'next/script';
import { usePathname, useSearchParams } from 'next/navigation';
import { useEffect } from 'react';

declare global {
  interface Window {
    fbq: (...args: any[]) => void;
    _fbq: any;
  }
}

interface MetaPixelProps {
  pixelId: string;
}

export function MetaPixel({ pixelId }: MetaPixelProps) {
  const pathname = usePathname();
  const searchParams = useSearchParams();

  useEffect(() => {
    // Track page views on route change
    if (window.fbq) {
      window.fbq('track', 'PageView');
    }
  }, [pathname, searchParams]);

  return (
    <>
      <Script id="meta-pixel" strategy="afterInteractive">
        {`
          !function(f,b,e,v,n,t,s)
          {if(f.fbq)return;n=f.fbq=function(){n.callMethod?
          n.callMethod.apply(n,arguments):n.queue.push(arguments)};
          if(!f._fbq)f._fbq=n;n.push=n;n.loaded=!0;n.version='2.0';
          n.queue=[];t=b.createElement(e);t.async=!0;
          t.src=v;s=b.getElementsByTagName(e)[0];
          s.parentNode.insertBefore(t,s)}(window, document,'script',
          'https://connect.facebook.net/en_US/fbevents.js');
          fbq('init', '${pixelId}');
          fbq('track', 'PageView');
        `}
      </Script>
      <noscript>
        <img
          height="1"
          width="1"
          style={{ display: 'none' }}
          src={`https://www.facebook.com/tr?id=${pixelId}&ev=PageView&noscript=1`}
          alt=""
        />
      </noscript>
    </>
  );
}

Create Tracking Hook

// hooks/useMetaPixel.ts
export function useMetaPixel() {
  const track = (eventName: string, params?: Record<string, any>) => {
    if (typeof window !== 'undefined' && window.fbq) {
      window.fbq('track', eventName, params);
    }
  };

  const trackCustom = (eventName: string, params?: Record<string, any>) => {
    if (typeof window !== 'undefined' && window.fbq) {
      window.fbq('trackCustom', eventName, params);
    }
  };

  return { track, trackCustom };
}

E-commerce Event Implementation

// hooks/useMetaEcommerce.ts
import { useMetaPixel } from './useMetaPixel';
import { ProductProjection, Cart, Order } from '@commercetools/platform-sdk';

export function useMetaEcommerce() {
  const { track } = useMetaPixel();

  const trackViewContent = (product: ProductProjection) => {
    const variant = product.masterVariant;
    const price = variant.prices?.[0];

    track('ViewContent', {
      content_ids: [product.id],
      content_name: product.name['en-US'],
      content_type: 'product',
      content_category: product.categories?.[0]?.obj?.name?.['en-US'],
      value: price ? price.value.centAmount / 100 : 0,
      currency: price?.value.currencyCode || 'USD'
    });
  };

  const trackAddToCart = (product: ProductProjection, quantity: number) => {
    const variant = product.masterVariant;
    const price = variant.prices?.[0];

    track('AddToCart', {
      content_ids: [product.id],
      content_name: product.name['en-US'],
      content_type: 'product',
      value: price ? (price.value.centAmount / 100) * quantity : 0,
      currency: price?.value.currencyCode || 'USD',
      num_items: quantity
    });
  };

  const trackInitiateCheckout = (cart: Cart) => {
    track('InitiateCheckout', {
      content_ids: cart.lineItems.map(item => item.productId),
      content_type: 'product',
      value: cart.totalPrice.centAmount / 100,
      currency: cart.totalPrice.currencyCode,
      num_items: cart.lineItems.reduce((sum, item) => sum + item.quantity, 0)
    });
  };

  const trackAddPaymentInfo = (cart: Cart) => {
    track('AddPaymentInfo', {
      content_ids: cart.lineItems.map(item => item.productId),
      content_type: 'product',
      value: cart.totalPrice.centAmount / 100,
      currency: cart.totalPrice.currencyCode
    });
  };

  const trackPurchase = (order: Order) => {
    track('Purchase', {
      content_ids: order.lineItems.map(item => item.productId),
      content_type: 'product',
      value: order.totalPrice.centAmount / 100,
      currency: order.totalPrice.currencyCode,
      num_items: order.lineItems.reduce((sum, item) => sum + item.quantity, 0),
      contents: order.lineItems.map(item => ({
        id: item.productId,
        quantity: item.quantity,
        item_price: item.price.value.centAmount / 100
      }))
    });
  };

  return {
    trackViewContent,
    trackAddToCart,
    trackInitiateCheckout,
    trackAddPaymentInfo,
    trackPurchase
  };
}

Usage in Components

// Product Detail Page
import { useMetaEcommerce } from '@/hooks/useMetaEcommerce';

export function ProductPage({ product }: Props) {
  const { trackViewContent } = useMetaEcommerce();

  useEffect(() => {
    trackViewContent(product);
  }, [product]);

  return (/* JSX */);
}

// Add to Cart Button
export function AddToCartButton({ product }: Props) {
  const { trackAddToCart } = useMetaEcommerce();
  const { addToCart } = useCart();

  const handleAddToCart = async () => {
    await addToCart(product.id, 1);
    trackAddToCart(product, 1);
  };

  return <button to Cart</button>;
}

// Checkout Success
export function CheckoutSuccess({ order }: Props) {
  const { trackPurchase } = useMetaEcommerce();

  useEffect(() => {
    const tracked = sessionStorage.getItem(`meta_purchase_${order.id}`);
    if (!tracked) {
      trackPurchase(order);
      sessionStorage.setItem(`meta_purchase_${order.id}`, 'true');
    }
  }, [order]);

  return (/* JSX */);
}

Server-Side (Conversions API)

For accurate tracking despite ad blockers, implement the Conversions API:

API Route Handler

// app/api/meta-capi/route.ts
import { NextRequest, NextResponse } from 'next/server';
import crypto from 'crypto';

const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;

function hashData(data: string): string {
  return crypto.createHash('sha256').update(data.toLowerCase()).digest('hex');
}

export async function POST(request: NextRequest) {
  const body = await request.json();
  const { eventName, eventData, userData } = body;

  const payload = {
    data: [{
      event_name: eventName,
      event_time: Math.floor(Date.now() / 1000),
      action_source: 'website',
      event_source_url: eventData.source_url,
      user_data: {
        em: userData.email ? [hashData(userData.email)] : undefined,
        ph: userData.phone ? [hashData(userData.phone)] : undefined,
        fn: userData.firstName ? [hashData(userData.firstName)] : undefined,
        ln: userData.lastName ? [hashData(userData.lastName)] : undefined,
        client_ip_address: request.headers.get('x-forwarded-for')?.split(',')[0],
        client_user_agent: request.headers.get('user-agent'),
        fbc: eventData.fbc,
        fbp: eventData.fbp,
      },
      custom_data: eventData.customData,
    }]
  };

  const response = await fetch(
    `https://graph.facebook.com/v18.0/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );

  const result = await response.json();
  return NextResponse.json(result);
}

Hybrid Client + Server Tracking

// hooks/useMetaHybrid.ts
import { useMetaPixel } from './useMetaPixel';

export function useMetaHybrid() {
  const { track } = useMetaPixel();

  const trackPurchase = async (order: Order, userData?: UserData) => {
    // Client-side tracking
    track('Purchase', {
      content_ids: order.lineItems.map(item => item.productId),
      value: order.totalPrice.centAmount / 100,
      currency: order.totalPrice.currencyCode,
    });

    // Server-side tracking (Conversions API)
    await fetch('/api/meta-capi', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({
        eventName: 'Purchase',
        eventData: {
          source_url: window.location.href,
          fbc: getCookie('_fbc'),
          fbp: getCookie('_fbp'),
          customData: {
            content_ids: order.lineItems.map(item => item.productId),
            value: order.totalPrice.centAmount / 100,
            currency: order.totalPrice.currencyCode,
          }
        },
        userData: {
          email: userData?.email,
          firstName: userData?.firstName,
          lastName: userData?.lastName,
        }
      })
    });
  };

  return { trackPurchase };
}

function getCookie(name: string): string | undefined {
  const value = `; ${document.cookie}`;
  const parts = value.split(`; ${name}=`);
  if (parts.length === 2) return parts.pop()?.split(';').shift();
}

Commercetools Subscription for CAPI

// Cloud Function for server-side purchase tracking
import * as functions from '@google-cloud/functions-framework';
import crypto from 'crypto';

const PIXEL_ID = process.env.META_PIXEL_ID!;
const ACCESS_TOKEN = process.env.META_ACCESS_TOKEN!;

functions.cloudEvent('trackMetaPurchase', async (cloudEvent: any) => {
  const message = JSON.parse(
    Buffer.from(cloudEvent.data.message.data, 'base64').toString()
  );

  const order = message.order;
  const customer = message.customer;

  const hashData = (data: string) =>
    crypto.createHash('sha256').update(data.toLowerCase()).digest('hex');

  const payload = {
    data: [{
      event_name: 'Purchase',
      event_time: Math.floor(Date.now() / 1000),
      action_source: 'website',
      user_data: {
        em: customer?.email ? [hashData(customer.email)] : undefined,
        external_id: order.customerId ? [hashData(order.customerId)] : undefined,
      },
      custom_data: {
        content_ids: order.lineItems.map((item: any) => item.productId),
        content_type: 'product',
        value: order.totalPrice.centAmount / 100,
        currency: order.totalPrice.currencyCode,
        num_items: order.lineItems.reduce(
          (sum: number, item: any) => sum + item.quantity, 0
        ),
      }
    }]
  };

  await fetch(
    `https://graph.facebook.com/v18.0/${PIXEL_ID}/events?access_token=${ACCESS_TOKEN}`,
    {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify(payload)
    }
  );
});

Event Reference

Event When to Fire Key Parameters
PageView Every page load (automatic)
ViewContent Product page content_ids, content_name, value
AddToCart Item added to cart content_ids, value, currency
InitiateCheckout Checkout starts content_ids, value, num_items
AddPaymentInfo Payment entered content_ids, value
Purchase Order complete content_ids, value, currency, num_items

Testing and Validation

Meta Pixel Helper

Install Meta Pixel Helper Chrome extension to validate events.

Events Manager Test Events

  1. Go to Events Manager → Test Events
  2. Enter your website URL
  3. Perform actions on your site
  4. Verify events appear in real-time

Conversions API Diagnostics

Check Events Manager → Data Sources → Your Pixel → Diagnostics for:

  • Event match quality
  • Deduplication status
  • Data freshness

Common Issues

Duplicate Events

When using hybrid (client + server) tracking, add event_id for deduplication:

const eventId = `${order.id}_${Date.now()}`;

// Client-side
fbq('track', 'Purchase', params, { eventID: eventId });

// Server-side
payload.data[0].event_id = eventId;

Missing User Data

Improve match quality by passing hashed user data:

  • Email (most important)
  • Phone number
  • First/Last name
  • Country, City

Next Steps