Skip to main content

Session Recordings

Session recordings let you replay exactly what a user did on your site — mouse movements, clicks, scrolling, and form interactions — without capturing any sensitive input data.

How Recordings Work​

Recording uses rrweb, an open-source library that serialises the DOM and records mutations, pointer events, and scroll events into a structured event stream.

The recording data never leaves the browser in one large payload. Instead, it is split into segments (one every 3 seconds or so while the visit is in progress, and a final one when the tab is closed or hidden) sent incrementally to the Platform. This keeps individual requests small and allows partial recovery if the user leaves mid-session.


Session Segments​

A recording session is composed of numbered segments: 0, 1, 2, 3, ...

Watching a visit​

A person's stay on the shop can be several recordings: the collector starts a new session after 30 minutes of inactivity, on sign-out and after an order. Opening any one of them shows the whole visit as a strip above the player, with one block per part laid out on the real clock — so a tab that was open while another was being used appears above it rather than after it, and a gap where the visitor was away stays a gap. Click a block to watch that part; when a part ends, the one that began after it starts automatically. A visit of a single part looks exactly as it always did.

A session is one browser's visit and is shared by every tab of that browser: a product opened in a second tab is part of the same visit, not a new session. Footage, however, is recorded per tab — each tab numbers its own segments from 0 — because a change recorded in one tab cannot be replayed onto the other tab's page. The player therefore offers a tab strip when a visit was recorded in more than one tab and plays one tab at a time; a long tab recording is split into parts (part 2, part 3) after 500 segments, still under the same visit. Clicks, errors and other markers belong to the whole visit and are shown on every tab's timeline.

The Platform checks for gaps in the sequence. If segment 2 arrives before segment 1, or a segment is missing entirely, the recording is flagged:

{
"sessionId": "abc-123",
"hasGaps": true,
"missingSegments": [1]
}

This hasGaps flag is displayed in the Dashboard with a warning, so you know the playback may be incomplete.


Which Recordings You See​

Every recording carries an interest score (0–100), computed on the server from how much happened in the visit: how long it lasted, how many pages and clicks it produced, and whether anything of value occurred — a lead, an order, an error, a burst of rage clicks. Frustration raises the score rather than lowering it, because the visit where something went wrong is the one worth your minutes.

The list shows visits of at least 1 minute. A shorter visit is left out of the Recordings list unless it carries a marker — an error, a rage click or a dead click — or a star. Leaving it out does not delete it: the visitor's own page, a lead's panel and the Recordings list narrowed to that visitor still show it. A visit that is still being recorded appears once it passes the minute.

A visit that showed nothing is deleted early. A single page, under ten seconds, no clicks, no error and no frustration is usually a mis-tap or a bot. That rule — and only that one — decides which visits the short retention window below deletes, and the API and connected AI clients leave such visits out unless asked. Two things it never touches: a visit where an error or a rage click was recorded, whatever it scored, and a visit that has not been scored yet, which is an unknown rather than a zero.

Recordings matching it are removed after 3 days instead of the usual 14 — see Storage.

A visit that produced no footage at all is a different case, and is counted separately. The session row is created the moment the recorder announces its first page, which is before the first fragment of footage has been packed and uploaded — so a visitor who leaves inside that second leaves a row naming a page and nothing to play. Those are left out of the list and removed after 24 hours: the only thing that could still turn one into a recording is a late upload arriving, which takes seconds, not days.

A visit is uploaded only once it has shown something. The recorder buffers the opening of every visit in the browser and sends nothing until one of four things happens: the visitor acts on the page (a click or a tap, or typing into a field), the page throws an error, the footage reaches 30 seconds, or your own code calls flushNow(). The length is the footage's — from the first thing recorded to the latest — not the time the tab was open, so a page left open that never moved is one still frame and is not uploaded however long it stayed. Scrolling, a finger starting a scroll and keys pressed outside a field (Space, the arrows) do not count as acting. A visitor who glances at a page and leaves therefore costs no upload, produces no recording and uses none of your plan — while a visit that earns its upload at twenty seconds still arrives complete, because the buffer is held rather than discarded: the first segment you receive is still the opening of the visit. Once a visit has earned its upload, the next pages it opens in the same tab are recorded from their first frame.

This is only the FOOTAGE. Pageviews, clicks, scroll depth, leads and errors are separate collectors with their own transports, none of them gated this way — so a bounce is still counted, still attributed to its traffic channel and still visible in analytics. What it does not leave behind is a recording of nothing.

