Meta — Integration guide
Self-contained setup for every popular stack. Pick the section that matches your site, follow the four steps, and you're done.
pb.js and fire Meta Conversions API events from five common stacks. Every section is self-contained — no need to read the others.Before you start
Make sure these three things are true. Each section below assumes them.
- You connected Meta on your AdsPing pixel (see the Meta CAPI overview). The Meta card on the pixel page shows Connected.
- You copied your pixel script ID from the install snippet on the pixel detail page. It looks like
pix_a3f8…and replacesYOUR_PIXEL_IDeverywhere below. - Your domain is in Allowed Domains on the pixel (events from other origins are rejected for security).
pbq("track", "Purchase", {...}), pb.js sends one POST to AdsPing. Our backend then forwards it to Meta's Conversions API and any other destinations you connected (TikTok, Google Ads, GA4) using the same payload. You write the event once.HTML / Vanilla JS
Use this when you control the raw HTML of your site — landing pages, static sites, or any framework where you can drop a <script> tag into <head>.
1. Install pb.js
Add this once, in your <head>, on every page you want to track. Replace YOUR_PIXEL_ID with your pixel ID.
<!-- AdsPing pb.js -->
<script async src="https://api.adsping.io/pb.js?id=YOUR_PIXEL_ID"></script>pb.js auto-fires PageView when it loads. You don't need to call it.
2. Track a Purchase
Call pbq on your order-confirmation / thank-you page once the order is committed. Currency is ISO 4217 (e.g. "USD", "EUR", "TRY"); value is the gross order total.
<script>
pbq("track", "Purchase", {
value: 49.99,
currency: "USD",
content_ids: ["SKU-1234"],
content_type: "product",
content_name: "Blue T-Shirt",
num_items: 1,
order_id: "ORDER-987",
});
</script>3. Track an AddToCart
Wire it to your "Add to cart" button. The example uses an inline handler — for larger sites, attach the listener in a script block instead.
<button
type="button"
onclick='pbq("track", "AddToCart", {
value: 49.99,
currency: "USD",
content_ids: ["SKU-1234"],
content_name: "Blue T-Shirt",
})'
>
Add to cart
</button>4. Verify in Meta Test Events
- Go to Meta Events Manager → your pixel → Test Events tab.
- Copy the test event code (e.g.
TEST12345). - In AdsPing → pixel detail → Pipelines → click pencil on the Meta pipeline → paste the code into Test Event Code → Save.
- Reload your site and trigger the event. It appears in the Test Events tab within 2 seconds.
- When done, clear the field and Save. Events resume going to production reporting.
React / Next.js
Use this for any React-based stack: Next.js, Vite, Create React App, Remix, Astro's React islands, etc. The Next.js <Script> component is shown — for plain React, replace it with a vanilla <script> in index.html.
1. Install pb.js
Next.js (App Router): add <Script> to your root layout so it loads on every page.
// app/layout.tsx
import Script from "next/script";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<Script
src="https://api.adsping.io/pb.js?id=YOUR_PIXEL_ID"
strategy="afterInteractive"
/>
</head>
<body>{children}</body>
</html>
);
}Plain React (Vite/CRA): drop the script into public/index.html the same way as the HTML section above. Don't use npm install — pb.js is loaded from our CDN, not bundled.
2. Track a Purchase
Wrap window.pbq in a tiny helper so TypeScript stops complaining and SSR doesn't crash. Then call it inside a useEffect on the order-confirmation page.
// lib/adsping.ts
declare global {
interface Window {
pbq?: (action: string, name: string, payload?: Record<string, unknown>) => void;
}
}
export function trackEvent(name: string, payload?: Record<string, unknown>) {
if (typeof window === "undefined") return;
window.pbq?.("track", name, payload);
}// app/order/[id]/page.tsx — confirmation page
"use client";
import { useEffect } from "react";
import { trackEvent } from "@/lib/adsping";
interface Props {
order: {
id: string;
total: number;
currency: string;
items: { sku: string; name: string; quantity: number }[];
};
}
export function ConfirmationView({ order }: Props) {
useEffect(() => {
trackEvent("Purchase", {
value: order.total,
currency: order.currency,
content_ids: order.items.map((i) => i.sku),
content_type: "product",
num_items: order.items.reduce((n, i) => n + i.quantity, 0),
order_id: order.id,
});
}, [order]);
return <div>Thank you, your order #{order.id} is confirmed.</div>;
}Purchase on the final confirmation view, not on every render. The dependency array [order] and the unique order_id together prevent double-counting across remounts and Strict Mode.3. Track an AddToCart
Call the helper inside the click handler. No effect needed — AddToCart is user-driven, not lifecycle-driven.
"use client";
import { trackEvent } from "@/lib/adsping";
interface Product {
sku: string;
name: string;
price: number;
currency: string;
}
export function AddToCartButton({ product }: { product: Product }) {
function handleClick() {
// Your existing add-to-cart logic
addToCart(product);
trackEvent("AddToCart", {
value: product.price,
currency: product.currency,
content_ids: [product.sku],
content_name: product.name,
});
}
return <button onClick={handleClick}>Add to cart</button>;
}4. Verify in Meta Test Events
Same flow as the HTML section above. Set the Test Event Code in the AdsPing dashboard, then reload your site and trigger the event.
Google Tag Manager
Use this when your team already manages tags in GTM and you don't want a developer involved. pb.js wraps window.dataLayer.push(): any GA4-style event you push gets forwarded to Meta automatically — you don't write a separate Meta tag.
1. Install pb.js
- GTM workspace → Tags → New → Custom HTML.
- Name it
AdsPing — pb.js. - Paste the snippet below and replace the pixel ID.
- Trigger: Initialization — All Pages (loads before GA4 / consent tags).
- Save → Submit → Publish.
<script async src="https://api.adsping.io/pb.js?id=YOUR_PIXEL_ID"></script>2. Track a Purchase
If your store already pushes GA4 Enhanced Ecommerce purchase events to dataLayer, you're done. pb.js intercepts them and forwards. The standard GA4 schema looks like this:
window.dataLayer = window.dataLayer || [];
window.dataLayer.push({
event: "purchase",
ecommerce: {
transaction_id: "ORDER-987",
value: 49.99,
currency: "USD",
items: [
{
item_id: "SKU-1234",
item_name: "Blue T-Shirt",
price: 49.99,
quantity: 1,
},
],
},
});If you don't already have this push, create a Custom HTML tag with the snippet above and trigger it on your order-confirmation page.
3. Track an AddToCart
Same idea — push the GA4 standard event and pb.js maps it to Meta's AddToCart automatically.
window.dataLayer.push({
event: "add_to_cart",
ecommerce: {
currency: "USD",
value: 49.99,
items: [
{ item_id: "SKU-1234", item_name: "Blue T-Shirt", price: 49.99, quantity: 1 },
],
},
});purchase → Purchase, add_to_cart → AddToCart, view_item → ViewContent, begin_checkout → InitiateCheckout, generate_lead → Lead. See the dataLayer integration guide for the full table.4. Verify in Meta Test Events
- Use GTM Preview mode (top-right Preview button).
- Open the preview window of your site, complete a test order. The
purchaseevent should appear in GTM's Tags Fired panel. - Then check Meta Events Manager → Test Events tab (with the Test Event Code set on your AdsPing pipeline) — the conversion appears within ~2 seconds.
Shopify
Use this for any Shopify store, including Shopify Plus. pb.js goes in your theme; the Purchase event uses Shopify's built-in order-status page. Both Online Store 2.0 and legacy themes are supported.
1. Install pb.js
- Shopify admin → Online Store → Themes → Edit code on your live theme.
- Open
layout/theme.liquid. - Paste the snippet below right before the closing
</head>tag. - Save.
<!-- AdsPing pb.js -->
<script async src="https://api.adsping.io/pb.js?id=YOUR_PIXEL_ID"></script>2. Track a Purchase
Shopify exposes the order on the order-status page via the checkout Liquid object. Add this snippet to Settings → Checkout → Order status page → Additional scripts (legacy checkout) or to the same theme.liquid wrapped in a {% if checkout %} guard.
{% if first_time_accessed %}
<script>
pbq("track", "Purchase", {
value: {{ checkout.total_price | money_without_currency | remove: ',' }},
currency: "{{ checkout.currency }}",
content_ids: [{% for item in checkout.line_items %}"{{ item.sku }}"{% unless forloop.last %},{% endunless %}{% endfor %}],
content_type: "product",
num_items: {{ checkout.line_items | size }},
order_id: "{{ checkout.order_number }}",
});
</script>
{% endif %}first_time_accessed ensures the Purchase only fires once — without it, a customer reloading the order page would double-count.3. Track an AddToCart
For themes using AJAX cart (most modern themes), listen to the cart:item-added event Shopify dispatches. Add this to theme.liquid after pb.js:
<script>
document.addEventListener("cart:item-added", function (event) {
var item = event.detail || {};
pbq("track", "AddToCart", {
value: (item.final_price || 0) / 100,
currency: "{{ shop.currency }}",
content_ids: [String(item.variant_id || item.id)],
content_name: item.product_title,
});
});
</script>For older themes that don't dispatch this event, attach a click listener to your form[action="/cart/add"] submit instead.
4. Verify in Meta Test Events
- Set the Test Event Code in AdsPing on the Meta pipeline (same as other sections).
- Place a test order with a 100%-off discount code, or use Bogus Gateway in dev stores.
- The
Purchaseappears in Meta's Test Events tab within seconds.
analytics.subscribe("checkout_completed", …) that calls pbq("track", "Purchase", …). Shopify exposes the order in event.data.checkout.WordPress / WooCommerce
Use this for any WordPress site (with or without WooCommerce). The cleanest install is via the theme's functions.php or — better — a tiny custom plugin so theme updates don't wipe the snippet.
1. Install pb.js
Add this to your theme's functions.php or a custom plugin file. It enqueues the script on every front-end page.
<?php
// Add this to functions.php (or a small custom plugin)
add_action('wp_head', function () {
$pixel_id = 'YOUR_PIXEL_ID';
echo '<script async src="https://api.adsping.io/pb.js?id=' . esc_attr($pixel_id) . '"></script>';
}, 1); // priority 1 = load early2. Track a Purchase (WooCommerce)
Hook into woocommerce_thankyou, which fires once on the order-received page. WooCommerce gives you the order object — pull total, currency, line items, then print the pb.js call.
add_action('woocommerce_thankyou', function ($order_id) {
if (!$order_id) return;
$order = wc_get_order($order_id);
if (!$order) return;
$items = [];
$count = 0;
foreach ($order->get_items() as $item) {
$product = $item->get_product();
if ($product) {
$items[] = $product->get_sku() ?: (string) $product->get_id();
$count += $item->get_quantity();
}
}
$payload = [
'value' => (float) $order->get_total(),
'currency' => $order->get_currency(),
'content_ids' => $items,
'content_type' => 'product',
'num_items' => $count,
'order_id' => (string) $order->get_order_number(),
];
?>
<script>
pbq("track", "Purchase", <?php echo wp_json_encode($payload); ?>);
</script>
<?php
}, 10, 1);3. Track an AddToCart (WooCommerce)
Listen for the AJAX add-to-cart fragment update. WooCommerce dispatches added_to_cart on jQuery — bind once on every page that has the Woo cart enqueued.
add_action('wp_footer', function () {
if (!class_exists('WooCommerce')) return;
?>
<script>
jQuery(document.body).on('added_to_cart', function (e, fragments, hash, button) {
var $btn = button;
var productId = $btn.data('product_id');
var price = parseFloat($btn.data('price') || '0');
var name = $btn.data('product_name') || '';
pbq("track", "AddToCart", {
value: price,
currency: "<?php echo esc_js(get_woocommerce_currency()); ?>",
content_ids: [String(productId)],
content_name: name,
});
});
</script>
<?php
});data-price / data-product_name on the cart button — if so, query the WC REST API or read from the product page's schema.org markup. The simplest fallback: omit value and content_name; AdsPing accepts AddToCart without them.4. Verify in Meta Test Events
- Set the Test Event Code on the AdsPing Meta pipeline.
- Place a test order using Cheque / Bank Transfer payment method, or use a 100%-off coupon.
- The Purchase appears in Meta's Test Events tab within seconds.
Event reference
Meta's standard event names and the parameters they expect. AdsPing forwards every parameter you pass — anything not on this list ends up in custom_data.
| Event name | When to fire | Required | Recommended extras |
|---|---|---|---|
Purchase | Order completed (final confirmation page) | value, currency | content_ids, num_items, order_id |
InitiateCheckout | User starts checkout | — | value, currency, content_ids |
AddToCart | Item added to cart | — | value, currency, content_ids, content_name |
ViewContent | Product detail page | — | content_ids, content_name, content_category |
Lead | Form submitted (quote, demo, contact) | — | value, currency |
CompleteRegistration | User signed up | — | content_name |
Search | Search query run | — | search_string |
Subscribe | Subscription started | — | value, currency, predicted_ltv |
WhatsAppClick or VideoComplete — call pbq("track", "YourEventName", {...}) and Meta surfaces them under Custom Conversions. See custom events guide.Common pitfalls
Purchase fires twice
Most common cause: confirmation page reloads or React Strict Mode remounts. Two fixes:
- Always pass
order_id. Meta and pb.js deduplicate within a 7-day window when the sameorder_idis seen twice. - Shopify only: wrap the script in
{% if first_time_accessed %}as shown above.
Events don't reach Meta
- Open DevTools → Network → filter
adsping. You should see a 200 to/api/v1/track. If 403: your domain isn't in Allowed Domains. Add it on the pixel detail page. - If the request goes through but Meta's Events Manager shows nothing: check the pipeline health badge in AdsPing — if it says auth_failed, your Meta token expired. Click Reconnect on the Meta card.
- Test Events tab is empty even though events arrive: the Test Event Code must be set on the AdsPing pipeline, not the Meta side. Edit the pipeline → Test Event Code → paste → Save.
Browser pixel and CAPI both fire — am I double-counting?
No. Meta's deduplication uses the event_id and event_name pair. pb.js generates a stable event_id per call and stores it on the browser pixel side too — Meta merges them automatically.
Match quality is low (under 7)
pb.js auto-harvests Advanced Matching parameters from form fields named email, phone, first_name, etc. If your forms use non-standard names, expose them via data-pb-email / data-pb-phone attributes — pb.js picks them up. All hashing is client-side; raw PII never reaches our server.
Currency or value missing on Purchase
Meta will accept Purchase without these but the conversion won't count toward bid optimization or ROAS. Always pass both, even if you have to hardcode currency.