Skip to main content
Sampling determines how much real user data gets collected and reported. Set the sampling rate too high and you pay for data you don’t need; set it too low and you may miss critical issues. This article explains how RUM sampling works and provides best practices for choosing and dynamically managing sampling rates.

What is sampling

The RUM SDK controls sampling through the sessionSampleRate parameter, a value from 0 to 100 representing the percentage of sessions to collect:
A sampled session reports all of its data (page views, resources, errors, user actions, and so on); an unsampled session reports nothing — including errors. This is the first key point about sampling: with a 20% sampling rate, if an error affects users in the other 80%, you will not see it on the platform. sessionReplaySampleRate is a second-stage sample applied on top of collected sessions: with sessionSampleRate: 20 and sessionReplaySampleRate: 10, sessions with replay recordings account for 2% of total traffic.

How sampling works

Understanding the following four rules resolves most “I configured the sampling rate but it doesn’t behave as expected” confusion.

1. The unit is the session, not the user or the event

The sampling decision happens when a session starts: the SDK decides once, with probability sessionSampleRate, whether to collect the session. If it does, the session is reported in full; otherwise it stays completely silent. There is no such thing as “20% of events within a session get reported” — session data is either complete or absent. The same user’s session may be sampled today and not sampled tomorrow. The default sampling mechanism is not anchored to specific users.

2. The decision is sticky within a session

The draw result is persisted with the session state (stored in a cookie on the web). A session lasts up to 4 hours while the user stays active, and expires after 15 minutes of inactivity; refreshing or navigating between pages does not trigger a new draw. Only when the session expires and a new one starts is the decision made again, using the sampling rate in effect at that time.
This means that after you change the sampling rate, new sessions immediately follow the new rate, while existing sessions keep their original decision until they expire naturally. This is exactly the right semantics for gradual rollout, but it also means the change does not take full effect instantly.

3. It is a probability, not an exact quota

Each session’s draw is independent, with no global coordination. A 20% sampling rate is an expected value. The more traffic you have, the closer the actual ratio gets to 20% (law of large numbers). With low traffic, it fluctuates: collecting 13 or 28 out of 100 sessions is normal.

4. The sampling rate is set when a session is drawn

Each new session is drawn at the rate in effect at that moment: the value delivered from the console when remote configuration is on, otherwise the sessionSampleRate passed to init(). init() can’t be called a second time while the page or app is running. Without remote configuration, changing the sampling rate means the next initialization (on the web, the next page load) must receive the new value.

Choosing a sampling rate

Errors are low-frequency events. If your main goal is error monitoring rather than performance statistics, use a higher sampling rate. A 20% sample represents performance metrics well, but an error that affects only 1% of users can take a long time to show up.

Best practice 1: adjust sampling dynamically with remote configuration

A sampling rate hardcoded in your source requires a release for every change. With remote configuration on, you change sampling settings on the Application management → Remote configuration tab in the console and publish them. Clients pick them up without a code change or release. Typical uses:
  • Raise the session sample rate while a new version rolls out, then lower it back once it’s stable.
  • Set it to 100 while you investigate a production issue, to capture as much evidence as possible.
  • Lower it when data volume spikes, or set it to 0 to stop collection in an emergency.
  • Lower the rate and turn on Capture additional sessions on error together, so sessions that weren’t drawn still report the lead-up when they hit an error. See best practice 3.

Enabling it and platform support

Set remoteConfigurationEnabled: true at SDK initialization (off by default). On Android and HarmonyOS, call .setRemoteConfigurationEnabled(true); on Electron, enable it in the main-process init(). Keep passing sessionSampleRate at initialization: the SDK uses it until the client first receives a configuration, and again after you disable remote configuration in the console. Each platform reads a different set of values. Values not listed for a platform have no effect there: The React Native SDK doesn’t support remote configuration; its sampling rate can only be set in init().

When changes take effect

  • At the next new session: the SDK requests the configuration at startup and whenever a new session starts. A new configuration applies to the next draw, and running sessions keep their decision (rule 2). Clients usually switch within about 4 hours of a publish.
  • Stored locally: the configuration is saved on the device, so the first session after the next launch (on the web, the next page load) is drawn with it right away. On the web, a page with nothing stored yet (such as a first visit or a private window) usually waits about 1 second for the configuration before collection starts, and the wait can be longer in a background tab. If the configuration does not arrive in time or the request fails, the first session uses the init() settings. Uncaught errors and console errors raised during the wait aren’t collected.
  • Failures don’t change anything: if the request fails, times out, or returns something unreadable, the SDK keeps the configuration already in effect.
These changes don’t wait for the session to expire. The SDK ends the current session right away, and the next session is drawn under the new configuration:
  • The session sample rate is set to 0 (emergency stop), or raised from 0 to a non-zero value (resume).
  • On the web, the replay privacy level becomes stricter.
All other changes wait for the next new session. If you need a change to apply right away, call the SDK’s stopSession() to end the current session.
stopSession() splits one user’s continuous activity into two sessions, which slightly inflates the session count and breaks session-duration statistics. Call it only when you need the change right away.

