The order that forgot where it came from

A customer clicks your Facebook ad, lands on your site, taps the Zalo chat button and buys in the chat. The sale is real. Your dashboard says the ad made nothing. Here is why, and how I carry the source across.

Published6 min read

Contents
  1. The flyer and the camera
  2. The customer who taps the chat button
  3. What that costs
  4. Carrying the colour across
  5. The parts that bite in real life

Marketing has one question, and it is a money question: if I put one more dong into a channel, which channel gives me the most orders back? To answer it you have to connect every single order back to the campaign that made it. When that chain breaks at any step, the order still happens, but nobody knows where it came from. And a decision to spend more on a channel, or to cut one, has nothing under it.

I learned this the uncomfortable way, in a conversation this August about orders that came in through a Zalo chat button on a website. Real orders, and not one of them could be tied back to an ad.

The flyer and the camera

The picture I use to keep it straight is this. You print flyers and hand them out in a few places: the market, the bus stop, the school gate. Each place gets its own colour. Inside your shop there is a camera. It sees which colour each customer is holding, and whether they end up paying.

Online, that is three separate pieces.

UTM         -> where did the customer come from?   utm_source, utm_medium, utm_campaign
Pixel / tag -> what did they do on the site?       view -> add_to_cart -> purchase
Attribution -> which campaign gets the credit?     last-click, first-click, data-driven

The UTM is the colour on the flyer. It is a few extra parameters you put on the link in your ad, so the site can tell which ad the click came from. The pixel is the camera: a small script on your site that records what the customer does and sends it back to Facebook or Google. Attribution is the rule for handing out credit when a customer touched more than one ad before buying.

Put the three together and you get a dashboard with cost per order and revenue per dong, by channel. From there the decision looks easy: more money into the channel that works, cut the one that does not.

Here is where the picture stops being true. A real colour does not fall off the flyer when the customer walks from one shop to another. A UTM lives only in the URL and the cookies of the website the customer is on right now. The moment they step into another app, Zalo, Messenger, a phone call, it is gone.

The customer who taps the chat button

If you sell in Vietnam, you almost certainly have a Zalo chat bubble in the corner of your website. Zalo is the messaging app most people here use, and for a lot of shops it is where the actual selling happens.

So this is the scene. A customer sees your Facebook ad for the summer campaign. They click it. The link carries utm_campaign=summer, the landing page loads, the pixel records a visit. So far the camera is working.

They have a question about sizes. They tap the chat bubble. The phone switches to the Zalo app. Your staff answers, the customer picks a shirt, pays by bank transfer, and staff write the order down in the CRM.

FB ad [summer] -> web [summer] -> Zalo app [ ??? ] -> order closed by hand [ ??? ]
                                      ^ the colour falls off here

The hidden assumption was “once the customer is on the site, the pixel will see the sale”. It does not, because the sale happened off the site. There is no purchase event to send. The deep link that opens Zalo does not carry the UTM parameters with it, and the Zalo Official Account on the other side cannot read where the person came from.

What that costs

Three things go wrong, and the second one is the dangerous one.

First, the Zalo column on your dashboard is a column of question marks. You do not dare scale any channel, because you cannot see which one is feeding the chat.

Second, the dashboard only shows the orders completed on the website, which for a chat-first shop is the small minority. The ad looks like it is losing money. So you cut it. You have just switched off the channel that was quietly filling your Zalo inbox.

Third, the ad platform never hears about the purchases either. Facebook’s optimiser learns from the purchase events you send back. If all it sees is clicks, it optimises for people who click, not people who buy. Targeting drifts and the cost per real order creeps up.

Carrying the colour across

The fix is to stop expecting the colour to survive on its own and carry it across the boundary yourself.

customer lands     -> save the UTM in a cookie or localStorage (first touch, about 30 days)
taps the Zalo button -> 1) send a click_zalo event, with the UTM, to GA4 or the pixel
                     -> 2) open Zalo with a prefilled message:
                           "I'm interested in the t-shirt [ref: fb_summer_a1]"
staff close the order -> put the ref code on the order in the CRM
order is complete    -> send a purchase event with that ref back to Facebook
                        (Conversions API) or GA4 (Measurement Protocol)

The prefilled message is the part that does not depend on a cookie or on anyone remembering. The ref code arrives in the first line of the chat, so staff can copy it, or better, the CRM can parse it on its own. The last step, sending the purchase back, is what feeds the optimiser again. Conversions API is just Facebook’s way of accepting a purchase event from your server instead of from the browser, which is exactly what you need when the purchase did not happen in the browser.

If you cannot touch the website code at all, there is a cruder version: one Zalo Official Account link, or one phone number, per campaign. You only get the source at the level of a whole campaign, and it does not scale past a handful of them, but it beats a column of question marks.

The parts that bite in real life

Cookies get lost. Safari cuts script-set cookies down to about a week, and people change phones. Accept some error, and treat the ref code in the message as the backup path, because it does not depend on a cookie.

Staff forget. If the ref depends on a human typing it in, the rate of orders with a ref drifts down. Put the ref in the first message and let the CRM read it. Then measure the share of Zalo orders that actually carry a ref, so you notice when it slips.

Credit gets counted twice. If an order fires both the website pixel and the Conversions API, send the same event_id in both so the platform can dedupe.

Keep the customer out of the ref. The code should name the campaign and nothing else. No phone numbers or names in URLs.

Give the dashboard a row called “source unknown”. That row is the measure of the tracking itself. After the patch it should go down, and if it climbs again something has broken.

Before trusting any of it, test by hand: open a link with a UTM, tap the Zalo button, check that the prefilled message has the ref and that the click_zalo event shows up in GA4’s DebugView.

A few other holes I check first, because they are cheap. A landing page that redirects and drops the query string on the way. A pixel that is installed but never sends a purchase event with a value, so the platform can only optimise for clicks. UTM parameters on internal links within your own site, which overwrite the real source with “internal”.

And attribution itself is a choice, not a fact. Last-click is simple but favours whichever channel closes, usually brand search. First-click favours whichever channel introduces. Pick one, write down which, and compare the same model over time rather than switching back and forth.

The rule underneath all of this is short. The source identifier has to survive, unbroken, from the first click to the moment money changes hands, even when the customer changes channel, changes app, or buys from a human in a chat.

All posts

Posts