Most eCommerce setups only send purchase data to analytics platforms, leaving out one critical part of the picture: refunds and returns. Without refund tracking, your Return on Ad Spend (ROAS) is inflated and misleading, especially when using platforms like Google Ads, Meta, and GA4.
By using Server-Side Tagging with OpenCart, we can accurately track refunded transactions and send them back to your analytics toolsβensuring true revenue-based attribution.
β Why Track Refunds?
| Reason | Impact |
|---|---|
| Clean ROAS | Exclude refunded revenue from conversions |
| GA4 Attribution Accuracy | Maintain realistic funnel reporting |
| Google Ads Optimization | Avoid over-optimized bids for refunded conversions |
| Meta Ads CAPI | Refund signals improve campaign quality |
π§° Requirements
| Item | Description |
|---|---|
| OpenCart | Version 3.x or 4.x |
| Server-Side GTM | Configured on a first-party domain |
| Google Ads, Meta CAPI, GA4 | Platforms integrated |
| Order Status Logic | To determine when an order is refunded |
| Consent Layer (Optional) | To handle GDPR/CCPA compliance |
π§± Step 1: Detect Refunded Orders in OpenCart Admin
Modify the refund trigger. In OpenCart, you can hook into order status updates.
In admin/controller/sale/order.php, locate or add code where order status is updated to βRefundedβ (you may need to define a custom status ID, e.g., 11):
if ($order_status_id == 11) { // 11 = Refunded
$order_info = $this->model_sale_order->getOrder($order_id);
$products = $this->model_sale_order->getOrderProducts($order_id);
$items = [];
foreach ($products as $product) {
$items[] = [
'item_id' => $product['model'],
'item_name' => $product['name'],
'quantity' => $product['quantity'],
'price' => $product['price']
];
}
$refund_payload = [
'event_name' => 'refund',
'event_id' => uniqid('refund_', true),
'transaction_id' => $order_info['order_id'],
'currency' => $order_info['currency_code'],
'value' => -1 * $order_info['total'],
'items' => $items
];
$this->sendRefundToServer($refund_payload);
}
π Step 2: Send Refund Event to Server-Side GTM
Add this custom helper function in the same file or a reusable module:
private function sendRefundToServer($payload) {
$ch = curl_init('https://gtm.yourdomain.com/event');
curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($payload));
curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type:application/json']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_exec($ch);
curl_close($ch);
}
β This will trigger every time an order is marked as refunded.
π§ Step 3: Parse Refunds in Server GTM
In your Server Container:
- Use the HTTP Request Client
- Match incoming event where
event_name = refund - Extract
transaction_id,items,value,currency, andevent_id
π€ Step 4: Send Refund Event to GA4 via Server GTM
Create a GA4 Event Tag in ssGTM:
| Parameter | Value |
|---|---|
| Event Name | refund |
| Transaction ID | {{transaction_id}} |
| Value | {{value}} |
| Currency | {{currency}} |
| Items | {{items}} |
| Event ID | {{event_id}} |
β This removes refunded purchase value from revenue reports in GA4.
π― Step 5: Send Refund to Google Ads
In ssGTM:
- Create a Google Ads Conversion Tag
- Configure:
- Conversion ID / Label
transaction_id:{{transaction_id}}value:{{value}}(should be negative)currency:{{currency}}event_id:{{event_id}}(deduplication)
β This tells Google Ads the conversion has been reversed.
π Step 6: Send Refund Event to Meta Conversions API
Use the Meta HTTP tag or a Custom Request:
POST https://graph.facebook.com/v18.0/<PIXEL_ID>/events?access_token=<TOKEN>
{
"data": [{
"event_name": "Refund",
"event_time": {{ timestamp }},
"event_id": "{{event_id}}",
"action_source": "website",
"custom_data": {
"transaction_id": "{{transaction_id}}",
"currency": "{{currency}}",
"value": -99.00
}
}]
}
β Meta will treat it as a revenue correction.
π‘οΈ Step 7: Consent-Safe Processing
If you’re passing PII (e.g., email for Enhanced Conversions), add this logic:
if ($user_consent_given) {
$payload['email'] = hash('sha256', strtolower($order_info['email']));
}
Or handle consent_granted logic in ssGTM to block tags if needed.
π§ͺ Step 8: QA Your Refund Events
| Tool | What to Check |
|---|---|
| ssGTM Preview | Inspect payload structure |
| GA4 DebugView | Look for real-time refund event |
| Google Ads Conversion Tag Diagnostics | Check negative conversion |
| Meta Events Manager β Test Events | View refund test data |
π Sample Refund Data Sent
{
"event_name": "refund",
"event_id": "refund_664c49d1a3bc9",
"transaction_id": "ORD12842",
"value": -99.99,
"currency": "USD",
"items": [{
"item_id": "PROD001",
"item_name": "Winter Jacket",
"price": 99.99,
"quantity": 1
}]
}
β Benefits Summary
| Feature | Benefit |
|---|---|
| True ROAS Calculation | Filter out refunded revenue |
| Attribution Accuracy | GA4 funnel metrics adjusted |
| Ads Optimization | Less bias in Smart Bidding or CAPI |
| First-Party Secure | No exposure of refund logic in frontend |
| Works with All Platforms | Google Ads, Meta, GA4, and others |
