Skip to content
Console
Browse documentation
Guide

Record session replay safely

Record browser sessions or native structure with independent privacy controls, and verify stored evidence without overstating device support.

On this page

Browser session replay is opt-in and available through the JavaScript SDK. Record only the journeys you are prepared to store and review.

Start recording

Native structural storage, recorded steps and read-only proof viewing have independent, default-off controls. Optional Android and iOS recorders and React Native and Flutter adapters require current native policy, independent consent, native store permission and separate project-bound upload authority. The browser setup below does not configure native capture. Source, device, simulator and recorder-to-storage qualification remain separate; source tests and an upload receipt do not prove retained playback. See native recording privacy.

import { startReplay } from '@anectico/sdk/replay';

const stopReplay = startReplay({
  endpoint: 'https://app.anectico.com',
  apiKey: import.meta.env.VITE_ANECTICO_REPLAY_KEY,
  distinctId: currentCustomerId,
  maskAllInputs: true,
  captureConsole: false,
  captureNetwork: false,
  slimDOM: 'all',
  inlineStylesheet: false,
});

Use a project-scoped browser key with replay:write. Replay chunks are not authorized by ingest:write. Do not add read, management, or agent scopes to a browser key. The project must still exist and be active when a chunk is admitted. If its current state cannot be verified, the upload is refused temporarily; a cached content-policy decision cannot authorize the upload.

Replay also enforces recording safety limits: at most 1,000 chunk indices (0–999), 100,000 events, 32 MiB of event JSON and 32 MiB of compressed data per session. Individual chunks are capped at 8 MiB, in addition to the upload request size limit. Exceeding a recording limit returns 429; retrying the same oversized upload does not increase the limit. Replacing an accepted chunk counts only its replacement size. Older recordings can refuse additional uploads when their prior usage cannot be verified.

Call stopReplay() when the application tears down the recorder. Chunks also flush during normal page navigation. The stop function is idempotent.

The default sessionId is scoped to the current browser tab and is shared by replay, events, errors, traces, and logs from that tab. Reloading the tab preserves it; opening or duplicating another tab creates a separate session and replay boundary. The recorder also preserves its next chunk index across a same-tab reload. Do not copy one explicit sessionId into recorders in multiple tabs: each tab has an independent chunk cursor, so their uploads can replace one another. Call identify() after authentication in each tab and call reset() when that tab logs out. Browser reset() first stops all active recorders and flushes their buffered prior-user tail, then rotates identity and session. This prevents the recorder started for one user from capturing the next user's UI. Call startReplay() again after identifying the next user if recording should resume.

Deleting the owning project or organization permanently blocks later Replay writes once the delete is consumed. Cleanup waits for writers holding the recording locks, and revisits deleted scopes to remove late object uploads whose outcome was uncertain after a connection failure. Stop recording when its owning scope is deleted; retrying cannot recreate that scope.

Keep the defaults private

Input masking is enabled by default. Keep it enabled unless every affected field has been reviewed. Network and console capture are off by default, including when diagnostics and performance consent are granted. Set captureConsole: true or captureNetwork: true only after reviewing what the application logs or sends and filtering sensitive payloads at the source.

Replay, autocapture, click friction and page snapshots honour the same DOM markers: data-anectico-mask, .anectico-mask and .rr-mask mask an element and its descendants; data-anectico-block, .anectico-block and .rr-block block them. A masked element's text, labels, attributes and identifying selector values are withheld. Masked actions produce no click-friction event because their action label is private. Blocked subtrees produce no autocapture or friction events and become empty boxes in recordings and snapshots. Replay may lose styling inside a masked subtree because its original attributes are withheld. Markers also apply through open shadow roots, assigned slots and direct shadow-root text mutations. Mark a closed shadow component's host: its internal markers are not visible to the other collectors.

For application-specific rules, pass the same maskSelectors and blockSelectors to each collector you start. Replay and friction refuse invalid selectors before starting collection. Project capture settings narrow participating autocapture and page snapshots; they do not configure a separately started replay recorder or friction tracker.

