TL;DR — PixelPal is our free WooCommerce plugin. It fixes the three things that quietly destroy Meta ad reporting for stores priced in non-USD currencies: inflated conversion values, double-counted events when Pixel and CAPI both fire, and GTM saying something different from both of them. We built it for our own clients. It's yours now.
.png)
The problem nobody talks about
Watching ad spend evaporate is bad. Watching it evaporate while your dashboard tells you everything is fine is worse. That was us, for months, running WooCommerce stores priced in Tunisian Dinar.
Meta Ads Manager kept reporting a conversion value of $300. The actual sale was 300 TND, which is about $97. Meta doesn't recognize TND, so it quietly strips the currency label and reads the raw number as dollars. Three hundred becomes three hundred. Except it isn't. Your ROAS looks extraordinary. Your decisions are garbage.
That was problem one. There were two more.
Three problems, one root cause
Problem 1: the currency phantom
Meta's Conversions API and Pixel both accept a value and a currency field. If your store runs in TND, MAD, DZD, or any non-USD currency Meta doesn't natively support, here's what happens:
- Your store sends
value=300, currency=TND - Meta finds no TND support, strips the label, reads the number as USD
- Meta reports a $300 purchase
- Reality: ~$97 purchase (300 TND at current rate)
- ROAS impact: inflated by roughly 3×
Every optimization decision Meta makes after that, from budget allocation to bid strategy, is built on a corrupted signal.
Problem 2: the duplicate event ghost
If your WooCommerce store runs both a browser-side Pixel and a server-side Conversions API (and it should), you may already have a quieter problem: duplicate event counting.
The existing plugins, even the well-rated ones, tend to generate different event IDs for the browser and server versions of the same action. Meta's deduplication system works by matching event_id fields. If they don't match, both events get counted.
.png)
- Browser fires:
Purchase { event_id: "fbpurchase_1234" } - CAPI fires:
Purchase { event_id: "server_order_789" } - Meta sees: two purchases
- Reality: one purchase
So your algorithm is now optimizing toward a funnel that doesn't exist. Cool.
Problem 3: the GTM disconnect
Add Google Tag Manager and you have a third disconnected stream, with its own logic and its own timing. Three sources of truth, none of them agreeing.
Channel data comparison:
- Meta Pixel | Event ID:
fbpurchase_1234| Currency: TND (raw) | Timing: on page load - Meta CAPI | Event ID:
server_order_789| Currency: TND (raw) | Timing: on webhook - GTM DataLayer | Event ID: varies | Currency: TND (raw) | Timing: on DOM ready
Three different versions of the same event, all feeding different optimization surfaces.
How we fixed it
We work with e-commerce brands across North Africa and beyond. For us, these weren't edge cases. They were the default. After patching broken integrations one too many times, we gave up and built the right one from scratch.
The core idea: one event ID, fired everywhere.
When a customer adds a product to their cart, PixelPal generates a single unified event ID, something like pp_AddToCart_1709123456_a3f9. The same ID gets fired to the browser Pixel, posted to Meta CAPI, and pushed to the GTM DataLayer at the same time. When Meta receives the browser and server versions, the IDs match and it deduplicates correctly. No inflation. No ghost events.
The purchase event is its own special hell
Purchase is the most important event you track and also the most fragile. Payment gateway webhooks can fire in the background, completely detached from the browser session. The customer pays, the webhook fires server-side, and there's no browser context to attach anything to.
.png)
PixelPal handles this with a split-tracking pattern. Step by step:
- Customer submits the order form. PixelPal captures browser fingerprint data into order meta:
fbp,fbc,client_ip,user_agent. The unifiedevent_idis generated and stored right then. - Payment webhook fires (background).
woocommerce_payment_completetriggers. PixelPal sends the CAPI event server-side using the stored browser data and the storedevent_id, then sets the_pixelpal_purchase_capi_trackedflag. - Customer lands on the thank-you page.
woocommerce_thankyoutriggers. PixelPal outputs the Pixel and GTM events using the same storedevent_id, then sets_pixelpal_purchase_browser_tracked. - Meta receives both. Same
event_idon both events. Deduplicated. One purchase counted.
Currency conversion, demystified
.png)
PixelPal resolves currency before sending anything to any channel, following a priority chain. Each order stores the full conversion record next to it:
_pixelpal_original_value→300.00_pixelpal_converted_value→97.12_pixelpal_exchange_rate→0.3237
A daily cron keeps the cached rate warm. If you'd rather lock a fixed rate, there's a manual override. Either way, every channel receives the converted value, Meta sees USD, and your ROAS finally reflects something real.
Event coverage
.png)
PixelPal tracks the whole WooCommerce funnel, on all three channels at once. Every event can be toggled individually. All of them share the same unified event_id across channels:
- PageView — Pixel + CAPI + GTM
- ViewContent — Pixel + CAPI + GTM (product pages)
- AddToCart — Pixel + CAPI + GTM
- InitiateCheckout — Pixel + CAPI + GTM
- AddPaymentInfo — Pixel + CAPI + GTM
- Purchase — Pixel + CAPI + GTM (split-tracked, deduplicated)
- Search — Pixel + CAPI + GTM
Setup in five steps
PixelPal is plug-and-play. No build step, no Composer. Pure PHP.
Step 1: install
Upload pixelpal.zip via WordPress Admin → Plugins → Add New → Upload, then activate. PixelPal drops you on an onboarding screen automatically.
Step 2: enter your IDs
- Meta Pixel ID — Meta Events Manager → Data Sources → your Pixel
- CAPI Access Token — Meta Events Manager → Settings → Generate Token
- GTM Container ID — Google Tag Manager → Admin → Container Settings
Step 3: set your currency
Pick your store currency (e.g. TND), your target currency (USD), and whether to use live exchange rates or a fixed manual one.
Step 4: enable test mode
Add your Meta test event code, turn on test mode, browse your store, and verify every event fires exactly once in Meta Events Manager → Test Events. window.PixelPalDebug in your browser console logs every fbq() call; window.PixelPalGTMDebug logs every dataLayer.push().
Step 5: turn test mode off and go live
What changes in your Ads Manager
The number is going to look worse. It's also going to be correct. Correct numbers are the only ones worth optimizing toward.
Why we're giving it away
Because the problem we solved isn't ours alone. Any WooCommerce merchant running Meta ads in a non-USD currency is burning budget on false signals, and any store running Pixel + CAPI without proper deduplication is feeding noise into an algorithm making real budget decisions.
PixelPal is our answer. We built it for our own clients first. Now it's available to anyone who needs it.
If you run a WooCommerce store and you actually care about the quality of your tracking data, it's worth a look.
PixelPal is a free WordPress plugin we built at fissa.agency. If you want help setting it up, a custom tracking implementation, or to work with us on your e-commerce growth, get in touch.