Documentation menu

Session replay

Replay connects a masked web recording to its analytics session, person and captured errors. Open Analytics → Replay. Collection is off until an operator enables it for an environment and the website grants separate replay consent.

pnpm add @saaspro/browser @saaspro/replay
import { spm } from "@saaspro/browser";
import { startReplay } from "@saaspro/replay";

spm.init({ key: "YOUR_PUBLIC_KEY", host: "https://your-console.example.com", consent: "pending" });
const replay = startReplay(spm, { consent: "pending", blockSelector: ".payment-details" });

// Connect to the visitor's separate consent choices.
spm.consent("granted");
replay.consent("granted");

// Revoke independently, or revoke analytics to stop both.
replay.consent("revoked");
// spm.consent("revoked");

// Tear down the application instance.
replay.stop();

Use one recorder per document. The SSR-safe addon waits for the browser client to initialize. Pending/revoked analytics consent, opt-out, respected Do Not Track and ephemeral privacy mode prevent recording. Reset/session rotation starts a new recording and discards unsent events from the prior identity. Withdrawal stops capture, clears memory and aborts requests immediately; already accepted recordings follow retention or operator deletion.

replay.flush() uploads pending sampled events with bounded retries. getStatus() and optional onStatus report waiting, disabled, buffering, recording, limited, stopped or unavailable. Replay has no persistent browser buffer. The optional package uses rrweb for DOM and interaction capture and keeps the core analytics bundle small.

Sampling and errors

Session sample records selected visits throughout the visit. Error buffer sample selects other visits to hold a masked snapshot and up to 30 seconds of interactions locally. The next snapshot replaces that buffer. An error accepted into the browser SDK queue triggers upload and continued recording. Errors dropped by before do not trigger replay. Sampling is deterministic per session and enforced again by the server. Active recorders refresh configuration every 30 seconds; pausing collection refuses new uploads immediately.

Use spm.captureException(error) or automatic browser error capture. Enable the Errors module to retain error details. Watch session replay at this error opens the matching recording at the error timestamp. Replay offers jump controls and issue links. Correlation uses the application, environment, session and recorded time. Person links appear after analytics resolves the session identity. Several page visits/tabs may create recordings under one session.

Privacy and playback

  • Capture masked text/input values, DOM layout, pointer/click/scroll, viewport and input-state changes.
  • Retain a closed set of computed layout/color styles. Strip recorded URLs, arbitrary personal attributes, scripts, stylesheets and external asset references before transport, and filter again at ingestion/playback.
  • Exclude entire elements with data-spm-private, data-spm-ignore or blockSelector. Masking cannot be disabled.
  • Exclude images, SVG, canvas, media, embedded frames, network bodies, console output, fonts and plugin payloads. Pseudo-elements, shadow-root styling, animations and asset-dependent visuals are not reproduced exactly. Replay shows masked layout and interactions, not screenshots or video.

Playback uses rrweb's script-disabled sandbox with interaction disabled and no recorded website asset requests. The console retains its nonce-based production CSP. A small version-pinned player patch applies the host nonce to rrweb’s internal stylesheet. Payload reads require analytics.read plus people.pii.read. Metadata browsing requires analytics.read; error/person links require their own module permissions.

Storage and limits

Limit Value
SDK memory buffer 1 MB pending masked events plus one in-flight chunk
Chunk 1,000 events; 900 KB SDK / 1 MB normalized server maximum
Snapshot 3,000 nodes, depth 64; byte budget also applies
Recording 15 minutes, 8 MB decoded, 20,000 events
Environment retained payload 256 MiB compressed; 2,000 recordings
Daily intake 1–2,000 new recordings; 256 MiB compressed per UTC day
Retention 1–30 days, capped by application retention

Private gzip chunks live in PostgreSQL binary rows. Limits exclude database/index overhead. No public objects or external storage credentials are required. Capacity-plan before increasing these bounds.

A random recording capability, ordered sequence and digest prevent overwrite or duplicate usage on retries. Requests have a five-second body deadline and per-key rate budget. The SDK retries failed chunks twice, then stops if still unavailable. Missing/conflicting sequences stop capture instead of creating a misleading timeline.

Expired recordings disappear immediately. replay.retention.purge deletes up to 100 expired recordings per environment, visiting 50 environments every five minutes. Operators with analytics.write can delete a recording with an audited reason and a fresh passkey. A 30-day tombstone prevents retry resurrection. Shortening retention also requires a reason.

API and troubleshooting

The SDK posts config/append to /api/v1/replay. Public keys select tenant/environment and require an allowed browser Origin; they cannot read recordings. Management list, detail, payload, settings and deletion routes live under /api/v1/apps/{org}/{app}/analytics/replay; see OpenAPI.

  • No recordings: check environment, allowed origins, both consent choices, sampling and collection settings.
  • Buffering: waiting for an SDK error; no recording has uploaded yet.
  • Limited: DOM, event, time, daily or storage budget stopped capture. Exclude large/private elements or lower sampling.
  • Unavailable: check connectivity, request limits and storage. Restart only after resolving the underlying issue.
  • No error link: check session IDs, Errors module, captured timestamps and uploaded coverage. Events after capture stops are outside the recording.