Refunds and product returns are critical to eCommerce profitability—but they are often missing from analytics platforms like GA4, Meta Ads, and Google Ads. This leads to inflated ROAS, misleading revenue reports, and misattributed campaign performance.
✅ Why Track Refunds Server-Side?
| Benefit | Description |
|---|---|
| 🎯 ROAS Accuracy | Subtract refunded revenue from paid campaign ROI |
| 🔁 Attribution Correction | Remove conversions from misattributed campaigns |
| 🔒 Privacy-First | No frontend JS needed—ensures GDPR/CCPA compliance |
| ⚙️ Backend Triggering | Triggered directly from return/refund system |
| 📉 LTV Accuracy | Enables precise LTV and cohort modeling in GA4 & CRM |
🧰 Prerequisites
- GA4 property and Measurement Protocol API secret
- Server-Side GTM set up on
https://gtm.yourdomain.com - Access to your platform’s refund webhook, job scheduler, or API (Shopify, Stripe, BigCommerce, etc.)
- Optional: Google Ads & Meta CAPI integration
🚀 Step-by-Step Setup
🔹 Step 1: Trigger Refund Event from Backend
Trigger a POST request to your ssGTM container when a refund is processed.
Example: Node.js Backend
const axios = require('axios');
app.post('/webhook/refund', async (req, res) => {
const { transaction_id, user_id, value, items } = req.body;
await axios.post('https://gtm.yourdomain.com/collect', {
event_name: 'refund',
event_time: Math.floor(Date.now() / 1000),
transaction_id: transaction_id,
user_id: user_id,
value: value,
currency: 'USD',
items: items
});
res.status(200).send('Refund Tracked');
});
✅ Fire this request from your refund system (e.g., Stripe Webhook, Shopify API)
🔹 Step 2: Capture Refund Event in Server-Side GTM
Create a GA4 Client (if not set up already).
Create Event Data Variables in ssGTM:
event_nametransaction_idvaluecurrencyitemsuser_id
🔹 Step 3: Create GA4 Refund Tag in ssGTM
Create a GA4 Event Tag with the following:
- Event Name:
refund - Measurement ID: Your GA4 Stream ID
- API Secret: Your GA4 API secret (Admin > Data Streams > Measurement Protocol)
- Parameters:
transaction_id:{{Event Data - transaction_id}}value:{{Event Data - value}}currency:{{Event Data - currency}}items:{{Event Data - items}}
✅ User Properties (Optional):
user_id:{{Event Data - user_id}}
🔹 Step 4: (Optional) Google Ads Conversion Adjustment
Google Ads allows conversion retractions or value adjustments using the enhanced conversions adjustment API.
Use an HTTP Request Tag in ssGTM:
POST https://www.google-analytics.com/g/collect
{
"conversion_action": "INSERT_YOUR_ACTION_ID",
"adjustment_type": "RETRACTION",
"order_id": "TX12345",
"adjustment_date_time": "2025-05-30T12:00:00Z",
"user_identifiers": [{
"hashed_email": "HASHED_EMAIL",
"user_agent": "Mozilla/5.0",
"ip_address": "103.25.116.1"
}]
}
✅ Ensure the original order used Enhanced Conversion tags.
🔹 Step 5: (Optional) Meta/Facebook Refund Attribution
Send a deduplicated refund event using Meta Conversions API (CAPI):
{
"event_name": "Refund",
"event_time": 1717058552,
"event_id": "refund_ORD123",
"action_source": "website",
"user_data": {
"em": "HASHED_EMAIL",
"client_ip_address": "103.25.116.1",
"client_user_agent": "Mozilla/5.0"
},
"custom_data": {
"value": -129.99,
"currency": "USD",
"order_id": "ORD123"
}
}
Send via an HTTP Request Tag in ssGTM to:
https://graph.facebook.com/v18.0/<PIXEL_ID>/events?access_token=<ACCESS_TOKEN>
✅ Always include a negative value for refund and the original order ID.
🔹 Step 6: GA4 Reporting Setup
- Go to Admin > Custom Dimensions
- Add
refund_reason,is_partial_refund, etc. if you want detailed context - In Explore, build a Refund-focused report:
- Breakdown by
source,campaign,item_name - Segment for users with at least 1 refund
- Funnel with Purchase → Refund path
- Breakdown by
✅ Refunds automatically reduce purchase revenue in GA4 when passed correctly with transaction_id.
🔹 Step 7: Optional CRM/LTV System Integration
Forward refund events to your CRM (Salesforce, HubSpot) or data warehouse via Webhook Tag:
{
"event": "refund",
"transaction_id": "ORD123",
"user_id": "USER001",
"value": 129.99,
"plan": "annual",
"refunded_at": "2025-05-30T12:00:00Z"
}
Send via HTTP Request Tag in GTM server container.
🔹 Step 8: QA & Validation
- Use Server GTM Preview Mode
- Confirm event name =
refund - Check transaction ID matches original purchase
- Validate in GA4 DebugView under Refund events
- Use Realtime Report with
transaction_idfilter
🔒 Bonus: Handle Partial Refunds
For partial refunds:
- Adjust
valueto reflect refunded portion - Modify
itemsarray to include only refunded SKUs
Example Partial Refund Payload:
{
"transaction_id": "ORD123",
"value": 49.99,
"items": [
{
"item_id": "SKU001",
"quantity": 1,
"price": 49.99
}
]
}
🧠 Best Practices
- Store all refund events server-side for auditing
- Hash emails before sending to Meta or Google Ads
- Batch refund events in low-traffic hours using a job scheduler
- Set up Looker Studio reports combining purchases and refunds
- Use
event_timeto reflect real refund timestamps for delayed processing
📦 Summary Table
| Step | Description |
|---|---|
| 1 | Trigger refund from backend |
| 2 | Parse in Server GTM |
| 3 | Send refund to GA4 with transaction_id |
| 4 | Adjust Google Ads conversions (optional) |
| 5 | Send Refund to Meta CAPI (optional) |
| 6 | Create GA4 custom dimensions and reports |
| 7 | Integrate with CRM/data warehouse |
| 8 | QA via GTM preview + GA4 DebugView |
