Skip to content

refund

The refund event tells GA4 to subtract revenue from a previously recorded purchase. It’s the counterpart to purchase — and just as critical for accurate revenue reporting. Without refund tracking, your GA4 revenue figures will exceed your actual revenue by every refund you’ve issued.

Refunds are unusual in one respect: they often happen server-side, initiated from your admin panel or payment processor, not from user action in the browser. For server-side refunds, you need the GA4 Measurement Protocol. For client-side refund confirmation pages, you can push to the dataLayer normally.

Fire refund when:

  • A user completes a refund request flow on the website (client-side — use dataLayer)
  • An admin issues a refund in your order management system (server-side — use Measurement Protocol)
  • A payment processor webhook confirms a refund (server-side — use Measurement Protocol)
// Full refund of a complete order
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'refund',
ecommerce: {
transaction_id: 'TXN-2024-98765', // must match the original purchase transaction_id
currency: 'USD',
value: 142.97, // full order value being refunded
tax: 28.59,
shipping: 5.99,
coupon: 'SUMMER15',
items: [
{
item_id: 'SKU-001-BLK-L',
item_name: 'Classic Leather Jacket',
item_brand: 'Heritage Co.',
item_category: 'Apparel',
item_variant: 'Black / Large',
price: 89.99,
quantity: 1
},
{
item_id: 'SKU-047-WHT-M',
item_name: 'Cotton Crew T-Shirt',
item_brand: 'Heritage Co.',
item_category: 'Apparel',
item_variant: 'White / Medium',
price: 24.99,
quantity: 2
},
{
item_id: 'SKU-112-TAN-34',
item_name: 'Woven Leather Belt',
item_brand: 'Heritage Co.',
item_category: 'Accessories',
item_variant: 'Tan / 34',
price: 34.50,
quantity: 1
}
]
}
});

For partial refunds where only some items are returned, include only the refunded items in the items array with the refunded quantities.

// Partial refund — only the jacket is being returned
dataLayer.push({ ecommerce: null });
dataLayer.push({
event: 'refund',
ecommerce: {
transaction_id: 'TXN-2024-98765', // same transaction ID as the original order
currency: 'USD',
value: 89.99, // only the refunded item's value
items: [
{
item_id: 'SKU-001-BLK-L',
item_name: 'Classic Leather Jacket',
item_category: 'Apparel',
price: 89.99,
quantity: 1 // only 1 jacket is being refunded
}
]
}
});
Event Schema refund
Parameter Type Required Description
event string Required Must be "refund"
ecommerce.transaction_id string Required The transaction ID from the original purchase event. GA4 uses this to match and subtract from the purchase.
ecommerce.currency string Required ISO 4217 currency code. Must match the original purchase currency.
ecommerce.value number Required The monetary value being refunded. For partial refunds, this is only the refunded amount.
ecommerce.tax number Optional Tax amount being refunded.
ecommerce.shipping number Optional Shipping amount being refunded (if applicable).
ecommerce.coupon string Optional Coupon code from the original order.
ecommerce.items[] Array<Item> Optional Items being refunded. For a full refund, include all items. For partial refund, include only refunded items.
items[].item_id string Required Must match the item_id from the original purchase.
items[].item_name string Required Product name.
items[].price number Optional Unit price at time of refund.
items[].quantity number Optional Quantity being refunded.

Server-side refunds via Measurement Protocol

Section titled “Server-side refunds via Measurement Protocol”

Most refunds are not initiated by the user in a browser — they’re triggered by admins, webhooks, or automated systems. For these, use the GA4 Measurement Protocol to send the refund event directly to GA4 from your server.

// Node.js — server-side refund via GA4 Measurement Protocol
async function sendRefundToGA4(refundData) {
const payload = {
client_id: refundData.clientId, // original buyer's GA4 client_id
events: [{
name: 'refund',
params: {
transaction_id: refundData.orderId,
currency: refundData.currency,
value: refundData.refundAmount,
items: refundData.items.map(item => ({
item_id: item.sku,
item_name: item.name,
price: item.price,
quantity: item.refundQuantity
}))
}
}]
};
const response = await fetch(
`https://www.google-analytics.com/mp/collect?measurement_id=${GA4_MEASUREMENT_ID}&api_secret=${GA4_API_SECRET}`,
{
method: 'POST',
body: JSON.stringify(payload)
}
);
return response.ok;
}

For client-side refund flows (user-initiated refund confirmation pages):

  1. Create a Custom Event trigger. Event name: refund. Name it CE - refund.

  2. Create Data Layer Variables for ecommerce.transaction_id, ecommerce.currency, ecommerce.value, and ecommerce.items.

  3. Create the GA4 Event tag. Event name: refund. Enable Send Ecommerce data. Add transaction_id, currency, and value as explicit event parameters.

  4. Test by verifying in GA4 DebugView — refund events should appear with the correct transaction_id and value. Check GA4 revenue reports to confirm the amount is being subtracted.

Wrong transaction_id. Using a new ID, a refund-specific ID, or any value other than the exact original purchase transaction_id means GA4 cannot match the refund to the purchase. Always use the original order ID.

Not tracking partial quantities. If a customer returns 1 of 2 T-shirts, the refund event should have quantity: 1 for that item, not quantity: 2. The quantity must match exactly what’s being refunded.

Sending refunds only from the frontend. Customer-initiated returns via a web portal can use the dataLayer. Admin-initiated refunds, payment processor webhooks, and automated refunds must go through the Measurement Protocol. Relying only on frontend tracking means every admin refund is invisible in GA4.