$pageview
Page views and time on page
Recorded on load with the path, title, referrer, UTM parameters and viewport size. When the visitor leaves, the time on page goes with it.
Self-hosted product telemetry
Hotspot records what visitors do with one script tag and stores it on your own server. The dashboard replays each session as the visitor saw it, and shows who is on the site right now, how visitors move between pages, where they drop out of a funnel and which clicks go nowhere.
<script src="https://hotspot.buxa.ai/hotspot.js" data-api-key="hs_demo" defer></script>
Events
Page views, clicks, scroll depth, form submits, errors, web vitals and a recording of the page are captured on their own. Identify and custom events are calls you add.
$pageview
Recorded on load with the path, title, referrer, UTM parameters and viewport size. When the visitor leaves, the time on page goes with it.
$autocapture
Every click, with the element's selector, text, tag and link target, plus where it landed in the viewport and on the page.
$rageclick
Three or more clicks within 1.2 seconds and 40 pixels of each other. A sign that something looks clickable and does not respond.
$is_dead_click
A click on something that is not interactive: no link, button, form control, role, handler or pointer cursor within five ancestors.
$scroll_depth_percent
How far down each page the visitor got, as a percentage, sent with the page leave event.
$form_submit
The form's id, name, action and method, not what the visitor typed. Typed values appear in the session replay unless the field is masked.
$exception
Uncaught errors and unhandled promise rejections, with the message, type and stack trace.
$pageview
A route change in a single-page app records a new page view, so client-side navigation shows up like any other page.
$identify
Call hotspot.identify() after sign-in to attach an email or name to a visitor, and hotspot.capture() for events of your own.
rrweb
One snapshot of the page's DOM, then every change, input, scroll and mouse movement, so the dashboard can play the session back as the visitor saw it. Password and payment fields are masked in the browser; data-hotspot-mask masks anything else.
console, network
Console output at the log, info, warn, error and debug levels, and fetch and XHR requests with their method, URL, status and duration. Bodies and headers are never recorded. Both play back next to the replay.
$web_vitals
LCP, FCP, TTFB, CLS and INP, sent once per page when the visitor leaves or hides it. The dashboard reports the 75th percentile per page and for the whole site.
Your server adds the browser, operating system and device type from the User-Agent header, and the country when your proxy sends a country header. IP addresses are not stored.
Dashboard
Every section is computed from the sessions on your server, over the time range, device, browser and operating system you pick.
Every session with its pages, clicks, device and duration. Filter by time range, device, browser and operating system, narrow the list to converted, rage, error, bounced, engaged, active, identified or dead-click sessions, or search.
The page as the visitor saw it, played back from the DOM recording, with the session's events, console output and network requests next to the player. Masked fields stay masked.
Steps are a path, an event, a clicked selector or clicked text. The funnel shows how many sessions reached each step, the drop-off between steps and the median time to the next one.
An animated map of how visitors move from page to page, with the drop-off at each page, rage clicks and errors marked on the page where they happened, and the landing, exit and most common journeys underneath.
Rage clicks, dead clicks and JavaScript errors in one place: the share of sessions each one hit, grouped by page and element, with a button that opens the sessions it happened in.
Views, sessions, average time, average scroll depth, exit rate and web vitals at the 75th percentile per page, plus a site-wide web vitals card. Sessions and conversion rate per referrer.
Visitors, sessions, page views, time on site, bounce and conversion rate against the period before, a trend chart, top pages, where visitors come from, devices and countries, and plain sentences about what stands out.
Who is on the site right now on a world map, visitors per minute, the pages being viewed and the raw event feed as it arrives.
How it works
The script, the server and the dashboard are the whole product. Nothing is sent anywhere else.
Put one tag in the head of your site or in the root layout of your app. The script is 14 KB gzipped and loads with defer, so it does not block rendering. When replay is on, it loads the recorder (24 KB gzipped) as a separate file.
The script queues events and posts them to your Hotspot server every 2.5 seconds, or sooner when ten are waiting. The recording goes out in chunks every 5 seconds. Sessions and recordings are kept in Redis, or in process memory when no Redis is configured.
Sign in with the dashboard password you set and look at the overview, the realtime map, sessions with their replays, the flow between pages, funnels, breakage, pages and sources, and a guided install page.
Some things are never recorded. Others are masked in the browser before they are sent.
data-hotspot-mask to mask other fields, or data-mask-inputs="all" on the script tag to mask every input. A form submit event carries only the form's id, name, action and method.
Install
One tag in the head of every page, or in the root layout of your app. It starts recording on load. The snippet below already has this server's address and key.
<script src="https://hotspot.buxa.ai/hotspot.js" data-api-key="hs_demo" defer></script>
?hotspot_debug_hud=1.Password fields, payment fields (autocomplete="cc-*") and one-time codes are always masked. Masking happens in the browser, so masked values never reach the server. The classes hs-mask and hs-block work like the first two attributes.
Also on window.hotspot: hasOptedOut(), getDistinctId(), getSessionId(), on(event, callback) and flush().
Using a coding assistant? Give it this instruction and it will place the tag in the right file.
Add <script src="https://hotspot.buxa.ai/hotspot.js" data-api-key="hs_demo" defer></script> to the <head> of the root layout (Next.js app/layout.tsx, Vite index.html, SvelteKit app.html, Astro layout). Do not add other analytics tags. Call window.hotspot.identify(userId, { email, name }) after sign-in. Add data-hotspot-mask to elements that show personal data so the session replay masks them.
HOTSPOT_API_KEYS
Comma-separated keys the capture endpoint accepts. Unset accepts any key, which is fine locally and wrong in production.
HOTSPOT_DASHBOARD_PASSWORD
Protects the dashboard and its API. Unset leaves the dashboard open.
REDIS_URL
Keeps data across restarts and shares it between workers. Unset keeps data in process memory.
HOTSPOT_REPLAY_ENABLED
Set to 0 to turn session replay off for every site. The server refuses recordings and the script stops sending them.
HOTSPOT_MAX_RECORDING_BYTES
The compressed size a session's recording can reach, 10 MiB by default. The dashboard says when a session hit it.
HOTSPOT_SESSION_TTL_SECONDS
How long a session and its recording are kept, 7 days by default.
FAQ
Yes. The script records a page view on load and on every route change, so client-side navigation shows up as separate pages. Add the tag once, in the root layout, and nothing else is needed.
Not as video or screenshots. The script records the page's DOM with rrweb: one snapshot, then every change, input, scroll and mouse movement. The dashboard rebuilds the page from that, so you see what the visitor saw, with the visitor's console output and network requests next to it.
Password fields, payment fields and one-time codes are masked in the browser before anything is sent. Add data-hotspot-mask or data-hotspot-block to elements that show personal data, or set data-mask-inputs="all" on the script tag. data-replay="false" on the script tag turns replay off for one site, and HOTSPOT_REPLAY_ENABLED=0 turns it off on the server.
On your server. Events and recordings go to Redis when REDIS_URL is set, otherwise they stay in process memory and are lost on restart. Sessions expire after 7 days by default, and their recordings with them; HOTSPOT_SESSION_TTL_SECONDS changes that. Up to 2,000 events are kept per session. A recording is kept compressed, up to 10 MiB per session (HOTSPOT_MAX_RECORDING_BYTES), and the dashboard says when a session hit that limit. The live feed keeps the last 5,000 events. Nothing is sent to anyone else.
Yes. Call hotspot.optOut() and the script stops recording in that browser. hotspot.optIn() turns it back on and hotspot.hasOptedOut() tells you the current state. The choice is stored in the visitor's browser.
The script is 14 KB gzipped and loads with defer, so it does not block rendering. When replay is on, it loads the recorder (24 KB gzipped) as a separate file. Events are queued and sent in batches every 2.5 seconds, and the recording goes out in chunks every 5 seconds. When the visitor leaves a page, the last batch goes out with sendBeacon, which does not delay the navigation.
A dead click is a click on something that is not interactive: no link, button, form control, role, handler or pointer cursor within five ancestors. It usually means something looks clickable and is not.
A rage click is three or more clicks within 1.2 seconds and 40 pixels of each other. It usually means something is clickable and does not respond. Both show up under Breakage with the page and the element.
Yes. A funnel is a list of steps, and each step is a path (path:/signup), an event (event:$form_submit), a click on a selector (click:#create-account) or clicked text (text:Buy now). A trailing * on a path or selector matches a prefix. The dashboard shows how many sessions reached each step, the drop-off between steps and the median time to the next step.