Goal: Prevent duplicate Purchase events in Facebook Ads Manager when both browser-side Pixel and server-side CAPI fire for the same user interaction.
π― Why Deduplication?
Without deduplication, Facebook may double count conversions, inflating performance metrics and ruining ad optimization.
Metaβs Deduplication Rule:
To deduplicate, both Pixel and CAPI events must:
- Have the same event name (e.g.,
Purchase) - Share a common
event_id
β Prerequisites
| Tool/Asset | Purpose |
|---|---|
| Facebook Pixel ID | For browser & server tracking |
| Facebook CAPI Access Token | Server-side events |
| GTM Web Container | Client-side (Pixel) events |
| GTM Server Container | Server-side (CAPI) events |
| Access to osCommerce | For injecting dataLayer and PHP code |
π§ Implementation Plan
- Inject
event_idinto osCommerce - Push purchase data into
dataLayer - Fire Pixel tag with
event_id - Forward same event (with
event_id) to sGTM - Post to Meta CAPI with same
event_id - Validate and test for deduplication
π Step-by-Step Implementation
πΉ 1. Inject event_id and Purchase Data in checkout_success.php
File Path: /checkout_success.php
<?php
$order_query = tep_db_query("SELECT orders_id, order_total, customers_email_address FROM " . TABLE_ORDERS . " WHERE customers_id = '" . (int)$customer_id . "' ORDER BY orders_id DESC LIMIT 1");
$order = tep_db_fetch_array($order_query);
$order_id = $order['orders_id'];
$order_total = $order['order_total'];
$customer_email = $order['customers_email_address'];
// Generate a UUIDv4-style event ID
$event_id = bin2hex(random_bytes(16));
?>
<script>
window.dataLayer = window.dataLayer || [];
dataLayer.push({
event: 'purchase',
transaction_id: '<?= $order_id ?>',
value: <?= $order_total ?>,
currency: 'USD',
email: '<?= $customer_email ?>',
event_id: '<?= $event_id ?>'
});
</script>
πΉ 2. GTM Web Container β Facebook Pixel Tag
a. Trigger:
- Custom Event =
purchase
b. Tag: Custom HTML Tag
<script>
fbq('track', 'Purchase', {
value: {{DLV - value}},
currency: '{{DLV - currency}}',
eventID: '{{DLV - event_id}}'
});
</script>
Enable:
- Trigger: Custom Event =
purchase
πΉ 3. GTM Web β Forward Event to sGTM
a. Tag: HTTP Request Tag
- URL:
https://<your-sgtm-domain>/collect - Method: POST
- Content-Type:
application/json
Body Template:
{
"event_name": "Purchase",
"event_id": "{{DLV - event_id}}",
"transaction_id": "{{DLV - transaction_id}}",
"value": {{DLV - value}},
"currency": "{{DLV - currency}}",
"email": "{{DLV - email}}",
"user_agent": "{{User-Agent}}",
"ip_override": "{{Client IP}}"
}
πΉ 4. GTM Server-Side Tag: Facebook CAPI with Deduplication
a. Variables:
Create variables for each field:
event_nameevent_idvaluecurrencyemailtransaction_iduser_agentip_override
b. Facebook CAPI Tag Code in sGTM
const sendHttpRequest = require('sendHttpRequest');
const log = require('logToConsole');
const JSON = require('JSON');
// Facebook credentials
const access_token = 'YOUR_FACEBOOK_ACCESS_TOKEN';
const pixel_id = 'YOUR_PIXEL_ID';
// Event data
const event_name = data.event_name;
const event_time = Math.floor(Date.now() / 1000);
const event_id = data.event_id;
const payload = {
data: [{
event_name: event_name,
event_time: event_time,
event_id: event_id,
action_source: 'website',
user_data: {
em: [sha256(data.email.trim().toLowerCase())],
client_ip_address: data.ip_override,
client_user_agent: data.user_agent
},
custom_data: {
currency: data.currency,
value: data.value,
order_id: data.transaction_id
}
}]
};
sendHttpRequest(
`https://graph.facebook.com/v18.0/${pixel_id}/events?access_token=${access_token}`,
{
method: 'POST',
headers: {'Content-Type': 'application/json'}
},
JSON.stringify(payload)
);
log('Sent deduplicated event to Meta CAPI: ' + event_name);
πΉ 5. Add SHA256 Email Hash Function
In your template or custom variable:
function sha256(str) {
return Utilities.computeDigest(
Utilities.DigestAlgorithm.SHA_256,
str,
Utilities.Charset.UTF_8
).map(b => ('0' + (b & 0xFF).toString(16)).slice(-2)).join('');
}
πΉ 6. Validate Deduplication
- Use Meta Events Manager > Test Events
- Use Meta Pixel Helper (Chrome Extension)
- Confirm both events arrive with same event_id
- Only one event should be attributed in Ads Manager
β οΈ Debugging Tips
| Issue | Fix |
|---|---|
| Two purchases shown in Ads Manager | Ensure both client & server send the same event_id |
| Server event not showing | Confirm CAPI call succeeds with HTTP 200 |
| Missing parameters | Log payload and inspect in sGTM preview |
π Privacy Best Practices
- Always hash
emailbefore sending to Meta - Use
client_ipanduser_agentfrom headers - Respect user consent before firing CAPI or Pixel
π§ Summary
| Step | Action |
|---|---|
| 1 | Inject purchase data + event_id in osCommerce |
| 2 | Fire Pixel with event_id from GTM Web |
| 3 | Forward same data to sGTM |
| 4 | Post deduplicated event to Meta CAPI |
| 5 | Test in Metaβs Event Manager |
β Final Code Recap
- Purchase
dataLayer.push()in PHP β - Web GTM Pixel tag with
eventIDβ - HTTP request from Web β sGTM β
- CAPI event with same
event_idβ - Deduplication enabled β
