# Session replay scoping that keeps production privacy and signal

> Scope session replay with sample rates, masking defaults, and error-first capture so AI-built apps debug real sessions without shipping PII.
> By Dave · 2026-09-11
> Source: https://otf-kit.dev/blog/session-replay-scoping-production

Session replay is useful in production only when you decide *what* gets recorded, *how often*, and *what never leaves the browser*. If you turn it on at 100% with masking relaxed "just for a week," you buy noise and privacy risk. If you leave defaults alone and pick sample rates from real traffic, you get the sessions that explain bugs — without shipping PII.

This post is about **scoping**: `replaysSessionSampleRate`, `replaysOnErrorSampleRate`, privacy masking defaults, and error-first capture. It is not a crash triage walkthrough, not LLM tracing, and not release health. For those, start with [crash triage that ships fixes](/blog/sentry-crash-triage-ships-fixes), [React Native error tracking in production](/blog/sentry-error-tracking-react-native-production), [release health for AI-built apps](/blog/release-health-production-ai-app), and [LLM observability](/blog/llm-observability-guide).

## What session replay actually records

Sentry Session Replay reconstructs a video-like view of the DOM: clicks, scrolls, navigations, network activity, and console entries around an issue. On the web SDK path, recording runs in the browser before anything is uploaded. Defaults are deliberately strict: mask all DOM text, block media, and mask input values before send. That is the privacy floor you should treat as production-safe until you have tested every unmask.

A session starts when the Replay SDK initializes. It keeps capturing across pageloads and same-tab navigations on the same domain until idle for **5 minutes** without interaction, until **60 minutes** elapse, or until the tab closes. Interactions that reset idle are clicks and browser navigations. Those lifetime rules matter when you size storage and when you explain why a long support call did not produce one continuous replay.

Error-only capture is a first-class mode, not a fallback hack. When a session is *not* selected by `replaysSessionSampleRate`, the SDK still buffers roughly **one minute** of events in memory. If an error fires and `replaysOnErrorSampleRate` selects the session, that buffer plus the rest of the session uploads. If nothing errors (or the error path does not sample), the buffer is discarded and nothing is sent. That is the core scoping lever for production: pay for continuous sessions on a fraction of traffic, and keep full fidelity when something breaks.

## Sample rates that match traffic, not vibes

Two knobs, checked in order:

1. **`replaysSessionSampleRate`** — fraction of sessions recorded fully and sent in real time.
2. **`replaysOnErrorSampleRate`** — fraction of *non-session-sampled* sessions that still upload when an error occurs (with the ~60s pre-error buffer).

Sentry's documented production guidance (use these as starting points, then adjust from your quota and support load):

| Traffic volume | Session rate | Error rate |
| --- | --- | --- |
| High (100k+ sessions/day) | `0.01` (1%) | `1.0` |
| Medium (10k–100k/day) | `0.1` (10%) | `1.0` |
| Low (under 10k/day) | `0.25` (25%) | `1.0` |

Keep the error path at **`1.0`** in production unless you have a measured reason not to. Error sessions are where replay pays for itself. Lowering session rate is how you control volume; lowering error rate is how you lose the debugging signal.

During local verification, set `replaysSessionSampleRate: 1.0` so every session uploads. Drop it before you ship. Leaving 100% session sampling in production is the most common scoping failure on young AI-built apps: the product still works, the bill and the review queue do not.

