How to Track Gokwik Checkout Events via Google Tag Manager with CustomerLabs for Shopify

Why Gokwik breaks your event data

Gokwik replaces Shopify’s native checkout with its own. That single change splits your funnel across two different tracking systems:

  • Top of funnel — Page View, View Content, Add to Cart — fires from Shopify’s tracking scripts
  • Bottom of funnel — Initiate Checkout, Add Payment Info, Purchase — fires from Gokwik’s scripts

Two systems, two sets of identifiers, no shared user journey. Meta receives the early signals from one source and the money signals from another, and in most setups it can’t stitch them into the same person. The algorithm ends up optimizing on an incomplete picture, and your attribution quietly falls apart.

This is where a first-party data platform earns its place. Routing these events through CustomerLabs and out to Meta’s Conversions API server-side means you get one consistent identity layer across the whole funnel, fewer events lost to browser restrictions and ad blockers, and a much better match rate on the events that matter.

Here’s the full implementation. I’ve done this on several Gokwik stores now, and the steps below are the version that actually survives contact with a live site.

Before you start

You’ll need:

  • A GTM container installed on the site
  • Publish access to that container (not just edit — you’ll be pushing live)
  • Gokwik pushing its checkout events into the dataLayer

That last one trips people up constantly, so start there.

Step 1: Confirm Gokwik is actually pushing events

Before you build a single tag, verify the data exists. I’ve watched people spend an hour debugging GTM triggers when the real problem was a toggle in the Gokwik dashboard.

  1. Open your site, right-click, Inspect, go to the Console tab
  2. Walk the full funnel — add to cart, start checkout, reach the payment screen, complete a test order
  3. Keep the inspect window open the whole time (closing it wipes your log)
  4. Type dataLayer and hit Enter

You’re looking for three events:

  • checkout-initiated
  • payment-screen
  • order-placed

If you don’t see them: log in to the Gokwik Dashboard → Kwik CheckoutIntegrations → enable CustomerLabs. Then re-test.

Mistake to avoid: these need to be genuine dataLayer events, not arguments being passed to some other platform’s function call. If you expand the object and find the event name buried inside a payload headed somewhere else, your GTM custom event trigger will never fire. Look for the event at the top level of a dataLayer push.

Step 2: Confirm GTM is on the page

Quick sanity check in the same console:

google_tag_manager

Expand the object and look for a key matching your container ID — GTM-XXXXXXX. If it’s not there, GTM isn’t loading.

On Shopify, add the GTM snippet to theme.liquid, immediately below the opening <head> tag. Don’t rely on an app to inject it if you can avoid it — you want GTM loading as early as possible so the checkout events aren’t racing it.

Step 3: The Checkout Made tag

This is your Initiate Checkout equivalent.

Tag setup

  1. GTM → TagsNew
  2. Name it CL_initiate_checkout
  3. Tag type: Custom HTML
  4. Paste the code below