Confirming the configuration is in effect

After publishing, check adoption in the rollout panel on the Remote configuration tab. It shows the share of recently arrived sessions on each configuration version. If the status stays at No configuration received, the SDK usually has remote configuration disabled or is older than the minimum version above. For publishing, rollback, and adoption details, see Application management - Remote configuration.

Best practice 2: business-defined custom sampling

The default random draw treats every user the same, but businesses often want to differentiate: collect everything for VIP users, watch canary users closely, always collect users under investigation. The SDK provides these interfaces so your code decides each session’s sampling rate: Put the rule parameters (lists, switches, the bucketing salt) in the custom configuration keys of remote configuration and you can deliver them dynamically from the console: changing a list or turning on investigation mode needs no release, and takes effect at the next new session as described in When changes take effect. For example, publish these custom configuration keys in the console:
The example below checks rules in priority order: investigation mode and key users are collected in full, and everyone else is bucketed by a hash of the user ID, with the bucket size taken straight from the session sample rate delivered from the console.
Flutter, HarmonyOS, and WeChat Mini Program provide the same beforeSampling, getRemoteConfig(), and setForcedSession(); see each platform’s advanced configuration page. The Electron and React Native SDKs have no beforeSampling: decide before init() yourself, then set sessionSampleRate to 0 or 100. When support or engineering needs to look into one user right away, you can also call setForcedSession() from your code instead of adding the user to debugUsers, for example when the user submits feedback:

Why hash bucketing instead of random numbers

The base rule uses hash(userId) % 100 instead of Math.random(), which brings two properties the default draw lacks:
  • User-level stability: the same user always gets the same decision, so you can answer “does user A have data?”. A sampled user has all sessions, and an unsampled user has none.
  • Monotonic rollout: when the rate goes from 20% to 100%, every sampled user stays sampled and new users are only added. The data stays continuous and comparable. Changing the salt reshuffles all buckets.

Caveats

  • Keep the callback light and deterministic. beforeSampling runs synchronously while a session is being created, so it must not make network requests or block, and the same input should give the same result. Drawing with Math.random() makes the same user’s decision flip back and forth; deterministic hashing avoids this by construction. If the callback returns a value outside 0–100 or throws, the SDK keeps the incoming rate.
  • Rule changes take effect at the next new session. When a user moves from the “not sampled” bucket to the “sampled” one, the running session isn’t re-decided. To apply it right away, call setForcedSession() or stopSession().
  • Custom configuration keys are public. They’re delivered to clients, and anyone holding the client token can read them. Never put secrets, access tokens, or personally sensitive information in them. Lists such as debugUsers should hold only user IDs that can’t be traced back to a person.
  • Use sessionSampleRate: 0 instead of skipping init(). Skipping initialization breaks addAction / addError calls across your code and forces null checks everywhere. Passing 0 initializes a silent SDK and keeps one code path.
  • Platform-side data reflects what was actually collected. With custom sampling, the platform can’t know your true sampling ratio. The session volume you see is what was collected, and you can’t extrapolate it to total traffic. If you need full-traffic estimates, compute them on your side based on your own sampling rules.

Best practice 3: full error capture with proportional sampling for the rest

Starting with Web SDK 0.3.0, enable sessionOnError to capture error sessions without your own beforeSend. Add sessionReplayOnError if you need error replays. See on-error session capture for configuration, billing, and limitations. The recipe below remains available for older versions or custom event filtering. Its failed-request filtering differs from the new options: a failed resource event alone does not trigger on-error capture.
A common requirement is “cut data volume to 20%, but never miss an error”. Setting sessionSampleRate to 20 will not do it: sampling works per session, and a session that loses the draw reports nothing at all — errors included (rule 1). You would lose roughly four fifths of your errors along with the volume. The custom filtering recipe below collects every session so no error is lost, then buckets on a stable key inside beforeSend so only the winning share keeps full data:
  • Error events: always kept — 100% of errors
  • Failed requests: always kept — 100% of API failures (HTTP 5xx and request failures are resource events, not error events)
  • Successful requests, user actions, long tasks: kept only for the winning 20% — the bulk of your volume converges to 20%
