CAPI · marketers · PMs · engineers
Do not fire the whole funnel on thank-you
ViewContent on the product page, AddToCart on add, InitiateCheckout on checkout start, Purchase on success. Content ids should match your catalog feed or dynamic ads will show the wrong SKU.
One step per page, not a bundle
ViewContent on the product page. AddToCart when the cart actually changes. InitiateCheckout when checkout starts. Purchase when money is captured (or when you have a hard policy that authorize is enough, and then you refund voids). Firing all four from the thank-you tag because the GTM recipe included them teaches the model that every converter also just viewed and added in the same millisecond.
Firing only Purchase starves upper-funnel learning. Prospecting campaigns that optimize for ViewContent or AddToCart need those events to be true. A Purchase-only account can still retarget buyers; it cannot easily find the next ones from on-site behavior it never sent.
Content ids match the feed
The same id the product feed uses, not the URL slug, not the Shopify numeric id unless that is what the feed uses. A mismatch is a creative problem that looks like a pixel problem: dynamic ads show the wrong SKU or none.
Send the same ids on ViewContent, AddToCart, and Purchase. A ViewContent of sku-9 and a Purchase of 123456789 is two products as far as the catalog is concerned. TikTok and Pinterest catalog ads punish this as hard as Meta.
Value along the funnel
AddToCart value is the cart, not 1. InitiateCheckout is the cart about to pay. Purchase is what was paid. Using 1 as a placeholder trains value optimization to bid toward $1 orders. Meta Purchase without value and currency fails vendor.meta-conversions-api.body.purchase_requires_value_and_currency. AddToCart without value is often legal and still a bad bid signal if you optimize for it.
GA4 add_to_cart and begin_checkout need currency, value, and items (vendor.google-analytics.body.cart_requires_ecommerce_fields). purchase adds transaction_id. Do not send GA4 purchase on the product page because it is easier than wiring the funnel.
Names on each vendor
Meta: ViewContent, AddToCart, InitiateCheckout, Purchase. Snap: VIEW_CONTENT, ADD_CART, START_CHECKOUT, PURCHASE. TikTok: ViewContent, AddToCart, InitiateCheckout, CompletePayment (not Purchase). GA4: view_item, add_to_cart, begin_checkout, purchase.
A shared dataLayer event checkout_start has to map. If sGTM sends InitiateCheckout to Meta and begin_checkout to GA4, good. If it sends Purchase to both because the trigger is Thank You Page, you bundled the funnel again.
Leads are a funnel too
Lead on form submit. CompleteRegistration if they confirmed email. Do not fire Lead on page load of /contact. PMs who want volume will ask. Volume is not a lead. A Lead pixel on every blog view is how CPL looks cheap and sales says the leads are junk.
If CRM closed-won is the optimization, the form Lead and the offline conversion are two events on purpose. Do not also fire Purchase on the form thank-you with value 1.
Thank-you is Purchase only
The product page already had ViewContent. Do not replay the funnel at the end.
// Wrong: thank-you fires the whole Meta funnel
fbq('track', 'ViewContent');
fbq('track', 'AddToCart');
fbq('track', 'InitiateCheckout');
fbq('track', 'Purchase', { value: 84.50, currency: 'USD' }, { eventID: 'order-1842' });
// Right: Purchase on success, same eventID on CAPI
fbq('track', 'Purchase', { value: 84.50, currency: 'USD', contents: [{ id: 'sku-9', quantity: 1 }], content_type: 'product' }, { eventID: 'order-1842' });
// Snap names are not Meta names
{ "event_name": "ADD_CART", "action_source": "WEB", "event_source_url": "https://shop.example/cart" }
// GA4 cart events need items
{ "name": "add_to_cart", "params": { "currency": "USD", "value": 84.50, "items": [{ "item_id": "sku-9", "quantity": 1 }] } }
Check the artifact
Paste the pixel URL or JSON body into the
playground. Same engine as
pixellint validate. Nothing leaves the tab.