<script>
    function productsConversion(productsArr) {
        var products = [];

        for (var i = 0; i < productsArr.length; i++) {
            var productsStructure = {};

            for (key in productsArr[i]) {
                switch (key) {
                    case "item_name":
                        productsStructure["product_name"] = {
                            t: "string",
                            v: productsArr[i][key],
                        };
                        break;

                    case "item_id":
                        productsStructure["product_id"] = {
                            t: "string",
                            v: (productsArr[i] || {})[key],
                        };
                        break;

                    case "price":
                        productsStructure["product_price"] = {
                            t: "number",
                            v: productsArr[i][key],
                        };
                        break;

                    case "category":
                        productsStructure["product_category"] = {
                            t: "string",
                            v: productsArr[i][key],
                        };
                        break;

                    case "quantity":
                        productsStructure["product_quantity"] = {
                            t: "number",
                            v: productsArr[i][key],
                        };
                        break;

                    case "variant":
                        productsStructure["product_variant"] = {
                            t: "string",
                            v: productsArr[i][key],
                        };
                        break;

                    case "brand":
                        productsStructure["product_brand"] = {
                            t: "number",
                            v: productsArr[i][key],
                        };
                        break;

                    default:
                        if (productsArr[i][key]) {
                            var isNum = /^\d+$/.test(productsArr[i][key]);
                            var type =
                                Number.isFinite(productsArr[i][key]) || isNum
                                    ? "number"
                                    : typeof productsArr[i][key];

                            productsStructure["product_" + key] = {
                                t: type,
                                v: productsArr[i][key],
                            };
                        }
                }
            }

            products.push(productsStructure);
        }

        return products;
    }

    var data =
        window.google_tag_manager["GTM-XXXXXXX"].dataLayer.get("eventData");

    var productProps = data["ecommerce"] || {};
    var properties = {};

    if (productProps) {
        properties["customProperties"] = {
            currency: {
                t: "string",
                v: productProps.currency,
            },
            content_type: {
                t: "string",
                v: "product_group",
            },
            coupon: {
                t: "string",
                v: productProps.coupon,
            },
            total_discount: {
                t: "number",
                v: productProps.total_discount,
            },
            value: {
                t: "number",
                v: productProps.value,
            },
        };

        properties["productProperties"] = productsConversion(productProps.items);
        _cl.trackClick("Checkout made", properties);
    }
</script>

Replace GTM-XXXXXXX with your own container ID. You’ll be doing this in four separate tags — see the mistakes section.

What the code is doing, in plain terms: it reads Gokwik’s eventData object out of the dataLayer, walks through the ecommerce items array, and rewrites each product field into CustomerLabs’ typed property format — every value wrapped as { t: "type", v: value }. CustomerLabs needs that type declaration to map properties correctly downstream. Then it calls _cl.trackClick("Checkout made", properties).

Trigger setup

  1. Click the triggering section → +
  2. Name it cl_initiate_checkout
  3. Trigger type: Custom Event
  4. Event name: checkout-initiated
  5. Fire on All Custom Events → Save

Step 4: The Add Payment Info tag

Tag setup

  1. New tag, name it cl_add_payment_info
  2. Custom HTML
  3. Paste:
