The coupon button that did nothing: one ecommerce bug, end to end

Most ecommerce bugs that cost money do not throw errors. A button with no click handler renders, takes focus, changes colour on hover and does nothing at all. No exception reaches your error tracker, no alert fires, the page passes every synthetic check that only asks whether it loaded. The only evidence is in the behaviour of the people who tried to use it: they click, click again, click five times in a second, and leave.

Northwind Outfitters is the outdoor-gear store we ship as the InsightClickIQ demo. It has real browser sessions recorded by the snippet and thirty days of history, and it has a few planted problems so the analytics have something to find. One of them is exactly that button: Apply coupon on /cart. This post follows it from the first signal to the fix, step by step, quoting what the product actually said at each step.

From signal to fix: the dead coupon button, five steps A signal names the Apply coupon button on /cart; an investigation watches the sessions behind it; a replay link opens at 0:18 where the rage click happens; the zones table measures the button across 30 days; the fix ships with a release annotation and an alert that watches dead clicks on /cart. The AI does the watching; a person makes the call at step 5 Northwind Outfitters demo store, real figures from the console 1 Signal found in the background 78 sessions this week 53 rage · 110 dead clicks ~6 lost conversions $1,841 at risk 2 Investigation one click from the signal 6 / 6 sessions watched 2 themes, each with timestamped evidence 0 orders in the sample 3 Replay evidence chip → ?t=18 0:18 rage click, on screen 8+ clicks in ~6 s intent: boots + coupon outcome: abandoned 4 Zones /cart, desktop, 30 days 37% of 830 clicks dead clickers convert 28% others convert 69% lift 0.41× 5 The fix a person decides bind the handler + inline feedback release annotation alert: dead clicks on /cart, 24 h verify: dead rate → 0, the signal stops firing
Five steps, each one click from the last. The AI does the watching and the counting; the decision at step 5 is a person’s, and the loop back is how you know the fix worked.

Step 1 — the signal nobody asked for

Nobody on the Northwind team went looking for a coupon problem. Every thirty minutes a background round computes signals: traffic and conversion anomalies, new traffic sources, slow pages, error spikes and frustration hot spots. A frustration signal is an element that collected rage clicks (three or more clicks within 700 ms in a 30-pixel circle) or dead clicks (a click on something interactive followed by no DOM change, no URL change and no network request for a second). The detection is statistics. The model is only asked to word it.

Signals · Frustration
The Signals page filtered to Frustration. The first card: Apply coupon on /cart frustrated 78 sessions this week, 53 rage clicks and 110 dead clicks, about 6 lost conversions, 78 sessions, $1,841 at risk, impact score 68, with Explain, Investigate, Analyze with an agent and session links
Read the red pill first. “~6 lost conversions · 78 sessions · $1,841 at risk” is computed, not narrated. The “Do:” line is the model’s recommendation. The numbered chips at the bottom are the sessions behind the signal, each a replay.

The card says: “The ‘Apply coupon’ element on /cart received 53 rage clicks and 110 dead clicks across 78 sessions this week … This friction is linked to an estimated 6 lost conversions and $1,841.28 in revenue.” Those last two numbers are the part that changes what happens next, and they are worth unpacking because they are arithmetic, not opinion:

Figure on the cardHow it is computedNorthwind value
Sessions touchedSessions in the last 7 days with a rage or dead click on that element78
Baseline conversionThe project’s conversion rate over the same window10.48%
Lost conversionsTouched sessions that did not convert × the baseline rate~6
Revenue at riskLost conversions × the average value of Order completed over 30 days$1,841.28
Impact scoreSeverity weight × log(1 + sessions), scaled so the project’s largest signal is 10068

The average order value behind that revenue figure is about $307, and it comes from the revenue property the store sends with clickiq("track", "Order completed", {...}). A store that sends no order value gets the lost-conversion estimate and no dollar figure, rather than an invented one. Signals are sorted by impact score, so the coupon button sits near the top, behind only a slow home page (LCP p75 4.8 s) and a tracking gap.

An estimate, and labelled as one

“Lost conversions” assumes the frustrated sessions would have converted at the site average. Some coupon hunters were never going to buy. The number is for ranking, not for the board pack; step 4 gives you a better one.

Step 2 — let the AI watch the sessions

A frustration signal carries the session ids behind it. The Investigate button on the card opens an investigation pre-filled with them. An investigation summarises each session once (from the event timeline, not the video pixels), then makes one model call that groups the summaries into themes. The rule that matters: every theme’s evidence has to cite a real highlight offset from a real session. Anything the model cannot point at is dropped before you see it.