Input masking is not a complete privacy policy. Review ordinary DOM text as well as form values; do not assume maskAllInputs hides every name, address, or account detail rendered on a page. Disabling console and network capture does not disable DOM recording. Keep replay off for journeys whose rendered content you are not prepared to store. Stop the recorder before navigating to or rendering an unapproved sensitive screen, not after its contents have appeared.

Stopping flushes previously captured events; it does not erase a recording already uploaded. Test with synthetic sensitive values and inspect outgoing payloads for every enabled capture channel, including action labels and snapshots, before recording real customers.

Replay data is opaque at ingestion time; browser-side masking is therefore the important privacy boundary.

Network URL masking removes embedded credentials and masks sensitive query parameters, including nested keys and SPA hash-route queries. Malformed URLs are omitted. Body masking also recognizes encoded form keys and complete quoted secret values. Body limits count UTF-8 bytes.

Explicitly enabled console and body capture redact sensitive keys and recognized credential assignments, Bearer tokens, JWTs and email addresses inside scalar strings, including nested JSON and decoded form values. Redaction precedes truncation. Each console argument is limited to 1,000 visited entries, 10 levels and 64 Ki characters; only the first 20 arguments are captured. Bodies over 64 Ki characters are omitted before parsing. Cycles, accessors, unsupported values and data beyond these budgets are omitted. The original console call and application request receive their original values. These detectors cannot identify every secret or private sentence; keep auxiliary capture off when its content cannot be reviewed.

Response bodies are observed asynchronously through a bounded stream. Bodies exceeding the byte limit, taking longer than one second, or provided without stream support are omitted; metadata can still be recorded. Stopping replay cancels pending reads and prevents later capture events.

Network instrumentation failures do not prevent the application's request or replace its response. Multipart form data and streaming request bodies pass through unchanged, and a failure after dispatch never causes the SDK to resend an upload. For recorder diagnostics, read stopReplay.diagnostics().recorder: it reports the name and actual bundled version without exposing recorded data or credentials. These diagnostics are also available before replay consent.

Keep large pages responsive

Start the recorder after rendering a small application shell and before progressively mounting a long feed, table, or virtualized workspace. slimDOM: 'all' removes non-visual metadata from snapshots. inlineStylesheet: false avoids embedding linked CSS when the replay viewer can load the same stylesheet URL. These settings reduce recorder parse and snapshot work; they do not disable DOM mutations, network capture, console capture, or user interactions.

Normal size- and timer-triggered chunks use ordinary requests. Only the final pagehide tail uses a keepalive request, avoiding the browser's shared keepalive-body quota during large initial snapshots.

Connect replay to the backend

Keep browser request tracing enabled and allow trace-header propagation to your own API origin. Your backend must allow the traceparent, tracestate, and baggage request headers and must validate identity at a trusted boundary.

When this path is configured correctly, a replay can link to the backend trace and error produced by the same session.

Verify

  1. Start a development session under a recognizable test customer.
  2. Navigate through the application and trigger a test error.
  3. Ask your agent to find the recording (list_recordings over MCP, or anectico replay list). The MCP result carries a link; open it to play the session on its read-only replay proof page.
  4. Inspect input values and ordinary page text, including signing out and signing back in; confirm the recording contains no data that should have been excluded.
  5. Ask your agent for the session's related evidence (get_session_signals over MCP, or anectico replay related <session-id>) and confirm the related error and trace are present.
  6. Ask your agent for the customer's timeline (get_person_timeline) and confirm the replay appears on it.

If the recording is absent or cannot play, see Replay or symbols are not working.

Native structural replay

Native replay uses native_structural version 1 (NR1): bounded structural frames, recorded actions and explicit gaps rather than screenshots. Choose the recorder or adapter for your application:

Runtime Start here
iOS UIKit source and controls
Android Android View source and controls
React Native Host components and privacy boundaries
Flutter Dart adapter and privacy boundaries

Configure the separate project-bound replay_upload credential, current application owner and independent replay consent. Current native capture policy and native store permission must also allow capture. See native recording privacy for the policy definition; browser permission, analytics consent and flag identity do not grant it.