<script>
    function clProductsConversion(productsData) {
        var products = [];

        for (var cli in productsData) {
            var product = productsData[cli];
            var newproduct = {};

            for (prodkey in product) {
                switch (prodkey) {
                    case "item_id":
                        newproduct["product_id"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_name":
                        newproduct["product_name"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "price":
                        newproduct["product_price"] = {
                            t: "number",
                            v: product[prodkey],
                        };
                        break;

                    case "item_brand":
                        newproduct["product_brand"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_category":
                        newproduct["product_category"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "quantity":
                        newproduct["product_quantity"] = {
                            t: "number",
                            v: product[prodkey],
                        };
                        break;

                    case "item_variant":
                        newproduct["product_variant"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "discount":
                        newproduct["coupon"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    default:
                        if (typeof product[prodkey] !== "object") {
                            newproduct["product_" + prodkey] = {
                                t: "string",
                                v: product[prodkey],
                            };
                        }
                        break;
                }
            }

            products.push(newproduct);
        }

        return products;
    }

    var data =
        window.google_tag_manager["GTM-XXXXXXX"].dataLayer.get("eventData");

    var productProps = data["ecommerce"] || {};
    var properties = {};

    properties["customProperties"] = {
        currency: {
            t: "string",
            v: productProps.currency,
        },
        coupon: {
            t: "string",
            v: productProps.coupon,
        },
        total_discount: {
            t: "number",
            v: productProps.total_discount,
        },
        shipping_tier: {
            t: "number",
            v: productProps.shipping_tier,
        },
        content_type: {
            t: "string",
            v: "product_group",
        },
        value: {
            t: "string",
            v: productProps.value,
        },
    };

    properties["productProperties"] = clProductsConversion(
        productProps["items"] || []
    );

    _cl.trackClick("AddPaymentInfo", properties);
</script>

Replace GTM-XXXXXXX with your container ID.

Same reshaping logic as before, with a couple of extra custom properties — shipping_tier and coupon — and it sends as AddPaymentInfo.

Trigger setup

  • Name: cl_payment_screen
  • Type: Custom Event
  • Event name: payment-screen
  • Fire on All Custom Events

Step 5: The Purchased tag

The one that matters most, so read the note underneath it carefully.

Tag setup

  1. New tag, name it CL-Purchased
  2. Custom HTML
  3. Paste:
<script>
    function clProductsConversion(productsData) {
        var products = [];

        for (var cli in productsData) {
            var product = productsData[cli];
            var newproduct = {};

            for (prodkey in product) {
                switch (prodkey) {
                    case "item_id":
                        newproduct["product_id"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_name":
                        newproduct["product_name"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "price":
                        newproduct["product_price"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_brand":
                        newproduct["product_brand"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_category":
                        newproduct["product_category"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "quantity":
                    case "qty":
                        newproduct["product_quantity"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "item_variant":
                        newproduct["product_variant"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "discount":
                        newproduct["coupon"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    case "mrp_price":
                        console.log("mrp_price", product[prodkey]);
                        newproduct["mrp_price"] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;

                    default:
                        newproduct["product_" + prodkey] = {
                            t: "string",
                            v: product[prodkey],
                        };
                        break;
                }
            }

            products.push(newproduct);
        }

        return products;
    }

    var data =
        window.google_tag_manager["GTM-XXXXXXX"].dataLayer.get("eventData");

    var productProps = data["ecommerce"] || {};
    var properties = {};

    var transactionId =
        productProps.transaction_id ||
        Math.floor(10000000 + Math.random() * 90000000);

    properties["customProperties"] = {
        currency: {
            t: "string",
            v: productProps.currency,
        },
        content_type: {
            t: "string",
            v: "product_group",
        },
        value: {
            t: "string",
            v: productProps.value,
        },
        coupon: {
            t: "string",
            v: productProps.coupon,
        },
        shipping_tier: {
            t: "string",
            v: productProps.shipping_tier,
        },
        payment_type: {
            t: "string",
            v: productProps.payment_type,
        },
        discount: {
            t: "string",
            v: productProps.discount,
        },
        tax: {
            t: "string",
            v: productProps.tax,
        },
        subtotal: {
            t: "string",
            v: productProps.subtotal,
        },
        shipping: {
            t: "string",
            v: productProps.shipping,
        },
        total_discount: {
            t: "string",
            v: productProps.total_discount,
        },
        transaction_id: {
            t: "string",
            v: transactionId,
        },
        transaction_number: {
            t: "string",
            v: transactionId,
        },
    };

    properties["productProperties"] = clProductsConversion(
        productProps.items || []
    );

    _cl.trackClick("Purchased", properties);
</script>

Replace GTM-XXXXXXX with your container ID.

Trigger setup

  • Name: cl_purchase
  • Type: Custom Event
  • Event name: order-placed
  • Fire on All Custom Events

Read this before you publish. The purchase code contains a fallback: if transaction_id is missing from the payload, it generates a random number instead. That keeps the event from failing, but a random ID is useless for deduplication and useless for reconciling against Shopify orders. Test a real order and confirm a genuine transaction ID is coming through. If it isn’t, fix it at the source before you go live — otherwise you’re building duplicate-purchase problems into the foundation.

Step 6: The Create User tag

This is the tag people skip, and it’s the one that decides whether your server-side setup is actually worth anything.

Server-side tracking only outperforms browser tracking if you’re sending identifiers — email, phone, name, city, state, country, ZIP. That’s what lifts Meta’s Event Match Quality and what makes Google’s Enhanced Conversions work. Without it you’ve built a more complicated pipeline for the same weak data.

Tag setup

  1. New tag, name it CL-CreateUser
  2. Custom HTML
  3. Paste:
<script>
    var obj =
        window.google_tag_manager["GTM-XXXXXXX"].dataLayer.get("eventData") || {};

    var email = obj["email"];
    var phone = obj["phone_num"];

    var userProperties = {
        customProperties: {
            user_traits: {
                t: "Object",
                v: {
                    firstname: {
                        t: "string",
                        v: obj["first_name"],
                    },
                    lastname: {
                        t: "string",
                        v: obj["last_name"],
                    },
                    email: {
                        t: "string",
                        v: email,
                    },
                    phone: {
                        t: "string",
                        v: phone,
                    },
                    city: {
                        t: "string",
                        v: obj["city"],
                    },
                    state: {
                        t: "string",
                        v: obj["state"],
                    },
                    country: {
                        t: "string",
                        v: obj["country"],
                    },
                    zip: {
                        t: "string",
                        v: obj["pincode"],
                    },
                },
            },
        },
    };

    if (email && email != null && email != "") {
        userProperties["customProperties"]["identify_by_email"] = {
            t: "string",
            v: email,
            ib: true,
        };

        if (phone && phone != null && phone != "") {
            userProperties["customProperties"]["external_ids"] = {
                t: "Object",
                v: {
                    identify_by_phone: {
                        t: "string",
                        v: phone,
                    },
                },
            };
        }
    } else if (phone && phone != null && phone != "") {
        userProperties["customProperties"]["identify_by_phone"] = {
            t: "string",
            v: phone,
            ib: true,
        };
    }

    if (phone || email) {
        _cl.identify(userProperties);
    }
</script>

Replace GTM-XXXXXXX with your container ID.

The logic prefers email as the primary identifier and attaches phone as a secondary external ID. If there’s no email, it identifies by phone instead. If neither exists, it doesn’t fire _cl.identify() at all — which is the right behaviour, because an identify call with no identifier just creates junk profiles.

Trigger setup — and this is the part that’s different:

  1. Click the triggering section
  2. Select the existing cl_purchase trigger
  3. Click + and also add cl_payment_screen
  4. Save

One tag, two triggers. User details become available at the payment screen, so you capture identity there and again at purchase.

Note the consequence: your Checkout Made event fires before identity is captured, so it’ll have weaker match quality than the two events after it. If checkout initiation matters for your campaign optimization, that’s worth raising with the client rather than letting them discover it in Events Manager.

Testing before you publish

  1. Install the CustomerLabs Pixel Helper Chrome extension
  2. In GTM, hit Preview, enter the site URL, click Connect
  3. Walk the entire funnel in the preview window — page view through completed purchase
  4. In the GTM debug panel, confirm each tag fires on the right event and nothing double-fires
  5. In the Pixel Helper, confirm Checkout made, AddPaymentInfo, Purchased and Create User all appear with populated properties

Don’t just check that tags fired. Open the payloads. Look at value, currency, transaction_id, and the user traits. An event that fires with an empty value is worse than no event — it teaches Meta the wrong thing.

Once it’s clean: Submit → version name and description → Publish.

Monitoring after launch

CustomerLabs → MonitoringEvent Manager → pick your date range.

For the first week, compare purchase counts in Event Manager against actual Shopify order counts daily. A healthy setup lands close to parity. A meaningful gap means something in the chain is dropping events, and you want to catch that in week one, not in the following month’s performance review.

Mistakes to avoid

Leaving GTM-XXXXXXX in the code. It appears in all four tags. Miss one and that tag silently does nothing — the script errors out on an undefined container and GTM reports the tag as fired. Search each tag for the placeholder before saving.

Using the wrong container ID when there are several. Plenty of Shopify stores carry two or three GTM containers from past agencies. You need the ID of the container the dataLayer is attached to, not just any container on the page.

Debugging GTM when the problem is Gokwik. Always verify the dataLayer events exist first. Step 1 exists for a reason.

Letting Shopify’s native Meta pixel and CustomerLabs both send Purchase. You’ll get inflated numbers or a messy deduplication fight. Decide which system owns each event and turn the other one off.

Testing without completing a real order. order-placed only fires on an actual purchase. Use a low-value test product or a COD order and clean it up afterwards. A form submission that stops at the payment screen never tests your most important tag.

Publishing without checking property types. These scripts declare value as a number in some tags and a string in others, which reflects what Gokwik sends at each step. Confirm the values arriving in CustomerLabs are what you expect before you trust the ROAS numbers built on them.