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
- Start a development session under a recognizable test customer.
- Navigate through the application and trigger a test error.
- Ask your agent to find the recording (
list_recordingsover MCP, oranectico replay list). The MCP result carries a link; open it to play the session on its read-only replay proof page. - 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.
- Ask your agent for the session's related evidence (
get_session_signalsover MCP, oranectico replay related <session-id>) and confirm the related error and trace are present. - 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.