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_name
transaction_id
value
currency
items
user_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_id
filter
๐ Bonus: Handle Partial Refunds
For partial refunds:
- Adjust
value
to reflect refunded portion - Modify
items
array 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_time
to 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 |