Inspect the recorder's diagnostics and upload outcome, then ask your agent for stored steps (get_replay_steps, or anectico replay steps <session-id>) and open the returned replay proof link. Reading steps needs replay:read, plus mcp:read over MCP; replay:content:read reveals detailed content. Check the reported format, source coverage, masks, blocks and gaps against your test actions. A receipt may acknowledge discard and never replaces retained readback. Follow native replay troubleshooting when capture, steps or viewing is unavailable. Device and runtime qualification remain separate.

Native flags and headless surveys connect product evidence to the customer timeline; they do not record visual replay. Their decision, receipt or flush cannot establish replay delivery. See mobile setup and evidence limits.

Recording format and upload receipts

The browser uploader declares rrweb_dom version 1. A recording has one explicit format and version; mixing it with native structure refuses. Upload acknowledgements bind project, session, chunk index, format, version, exact uncompressed array digest and byte, event and frame counts. Native structure uses native_structural version 1 and separate replay_upload authority. It needs a current explicit replay owner and consent, current capture revision, active project and native store permission; browser capture permission cannot grant any of those.

Compare every binding before releasing a frozen native upload item. A valid-looking receipt for different same-sized bytes is uncertainty; keep the exact item for bounded retry. An accepted upload can have been discarded and does not prove retention, playback or erasure. Recordings and all retained copies follow their original owner's authorized erasure reach and format-specific retention. Person archives include metadata with format and version and exclude raw content.

Native qualification status

Portable recorder and host tests have exercised native upload, retained structural playback, recorded steps and privacy controls on a local test deployment. A separate Android API 36 x86_64 emulator run builds and installs standard View, React Native 0.86 with Fabric and Hermes, and Flutter 3.47.7 release apps. It verifies native HTTPS, masked source, consent and owner fences, retained steps and proof viewing. This establishes only those emulator configurations. Apple runtimes, physical devices and published-package installation require separate checks.

No release-device throughput or battery claim follows from these tests. Engineering ceilings are safety limits, not measured sustainable rates. A receipt, copied source, fixture or mechanical agent result cannot replace a missing runtime check. Treat an unsupported region, refused read or missing recording as unknown; a gap does not prove nothing happened or that checkout succeeded.

iOS structural recording

The iOS SDK has an optional, separately consented structural implementation. Configure the separate project-bound replay-upload credential, explicitly bind the current application owner and grant replay consent only after your app obtains it. Both current native capture settings and native store permission must allow capture; missing or expired policy keeps it off. Browser permission, analytics consent and flag identities do not authorize native replay. See iOS setup and the control and source matrix.

The explicit UIKit source observes bounded boxes and app-observed actions. Inputs, text, images, accessibility values and survey answers are private before encoding. Masks propagate, blocks stop descendant inspection, and unsupported surfaces have visible gaps. Optional static action declarations need local and remote eligibility and are untrusted application evidence. SwiftUI, embedded web, custom drawing and framework adapters have no support claim from UIKit traversal.

A native upload receipt proves protocol acceptance only. Gaps, policy withholding, unsent losses and unsupported regions cannot establish a completed visit. Read stored steps or a returned proof link only when the deployment supports that format and current read policy permits it. Derived proof remains bound to the retained source; local withdrawal does not certify server erasure. Recorder-to-storage qualification, native device privacy and release overhead must run before publication.

React Native structural recording

The optional React Native source has separate native controls and component privacy hooks. Explicit current application ownership, independent replay consent and current native capture and store permission are required. Mask and block boundaries dominate declared safe labels; inputs, images and accessibility never supply raw content. Read the source matrix before describing coverage. Actual host frames retain an unsupported qualification gap, and the tested Android emulator configuration has separate retained-playback evidence. Other operating systems, architectures and physical devices remain unverified.

Flutter structural recording

The optional Flutter Dart adapter declares screens, structural regions, masks, blocks and safe actions, then submits sanitized frames to the native recorder. It requires independent replay ownership and consent, current native permission and its own project-bound upload key. Native hierarchy inspection cannot observe Dart UI. Custom painting and unqualified embedded regions are visible gaps. Host source tests do not establish Flutter on Android or iOS device support, release overhead or retained playback. A native upload receipt may mean discard; verify current stored steps and proof separately, under the source's current policy.