Blog

Forward Shopify webhooks to GA4 via sGTM

Shopify webhooks fire reliably even when the customer closes the tab. Routing them through sGTM gives you bulletproof purchase tracking.

Client-side purchase tracking on Shopify breaks when customers close the tab before the success page finishes loading, when they have aggressive ad blockers, or when their connection drops mid-checkout. Shopify's webhook integration solves all three by sending the order details server-to-server. Routing through your sGTM container ties this back to GA4.

Set up the Shopify webhook

Shopify Admin > Settings > Notifications > Webhooks > Create Webhook. Event: "Order creation." Format: JSON. URL: https://data.example.com/webhook/shopify. Webhook API version: latest stable.

Shopify shows a webhook secret. Store it; you will use it to verify signatures in the next step.

Verify the signature

Shopify signs every webhook with HMAC-SHA256. The signature is in the X-Shopify-Hmac-Sha256 header. In your sGTM Webhook client, verify by hashing the request body with your secret and comparing.

const sha256 = require('sha256');
const body = getRequestBody();
const expected = sha256(body + SHOPIFY_SECRET, {outputEncoding: 'base64'});
const received = getRequestHeader('x-shopify-hmac-sha256');
if (expected !== received) {
  setResponseStatus(401);
  returnResponse();
}

Map Shopify fields to GA4 purchase

Shopify's order payload is rich. Map the relevant fields:

Shopify fieldGA4 parameter
idtransaction_id
total_pricevalue
currencycurrency
emailuser_data.email_address
line_itemsitems (mapped per item)
total_taxtax
shipping_lines[0].priceshipping

Handle the dual-source problem

If Shopify's native GA4 integration is also firing (it does by default), you now have two sources of purchase events. Either disable the native integration in Shopify settings, or set up event_id dedup using the order ID. Disabling is simpler.

Refunds

Add a second webhook for the "Refund creation" event. Map similarly to a refund GA4 event with negative value. Without this, your purchase numbers are gross of refunds, which is wrong for almost every report.