Investigation · Apply coupon rage
An investigation titled Apply coupon rage: 6 chosen sessions, watched 6 of 6. What the AI saw: two compounding breakdowns, a non-functional Apply coupon button hit by all 6 visitors and a JavaScript crash reading 'days' that kills Place order for 3. Two theme cards with evidence chips such as 0:18 Rage-clicked Apply coupon, button broken, and a What to do list
The evidence chips are links. “0:18 Rage-clicked ‘Apply coupon’ — button broken” opens that session’s replay at the eighteenth second. The investigation found a second bug the signal did not mention.

The six sessions came back as two themes, and the second one is the reason to run an investigation rather than just reading the signal:

  • ‘Apply coupon’ completely unresponsive — all 6 sessions. Every visitor in the sample tried it and every one rage- or dead-clicked it. Three of them had arrived with an obvious coupon intent.
  • ‘Place order’ JavaScript crash blocks checkout — 3 sessions. Three mobile visitors with carts of $429, $461 and $578 got past the coupon, reached checkout, and hit Cannot read properties of undefined (reading 'days') on every press of Place order.

The narrative put it plainly: “Together these bugs blocked every single visitor from converting … Zero orders were completed across all 6 sessions.” The signal had found one problem. Watching the people found two, and showed that they compound: the coupon button costs you the price-sensitive visitor at the cart, and the checkout crash costs you the one who pressed on.

Check the crash against the release log

The checkout error is not new — 870 occurrences in 223 sessions over 30 days, first seen on 27 September. But its daily count ran in the 20s and 30s until a release annotation, “v2.4 — new checkout flow”, on 20 October, then went 41, 103, 98. The annotation sits on the Overview and Analyze trend charts as a dashed marker, so that jump has a label on it. In the demo the throw comes from the “Same-day (beta)” shipping option.

Step 3 — the replay, at the second it went wrong

Watching a five-minute session to find a three-second problem is the reason replay tools go unused. The evidence chip removes that: it opens /sessions/<id>?t=18, which seeks the player to 0:18 and scrolls the timeline to the same moment.

Session replay · t=0:18
A session replay of the Northwind cart: Glacier Mid GTX Boot, size 10, $229, the promo code field masked as asterisks and the cursor on Apply coupon. The timeline on the right lists Clicked Apply coupon, Dead click on Apply coupon, and Rage clicked Apply coupon, all between 0:16 and 0:21
Look at the timeline, then the field. Six clicks on Apply coupon between 0:16 and 0:21, two of them flagged dead and one rage. The promo code itself shows as asterisks: replay masks every input by default, so you see the attempt and never the code.

The session summary for this visitor, written from the timeline, reads: intent “find and buy hiking boots with a coupon”, outcome “abandoned”. Arrived from Reddit at 0:00, picked the Glacier Mid GTX Boot ($229) at 0:06, rage-clicked the star rating at 0:09 (another planted dead element), added size 10 at 0:12, viewed the cart at 0:14, and from 0:16 made “at least 8 attempts over ~6 seconds” on Apply coupon. The session ends there, 23 seconds in.

The replay also shows what the counts cannot: the visitor typed a code first. They had a code and the page would not take it. How replay summaries and seekable highlights work →

Step 4 — zones: from six sessions to a month of traffic

Six sessions are a story. Before anyone schedules engineering time you want the size of the problem across all traffic, and you want to know what the button is doing to conversion. The Heatmaps page for /cart has a zones table: one row per element, grouped by its CSS selector, with clicks, the share of page sessions that clicked it, rage and dead counts, median time to first click, conversion lift of clickers against everyone else, and how far down the page it sits relative to how far people scroll.

Heatmaps · /cart · Explain this page
The Explain this page panel for /cart on desktop over 30 days: 691 sessions, 1,368 clicks. Findings: dead clicks on Apply coupon (303 of 830), rage clicks (153), Checkout is the highest-converting action (68% vs 8%, 8.7x lift), a 17-second delay before Checkout is clicked. The zones table below: Apply coupon 830 clicks, 34.0% click rate, 153 rage, 303 dead (37%), 21 s to first click, lift 0.41x; Checkout 538 clicks, 77.9%, lift 8.72x
Compare the two lift badges. Clicking Checkout goes with an 8.72× higher conversion rate; clicking Apply coupon goes with 0.41×. The red badge is the one that matters for the fix.

Over 30 days on desktop the numbers are stark. 691 sessions viewed the cart. Apply coupon took 830 of the page’s 1,368 clicks — 61% — from 235 sessions, and 303 of those clicks were dead, a 37% dead rate, with 153 rage clicks on top. Sessions that clicked it converted at 28%; sessions that did not converted at 69%. Checkout, by contrast, was clicked by 538 sessions, who converted at 68% against 8% for everyone else.

830clicks on Apply coupon, 30 days, desktop
37%of them dead — nothing happened
28% vs 69%conversion, clickers vs the rest
0.41×lift: the button goes with losing the sale

