Skip to content

Session Replay reference

Replay is configured through the RUM application and its recording rules, not through its own object.

Replay records only for sessions matching a Replay recording rule. While a Replay rule could still match, the SDK buffers the last 60 to 120 seconds in memory, and a match sends that buffer before streaming; see replay buffer and privacy. Once replay activates for a session it stays on for the rest of that session, in every tab and across page loads.

SettingWhereDefaultDescription
captureRecording rulerequiredreplay records a replay when the rule matches.
sample_rate, auto_sampleRecording rule1, falseShare of matching sessions; Auto follows the replay quota.
min_duration_ms, require_interactionRecording rule3000, true for ReplayGates before upload, bypassed by error conditions.
enabledRecording ruletrueDisabled rules are never applied.

Set on the RUM application under settings.privacy and delivered to the SDK with its config. Change them with PATCH /rum/{app_id}, sending only the keys you change; it needs an account admin or an API key with no scope restriction. A privacy level writes these flags for you:

FieldDocumented defaultEffect
mask_inputstrueMasks all input values in the recording (rrweb maskAllInputs). Any privacy level masks inputs whatever this flag says.
mask_textfalseMasks all user-visible text in the recording. Strict masks text whatever this flag says.

settings.privacy also carries the privacy level, the personal data patterns, the per-element selectors (mask_selectors, unmask_selectors, block_selectors, ignore_input_selectors) and never_record_urls; see replay privacy. It also carries hide_action_text, which is not a replay setting.

With mask_text off and below Strict, user-visible page text is recorded (Balanced scrubs emails, card numbers and long digit runs from it). Turn it on, choose Strict, or add mask_selectors if the recorded pages render anything you would not want in a recording.

The SDK records with rrweb, in a separate chunk loaded only for sessions a Replay rule has matched or could still match. Every tab records its own window, and each page load is its own stream of segments:

  • A segment closes at 20 seconds or about 2.5 MB of JSON, and when the tab is hidden. A page’s first full snapshot is sent at once.
  • Segments are numbered from 0 per page load (q) and carry the session, window and page load IDs, the event range and, on the last one, a final marker.
  • Each segment is a JSON array of rrweb events, gzipped where the browser can, sent to POST https://in-replay.siteqwality.com/v2/segments with the client token in Authorization and the index in the x-sq-replay-index header.
  • A failed upload is retried with backoff, and a Retry-After holds every send. A retried upload with the same content is accepted and not stored again. See replay transport for the full contract.

A replay shows what a user saw, so reading one needs more than read access to RUM.

CallerNeeds
Team memberThe account’s replay access level: Members (default) lets any member watch, Admins only admins and super admins. Viewers and auditors never can.
API key or M2M clientThe read:rum_replay scope, plus read:rum. A key with no scope list does not get replays: add both scopes.
AI assistantNever. The MCP server does not expose replay content.

Change the access level with PUT /rum/settings (account admins only), sending only the fields you change:

{ "replay_access": "admins", "body_access": "members" }

body_access sets the same rule for network bodies, headers and console arguments. GET /rum/settings returns both.

Callers without access get a 403. An application outside your account returns 404.

The player loads a replay in two steps: a manifest that lists every piece without URLs, then short-lived URLs for the pieces it is about to play.

GET /rum/{app_id}/sessions/{session_id}/replay:

FieldDescription
statusready (all compacted), live (a segment arrived in the last 2 minutes on a page still open: poll every 5 seconds), processing (some page loads are still raw segments; they play, and are compacted within about 35 minutes) or none.
reasonWith none, why nothing was recorded, when the SDK reported it: not_sampled, no_rule, consent, quota, privacy_url, too_large or opted_out.
start_ms, end_msFirst and last recorded event, Unix milliseconds.
active_ms, activityTime with user input, and the [start, end] ranges it falls in, for skipping inactivity.
windows[]One per browser tab: window_id, index (0 for the tab that recorded first) and its page_loads[].
page_loads[]One per document: page_load_id, url (minimized), start_ms, end_ms and its chunks[] in playback order.
chunks[]id (opaque), kind (block or segment), start_ms, end_ms, bytes, and snapshot (playback can start here).

POST /rum/{app_id}/sessions/{session_id}/replay/chunks with {"ids": [...]}, at most 20 ids, returns one entry per id found (unknown ids are left out):

FieldDescription
id, kindThe id asked for, and block, segment or stylesheet.
urlPresigned GET URL.
rangeFor blocks: send it as the Range header.
expires_in120 seconds. On a 403 from storage, ask for the URL again.

Keep ids opaque and use the range returned with each URL. If a chunk id is missing from the response, refresh the manifest before retrying. Issued URLs stay valid through recompaction until they expire.

How to play each kind:

  • block: a compacted block, one gzip stream holding a JSON array of rrweb events. Gunzip it yourself; the response has no Content-Encoding. Every block starts with Meta and a full snapshot and plays on its own. When you continue from the previous block of the same page load, skip the new block’s leading Meta and full snapshot events and every event marked "sqk": 1: they only rebuild the state the player already has.
  • stylesheet: plain CSS. A block’s _cssText attribute or style text that reads sq-css:<hash> names one: request css:<hash> with the block’s other ids and swap in its text before handing events to rrweb.
  • segment: a raw segment, a JSON array of rrweb events as the SDK sent it: gzip, or plain JSON for an uncompressed tail sent at page close. Check for the gzip header before unpacking.

About 30 minutes after a page load goes quiet (2 minutes once the SDK marks it ended), its segments are compacted into blocks. Compaction applies the application’s current privacy settings and selectors again on the server; changing settings later does not recompact existing blocks. It moves stylesheets of 1 KB or more into a shared store and adds a full snapshot about every megabyte, so seeking never replays the whole page load. Raw segments stay until their retention ends.

Every replay view is written to the audit log as rum.replay.viewed, with target_kind rum_session and the session as target_id, at most once per viewer per 30 minutes. Whenever the manifest or the chunk URLs return something to play, the view is recorded first; if it cannot be recorded, the request fails with a 500 and returns no content.

GET /rum/{app_id}/sessions/{session_id}/viewers lists who watched over the last 90 days, most recent first: actor_type, the person or key (user_id, email, api_key_id, api_key_name, m2m_client_id), first_viewed_at, last_viewed_at and views.

The dashboard player opens at a given moment from its URL, https://app.siteqwality.com/rum/{app_id}/sessions/{session_id}:

QueryEffect
?t=754, ?t=12:34, ?t=1:02:03Opens at that offset into the recording (seconds, m:ss or h:mm:ss), clamped to the recording. Share writes this form.
?at=<epoch ms>&event=<event_id>Opens 5 seconds before that absolute time (never before the start) and highlights the event row with that ID. The Replay link in an error’s Affected sessions list uses the error’s error_ts_ms and event_id.

When both are present, t wins.