How long a visit lasted is measured when the visit ends, not when it starts. A recording is scored while it is still in progress, so the platform re-scores a visit a few minutes after it goes quiet, and immediately when the browser reports its final fragment. That is why a recording made moments ago may briefly show a shorter duration than it had — and why its score, which the floor above reads, settles within minutes rather than overnight.


Rage Clicks & Dead Clicks​

Detected by their own collector, not by the recorder, and stored as their own rows rather than as a JSON blob on the session. That split is deliberate: a JSON array hanging off one session can tell you a person got frustrated, but it cannot answer where on the checkout people rage-click, which is the only version of the question worth asking.

It also means frustration data keeps working with session replay switched off — the interactions bundle carries no rrweb and costs a few KB.

Interaction typeDefinition
Rage click3 or more clicks within 800 ms, inside 30 px, on the same element
Dead clickA click after which nothing changes for 700 ms — no DOM mutation, no scroll, no navigation, no network request — and the element was rendering a pointer cursor

The cursor condition is what makes dead clicks a signal instead of noise. Without it every click on a paragraph counts as dead. With it, the statement is precise: the page showed this was clickable, someone took the offer, and nothing happened.

Positions are normalised in the browser before they are sent:

  • rx — a 0–1 fraction of the content root's width, not the viewport's. A click on the edge of a centred 1200px container sits at 0.26 of a 2560px screen and 0.03 of a 1280px one; storing viewport fractions smears every heatmap into the empty margins.
  • yBand — the offset down the content root in 20px bands. Vertical position is not a fraction of anything, because page height changes with content.
  • device — mobile / tablet / desktop / wide. These buckets never mix. The same CSS renders different layouts, so averaging across them draws a page nobody was served.

Clicks are also grouped by a page key — a hash of the normalised path, with numeric and id-like segments and product slugs collapsed. Without it a 10 000-SKU catalogue produces 10 000 heatmaps of three clicks each. The trade is stated plainly: a collapsed page is an average of its instances, which is right for layout questions and wrong for questions about one specific product.

rage and dead are annotations over the click stream, not extra clicks: every click is recorded once as click, and a burst or a silent click adds a second row describing it. Summing all three types double-counts — each is its own overlay.


Privacy Considerations​

Every form input is masked — every <input> of every type, every <textarea> and every <select>, replaced character-for-character with * so an abandoned field still looks different from a completed one. Also never captured: cross-origin <iframe> content, canvas content (unless you switch canvas recording on), anything inside an element with the rr-ignore class, and the TEXT of any element marked private (below).

WooCommerce's own customer blocks are masked by default — the customer details and addresses on the order-received page, the My Account content area and the address cards of a block checkout. The replay still shows the buyer reaching "Thank you"; the name and address are asterisks of the same length.

What is NOT masked, and you should know this before enabling recordings: text your own theme RENDERS anywhere else. A customer's name in the header, a custom account widget, a basket summary — all of it is captured as the visitor saw it, because it is page text rather than an input value. That is normal for every session-replay product, and it is the reason the private marker and Settings → Privacy → Excluded URLs exist. Use them on any page that renders personal data.

The Privacy Policy states this in the same terms; if the two ever disagree, that one is authoritative.

Mark an element private with the data-wpel-mask attribute (or the storepilot-mask class; data-storepilot-mask and wpel-mask work too). Its text, and the text of everything inside it, is recorded as asterisks; the layout stays visible:

<div data-wpel-mask>This text is recorded as asterisks</div>
<p class="storepilot-mask">So is this</p>

Storage​

By default, recording segments are stored on the Platform server filesystem.

Recordings are kept for 14 days, except those hidden as showing nothing (see Which Recordings You See) — those are removed after 3 days, files and rows together. Three rather than one because the nightly re-score revisits the last two days: a lead or an error can be matched to a visit after the footage arrives, and a recording must never be deleted before the job that could raise its score has seen it.

For high-volume sites, configure S3-compatible object storage — see Configuration → Storage.


Session Stitching​

Multiple recording sessions by the same visitor (across different page loads) are linked under a single visitor profile. You can view the full chronological session history for a visitor in Dashboard → Visitors → [Visitor] → Sessions.

This allows you to trace a user's entire journey across multiple visits.