Pick one bucketing style — each tab below is a complete, copy-pasteable recipe. Choose with this table first: One more thing if you use Session Replay: it needs an alignment step. Natively, replay is a second-stage sample on top of collected sessions. This recipe sets sessionSampleRate to 100, so the SDK treats every session as fully collected and samples replay across all of them. Some replays land outside the detail bucket and show up as “has a replay, but no behavior data”. Each tab gives the matching fix — skip it if you do not use replay.
The complete data-sampling configuration:
Replay alignment: the session ID doesn’t exist yet at init(), and the replay rate is fixed at initialization. Turn off the automatic draw, switch to manual recording, and do the second draw yourself after initialization. Merge the two options below into the init() above and run the decision once after init():
The manual approach has one known limit. When a session expires and renews while the page stays open (15 minutes idle or 4 hours total), the new session gets a new ID and bucket. The SDK exposes no session-renewal event. Pages with very long dwell times need to poll getInternalContext().session_id for changes to stay strictly aligned.
With either approach the replay set is strictly a subset of the detail bucket, and the final recording share is DETAIL_SAMPLE_RATE × REPLAY_SAMPLE_RATE — exactly what native sessionSampleRate: 20 plus sessionReplaySampleRate: 10 yields at 2%.
Keep a marker such as rum_sampling. Unsampled sessions have an inherently incomplete timeline — only views, errors and failed requests. Without a marker, a session that shows no clicks at all leaves you unable to tell whether sampling dropped them or the user genuinely never clicked. With the marker, the gaps are explained: an errors_only session was never meant to carry behavior data, while anything missing from a full session really did not happen. Filter on rum_sampling:full when you need complete timelines. context is a field beforeSend is allowed to modify, so the marker and the drop decision live in the same function and cannot drift apart.
Bucket on a stable key (session or user) — do not draw per event with Math.random().Drawing per event is statistically harmless: what you keep is an unbiased sample, and aggregates such as error rate and P75 remain accurate. The damage shows up when you investigate a single session.Picture a session that produces these events in order: land on a product page, click “Buy now”, fire three API requests (one of which returns 500), then throw an exception. With a per-event draw, the error and the failed request survive because they are allow-listed, while the click happens to lose its draw and is dropped. What you see in the session is: the user opened the page, did nothing, and then a request failed and the page threw.The real problem is not the one missing event — it is that the gaps fall in different places in every session, and you cannot tell them apart. Seeing no click in a session, you cannot know whether sampling dropped it or the user genuinely never clicked. The session stops being evidence and becomes unreliable testimony.Bucketing on a stable key leaves only two states: sampled sessions carry the full picture, and the rest carry errors and failed requests only. Neither state misleads you. The reasoning is the same as in hash bucketing.

Three things to know before adopting this

  • Volume will not land exactly on 20%. View events are the skeleton of a session and cannot be dismissed, so they still report at 100%. How far volume actually falls depends on the share of resource and action events in your data.
  • Absolute counts need to be scaled. The platform still shows 100% of sessions, but only about 20% carry full data. Ratios and percentiles such as error rate and P75 are unaffected, because a random sample still represents the whole. Scale absolute counts, such as total resource requests or clicks, by 1 / 20%.
  • Session Replay needs one explicit alignment step. This recipe sets sessionSampleRate to 100, so the SDK assumes every session is collected in full and draws replay across all of them. Skip the alignment step in the matching tab above and replay lands on sessions outside the bucket — you get the recording, but the session behind it holds no behavior data.
Compare it with the “report only errors” configuration. That one turns non-error data off entirely and suits teams that only care about errors. This one keeps one fifth of the full picture and suits teams that want every error plus performance and behavior analysis.

Platform support

For minimum versions and the values each platform reads, see Enabling it and platform support. Every platform can call stopSession() to end the current session so a new rate applies right away.

FAQ

Strictly reporting only errors is not possible — view events are the backbone of a session and cannot be turned off. But you can get very close, by combining two independent controls:
  • The sampling rate decides which sessions are collected. It operates on whole sessions, and an unsampled session reports nothing at all, errors included (rule 1). So lowering the sampling rate is not a way to save volume — it drops your errors along with everything else.
  • The event switches decide which events each collected session reports. trackResources, trackLongTasks, trackUserInteractions and trackWebVitals are independent of sampling and apply to every session that is collected.
So the right configuration for “show me errors and as little else as possible” is: set sessionSampleRate to 100 so no error is missed, then use the event switches to suppress non-error data. Resource events are usually the bulk of the volume, so trimming them pays off the most.
Do not simply set trackResources: false. In the browser, HTTP 5xx responses and failed requests are not error events — they are resource events carrying a status_code. RUM error events come only from JavaScript runtime exceptions, console.error, the browser Report API and manual addError calls. Turning resource collection off makes API failures disappear from the platform entirely, and those are often exactly the “errors” you care about most.
Keep resource collection on instead, and use beforeSend to discard only the successful requests:
Understand the three costs before adopting this setup:
  • View events are still reported. At least one per page, plus throttled updates whenever metrics or event counts change, plus a keep-alive update every 5 minutes while the session is active. This baseline cannot be removed, and beforeSend cannot discard view events either.
  • Error evidence is reduced to a stack trace. With trackUserInteractions off, you no longer know what the user clicked before the error, which makes investigation noticeably harder.
  • Traced requests are not affected by the resource switch. Requests matching allowedTracingUrls are still reported with resource collection off. They’re flagged as not indexed and don’t count toward volume, but traffic doesn’t drop to zero.
If your goal is “errors first, but keep the evidence”, use best practice 3 instead. It keeps all errors and failed requests and samples everything else proportionally, so volume stays low without losing evidence.