![Session sample vs error-sample vs discard decision diagram](https://cdn.otf-kit.dev/thumbnails/session-replay-scoping-production-inbody-02-20260911c.png)

## Production init that keeps privacy defaults

Minimal production-shaped init (JavaScript browser SDK pattern from Sentry docs). Replace the DSN and pick the session rate from the table above:

```javascript
import * as Sentry from "@sentry/browser";

Sentry.init({
  dsn: process.env.NEXT_PUBLIC_SENTRY_DSN,

  // Medium traffic example — change to 0.01 or 0.25 from the table
  replaysSessionSampleRate: 0.1,

  // Keep error-triggered capture complete in production
  replaysOnErrorSampleRate: 1.0,

  integrations: [
    Sentry.replayIntegration({
      // Defaults — keep true unless you have tested every screen
      maskAllText: true,
      blockAllMedia: true,
      maskAllInputs: true,
    }),
  ],
});
```

Privacy options you will reach for after the defaults:

- **`mask` / `unmask`** — CSS selectors (plus defaults like `.sentry-mask` / `[data-sentry-mask]`).
- **`block` / `unblock`** — replace elements with same-size placeholders (`.sentry-block` / `[data-sentry-block]`).
- **`ignore`** — drop input *events* for matching fields (`.sentry-ignore` / `[data-sentry-ignore]`).
- **`maskFn`** — custom character masking (default repeats `*` to string length).

Do **not** set `maskAllText: false` or `blockAllMedia: false` for a marketing site "because there is no PII" without a pass over auth, search, support chat, and any user-generated content. Sentry's privacy docs are explicit: if you change masking, or if your UI framework changes how text and media render, re-test before production. If you find a leak, stop shipping replay until it is fixed.

From SDK v8 onward, `unblock` and `unmask` no longer inject older default selectors automatically. If you rely on `data-sentry-unmask` / `sentry-unmask` in components, opt in:

```javascript
Sentry.replayIntegration({
  maskAllText: true,
  blockAllMedia: true,
  unmask: [".sentry-unmask", "[data-sentry-unmask]"],
  unblock: [".sentry-unblock", "[data-sentry-unblock]"],
});
```

Bake privacy into shared UI primitives (avatar, username, free-text input) with `data-sentry-mask` / `data-sentry-block` so every screen inherits the rule. Prefer that over hunting pages after launch. Avatars rendered as CSS `background-image` on a `div` are a common miss: default media blocking targets real media elements, so mark those wrappers with `data-sentry-block`.

## Error-first capture as the default product stance

For many AI-built apps under 10k sessions/day, a practical stance is:

- Session sample at **0.25** (or lower once support can still reproduce issues).
- Error sample at **1.0**.
- Masking left at defaults.
- Network request/response bodies left **opt-in** and limited to non-PII routes.

That combination means most quiet sessions never leave the device, while almost every errored session still gives you the minute before the failure. Pair it with solid issue triage ([crash triage that ships fixes](/blog/sentry-crash-triage-ships-fixes)) so replay is attached to a fix queue, not a curiosity tab.

If your traffic is already high, jump straight to `0.01` / `1.0`. Do not "warm up" at 100% session sampling in production to "see what we get." You already know what you get: more of everything, including sessions you will never watch.

![Byte and Nova adjusting sample-rate dials beside an error replay buffer](https://cdn.otf-kit.dev/blog/session-replay-scoping-production/inbody-01-cast-20260911d.jpg)

## Scope decisions that bite later

**Canvas recording.** Canvas replay is opt-in via `replayCanvasIntegration()`. There is currently **no PII scrubbing** in canvas recordings. Treat that as a hard scope decision: enable only if the canvas cannot show secrets, messages, or identifiers — or keep it off.

**CSP and the compression worker.** Replay uses a Web Worker for compression. Your Content-Security-Policy needs `worker-src 'self' blob:` (and `child-src 'self' blob:` for older Safari). Missing this shows up as "replay mysteriously empty," not as a clear privacy win.

**Network bodies.** Capturing request/response bodies is opt-in for a reason. Prefer allowlists of safe endpoints over global capture. Server-side scrubbing exists as a backstop; it is not a license to record auth payloads.

**Lazy-loading Replay.** You can init Sentry without Replay, then `addIntegration(replayIntegration())` later to shrink the critical path. Scoping rules (sample rates + privacy) still apply the moment Replay is added — lazy load is not a privacy control.

## Checklist before you flip production on

Use this as a PR checklist for AI-built apps:

1. **Pick session rate from traffic band** (0.01 / 0.1 / 0.25) — not from a tutorial that used `1.0`.
2. **Set `replaysOnErrorSampleRate: 1.0`** unless you have quota math that says otherwise.
3. **Leave `maskAllText`, `blockAllMedia`, and input masking on**; unmask only named safe selectors you tested.
4. **Mark design-system PII surfaces** with `data-sentry-mask` / `data-sentry-block` (avatars, names, message bodies).
5. **Decide canvas explicitly** — off unless you accept no PII scrubbing.
6. **Add CSP `worker-src blob:`** (and `child-src` if you still support old Safari).
7. **Keep network body capture opt-in** and route-scoped.
8. **Verify in staging with session rate 1.0**, then lower before prod promote.
9. **Confirm idle/max session rules** match how support expects replays to look (5 min idle / 60 min max).
10. **Link replay to your error and release process** — do not treat it as a separate product from [error tracking](/blog/sentry-error-tracking-react-native-production) or [release health](/blog/release-health-production-ai-app).

## Where OTF kits fit

If you start from an [OTF kit](https://otf-kit.dev/templates), you get owned source plus AI-tool configs (Cursor, Claude, and related agents) so the agent extends *your* repo instead of a throwaway sandbox. Observability still has to be scoped on purpose: paste the init above into the web client, set rates from your traffic band, and keep masking defaults until a privacy review says otherwise. One-time payment kits do not remove the need for production sample-rate math — they just mean the agent is editing code you keep.

## Sources

- [Sentry Session Replay product overview](https://docs.sentry.io/product/explore/session-replay/)
- [Set up Session Replay (JavaScript) — sample rates, error buffering, CSP](https://docs.sentry.io/platforms/javascript/session-replay/)
- [Session Replay privacy — masking defaults and CSS classes](https://docs.sentry.io/platforms/javascript/session-replay/privacy/)
- [Session Replay configuration — `replaysSessionSampleRate` / `replaysOnErrorSampleRate`](https://docs.sentry.io/platforms/javascript/session-replay/configuration/)
- [OTF templates — kits with AI configs](https://otf-kit.dev/templates)
