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_id
into 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_name
event_id
value
currency
email
transaction_id
user_agent
ip_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
email
before sending to Meta - Use
client_ip
anduser_agent
from 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 β