Blog
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.
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.
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();
}
Shopify's order payload is rich. Map the relevant fields:
| Shopify field | GA4 parameter |
|---|---|
| id | transaction_id |
| total_price | value |
| currency | currency |
| user_data.email_address | |
| line_items | items (mapped per item) |
| total_tax | tax |
| shipping_lines[0].price | shipping |
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.
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.