Explain this page turns that table into findings. The model receives the zone table, the scroll bands and the rule flags, and may only cite them; its answer is checked against the table (known finding kinds, selectors that exist) before it is shown. Its first finding was the dead button — “303 of 830 clicks on button#apply-coupon were dead, with 153 rage clicks across sessions” — and its first recommendation was to “fix button#apply-coupon’s dead-click bug so coupon validation responds on every click and shows clear success/error feedback.” With no model configured, the rules produce the same finding, because a dead rate above 30% with at least three dead clicks is a rule, not a judgement.

One caution the page itself will not make for you. The 0.41× lift is a correlation: people who look for a coupon box are, on average, more price-sensitive than people who do not, and some of them would have left anyway. What the lift does tell you is that the button is sitting on your least committed visitors at the exact moment they are deciding. That is the worst place for a dead control.

What the counts alone said“Coupon usage is low and coupon users convert badly.”

Read without behaviour, the same numbers support a pricing conclusion: coupon hunters are bargain browsers, maybe stop advertising codes. That conclusion is wrong, and it is the one a weekly dashboard would have led to.

What the behaviour said“Coupon users cannot use the coupon.”

37% of clicks dead, rage bursts of eight clicks in six seconds, a code typed in the field before each burst. The fix is an engineering ticket, not a pricing decision.

Step 5 — the fix, and how you know it worked

The engineering change is small: the button had no click handler. Bind one, call the coupon endpoint, and show the outcome inline — a success line with the new total, or “that code has expired” — so a wrong code produces a message instead of silence. The second bug is a null guard on the shipping option’s days property. Neither is the interesting part. The interesting part is closing the loop, because a fix nobody verifies is a fix nobody believes.

  1. Mark the release. Add an annotation — kind release, the version, a link to the change — so every trend chart carries a dashed marker at the moment the fix shipped. Editors create them from the Segments page or with one API call.
  2. Put an alert on the symptom, not the code. An alert is a metric spec plus a condition. Dead clicks on /cart over 24 hours, above a threshold you choose, to the in-app inbox, an email list or a webhook. If someone breaks the button again in six months, you hear it from the alert, not from the quarter’s numbers.
  3. Re-read the zones. After a week of traffic, the Apply coupon row should show a dead rate near zero and a lift moving toward 1×. If the lift stays low with no dead clicks, you have learned something different: the button works and coupon hunters really are less likely to buy.
  4. Watch the signal stop. The frustration signal is recomputed every 30 minutes over the last 7 days. When the element stops collecting rage and dead clicks it drops off the board on its own.
POST /api/alerts
{
  "name": "Dead clicks on /cart",
  "project_id": "<Northwind>",
  "spec": { "kind": "metric", "series": [{ "event": "dead_click",
            "filters": [{ "property": "path", "op": "eq", "value": "/cart" }] }] },
  "condition": { "op": "above", "threshold": 10 },
  "window": "24h",
  "channels": { "in_app": true, "email": ["web-team@northwind.example"] },
  "enabled": true
}

The cart had been producing 15 to 27 dead clicks a day; a threshold of 10 per 24 hours is quiet after the fix and loud if the regression returns. Test on the alert runs the check once and tells you the current value, so you can see it would have fired before you rely on it.

Why the order of the steps matters

Any analytics product can count dead clicks on an element you already suspect. The difference is the direction of travel: this starts with evidence the system found on its own and a cost to rank it by, and asks a person to decide only at the end.

StepWho does the workWhat it gives youModel needed?
SignalBackground statistics every 30 minThat something is wrong, where, and what it costsNo — the model only words it
InvestigationOne summary per session + one grouping callThemes with evidence offsets; the second bugRules group by outcome and dominant problem without it
Replay at ?t=The playerThe moment itself, with inputs maskedNo
Zones + ExplainSQL over 30 days of clicksSize of the problem and its link to conversionNo — rule findings without it
Fix + verifyA person, an annotation and an alertProof that it worked, and a tripwireNo

The last column is deliberate. Every AI step in InsightClickIQ has a rules path, so if the model provider is down or switched off (CLICKIQ_AI=false) the signal still fires, the zones still flag the dead button, and the investigation still clusters the sessions. The model makes the words better. It is not what finds the bug.

The button did not throw an error. It just took 830 clicks and gave nothing back. The people who clicked it were the alert — the product’s job was to notice them.

What this needed from the store

  • The snippet, which captures clicks with an element descriptor, rage and dead clicks and errors without any tagging.
  • Four custom events — Product viewed, Added to cart, Checkout started, Order completed with a revenue value. These define conversion and the dollar figure. On Northwind the funnel over 30 days runs 4,066 → 1,470 → 825 → 524, an overall 12.9%.
  • Replay switched on for a sample of sessions, with inputs masked (the default).

How signal impact is calculated →  ·  Investigations in depth →  ·  Zone-based click analytics →

← All posts   Request a demo