Twenty checkout sessions, zero sales — and every one of them was mine
My store dashboard said this: page_view 11 checkout_start 9 purchase 0 An 81.8% view-to-checkout rate and a 0% close rate. Two ways to read that. Either my pricing is catastrophic, or the top
My store dashboard said this:
page_view 11
checkout_start 9
purchase 0
An 81.8% view-to-checkout rate and a 0% close rate. Two ways to read that. Either my pricing is catastrophic, or the top of the funnel is fake. I spent an hour assuming the first one before I checked the second.
The top of the funnel was fake. All nine checkout opens were me.
The number that gave it away
Stripe's own list of checkout sessions was the only source I had not looked at. Twenty sessions, live mode:
status payment_status count
open unpaid 15
expired unpaid 5
Twenty sessions opened, zero paid. Fine, that is just low traffic, right? No. Look at what the sessions actually contained:
created status amount currency product
...4712137 open 4700 brl ship-your-mcp-server
...4711914 open 14700 brl pack-completo
...4711913 open 4700 brl mcp-oauth-debug
...4708349 open 6700 brl guia-agente-que-entrega
...4705960 open 4700 brl visual-regression-gates
...4705958 open 4700 brl visual-regression-gates
...4705957 open 4700 brl visual-regression-gates
Three sessions for the same product within three seconds. Eleven different products inside a few minutes. One payment method list on every single session. That is not a customer comparing options. That is a script walking the catalog, and the script had my IP.
I had been testing checkout links after a deploy and never cleaned up the signal. Then I read the dashboard, saw 81.8%, and started writing a plan to fix a conversion problem that did not exist yet.
Why the dashboard could not tell me
Every analytics stack will tell you "a checkout started". Almost none will tell you "a customer started a checkout". The event has no buyer attached to it, so a bot, a probe, your staging run and a real person land in the same counter.
The fix is not a better dashboard. It is attaching evidence to the event, at the moment it is written:
-
Identity. Was the request from my own IP, my own cookie, or a header my test harness sets? Those events get a
donoclass, not a customer class. - Timing. Sessions per IP inside a sliding window. Three in ten minutes is a comparison shopper being unlucky. Eleven in thirty seconds is a walker.
- Payment method surface. If every session exposes the exact same method list and none progresses, nothing human is in the loop.
-
Status, not just creation.
openandunpaidis not a sale. A dashboard counting session creation as intent will always look better than the business.
That last one is the whole bug in one line: I was counting my intent to test, not their intent to buy.
The class of bug that keeps paying me visits
I have written about this shape before, in three other costumes:
- A regex that matched a single digit, so
1.234silently became1, and a dashboard read the same number for three weeks. - A guard reading a database that no longer received events, reporting "0 pending" with full confidence.
- A watchdog restarting services at boot because
systemctl is-activereturns non-zero foractivating.
Same shape every time: the check reports on a proxy, the proxy drifts from reality, and the report stays green. You do not fix this by being more careful. You fix it by making each number answer a narrower question with its evidence attached. Not "did a checkout start" but "which person started how many checkouts, from where, and did any of them finish".
What the real funnel looks like now
Once test traffic is excluded, the honest numbers on my store are small enough to be embarrassing and useful at the same time:
- 29 human visitors in two days of measurement
- 2 leads, both from the free skill
- 0 sales, from anyone, ever
Nine checkouts at 81.8% was a story I could act on. Twenty-nine humans and no sale is a different story: there is no conversion data yet, because there is not enough traffic to convert. Optimising a page that 29 people saw is a way to feel productive.
So the fix went in the other direction. I made the test traffic visible instead of removing it, because the next person to read the dashboard needs to know which class of visitor produced the number:
def classifica_visitante(ua, ip, dono_header, cookie_dono):
if dono_header or cookie_dono:
return "dono", "marcado"
if ip and ip in IP_DONO:
return "dono", "ip_dono"
if _BOT_RE.search(ua or ""):
return "bot", "ua_bot"
if _REAL_BROWSER_RE.search(ua or ""):
return "humano", "ua_navegador"
return "incerto", "ua_desconhecido"
The incerto class matters more than it looks. The previous version defaulted unknown traffic to "human", and a single probe generated 61 page views attributed to one person. Absence of evidence is not evidence of a customer.
One caveat I now carry: adding a classification column to a table does not retroactively classify history. My own first read of the new dashboard showed 323 "uncertain" visitors and looked like a traffic spike. It was the column's default value on rows written before the column existed. If a metric changes shape on the day you shipped it, that is the migration, not the market.
The rule I keep
Before writing a single line about conversion, click the path yourself with the data open. The gap between "the dashboard says" and "the provider says" was twenty sessions and one hour. The provider knows who paid. The dashboard knows who loaded a pixel.
Series start (if you landed here first): https://dev.to/samuelfmedeiros/a-marketplace-price-is-retail-not-cost-how-not-to-build-margin-on-the-wrong-number-2oek
Originally published by Dev.to WebDev. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.