Most analytics tools answer what happened.
Behavior analytics answers why:
Where users clicked
How far they scrolled
Where they got stuck in forms
Where they dropped off in funnels
What they said (surveys/feedback) alongside what they did
I built Spyglass360, a Hotjar-style behavior analytics + CRO platform, using a stack a lot of people overlook:
PHP
MySQL
Vanilla JavaScript
Bootstrap
Square for subscriptions
This post is the engineering breakdown: the event model, storage strategy, and the “gotchas” you hit when you ship a real tracker.
The install contract: one snippet, stable URL
If install takes more than one snippet, you’ve already lost half your users.
Your tracker needs a stable public endpoint (don’t leak internal file paths):
(function(){
var s = document.createElement("script");
s.src = "https://spyglass360.com/track.js?api=YOUR_API_KEY";
s.async = true;
document.head.appendChild(s);
})();
What is this?
Rule: /track.js should remain stable even if you refactor your internal /assets/... paths later.
Event design: collect the minimum data that enables the most features
A lot of tracking systems fail because they capture too much (privacy risk, storage cost), or too little (can’t compute anything useful).
A workable base event model:
pageview (url, referrer, viewport)
click (normalized x/y + element hint)
scroll (depth percent)
form_focus, form_blur, form_submit (never raw values)
custom_event (for funnels/goals)
Example payload:
{
"apiKey": "…",
"siteId": 123,
"visitorId": "v_abc",
"sessionId": "s_xyz",
"type": "click",
"url": "https://example.com/pricing",
"ts": 1710000000000,
"viewport": { "w": 1440, "h": 900 },
"event": { "x": 0.62, "y": 0.41, "selector": "button#start" }
}
Normalization matters: store coordinates as percentages so heatmaps work across screen sizes.
Storage strategy: raw events + aggregates
PHP can handle ingestion just fine. The real problem is query shape.
The common pattern:
Write all events to an append-only raw store
Maintain aggregate tables for dashboard queries
Raw table (append-only)
fast inserts
flexible schema via JSON payload
used for replay + deep analysis
Aggregate tables (fast reads)
daily/site stats
funnels summary
form dropoff stats
heatmap bins per page
This lets you keep MySQL without instantly needing ClickHouse/BigQuery.
Heatmaps: bins + rendering
Heatmaps are basically “count points and draw a gradient”.
Implementation approach:
Normalize x,y
Convert to bins (e.g., 100×100 grid)
Increment bin counters
Render bins as an overlay in the dashboard
Why bins:
you avoid storing every pixel as its own row
you can pre-aggregate quickly
rendering becomes predictable
Session replay: not video, a timeline
Replay looks like video, but it’s really:
an initial snapshot
a stream of events
a player that reconstructs the session
An MVP replay can start with:
click trail + scroll positions + page navigation timeline
Then evolve into:
DOM snapshots
mutations
masking rules
Big rule:
Mask and sanitize by default. Replay must never leak passwords, credit cards, or sensitive fields.
Funnels: pay attention to correlation, not just counts
Funnels work when you can answer:
“Did this same session reach step 1 → 2 → 3?”
“How long between steps?”
“Where is the dropoff?”
Implementation:
define funnel steps by URL patterns and/or event names
compute step completion per session
aggregate dropoffs
Funnels are one of the highest paid-value features because they directly tie to revenue.
Form analytics: capture friction without capturing values
Form tools get creepy fast. Don’t collect raw input.
Instead track:
field focus/blur
time-in-field
submit attempt
validation errors (optional)
Output:
“field X is where 60% abandon”
“average time spent here is 12s”
“users rage-click submit then bounce”
Feedback widgets + surveys: the “why” layer
Behavior tells you what. Feedback tells you why.
What matters is targeting:
show surveys based on pages, events, or user segments
connect responses to session context (without exposing identity)
Once you can connect:
“users rage click here”
with
“the pricing is confusing”
you get actionable insight.
Experiments (A/B testing): integrated beats bolted-on
Once you have:
visitors/sessions
conversion goals (events)
segmentation
…experiments become much easier:
assign variant consistently per visitor
measure goal completion
break results down by segment
The win isn’t “A/B testing exists”, it’s:
“Testing is integrated with behavior + funnels + feedback.”
Billing gotchas: Square subscription plan variation IDs
If you use Square subscriptions, here’s the sharp edge:
Subscription enrollment requires the SUBSCRIPTION_PLAN_VARIATION ID, not the plan ID.
If you pass the wrong one, you’ll see errors like:
Object does not have type SUBSCRIPTION_PLAN_VARIATION
Fix:
create the plan AND variation in your admin tooling
store both IDs
always enroll by variation ID
Launch gotcha: never grant access before payment success
The classic SaaS bug:
user picks paid plan
you create account and log them in immediately
payment never completes
they still get access
Fix with a proper state machine:
pending_payment
active
failed/canceled
Gate the dashboard/API based on active.
What I’d recommend if you build something like this
Start with funnels + form analytics (they sell)
Heatmaps are easy value
Replay is hardest (build in layers)
Lock down privacy early
Keep /track.js stable forever
Add “install verification” UX so users know it’s working
Wrapping up
Building behavior analytics is less about “writing a tracker” and more about:
event design
storage strategy
privacy discipline
and turning data into actionable UX
If you’re building something similar, I’d love to hear:
what feature is hardest for you?
what you wish Hotjar-style tools did better?
(If you want to try Spyglass360, it’s at spyglass360.com.)
Top comments (0)