Skip to content

Privacy

Session Replay and Cookie Consent: Record After Opt-In

How to record sessions only after consent: wiring a cookie banner to requireConsent, returning visitors, withdrawal, Tag Manager, Next.js and React Native.

Published · 6 min read

If your policy puts session replay behind a consent banner, the recorder has to do nothing until the visitor opts in, and stop when they opt out. With AnyReplay that means requireConsent: true in the options, consent(true) when the visitor accepts (and again on every later page load), and consent(false) when they withdraw.

This post is the implementation side: the banner category, the calls, returning visitors, Google Tag Manager, Next.js and React Native examples, and how to test it. Whether you need consent at all is a legal question for your own advice; session replay and GDPR gives the background. Nothing here is legal advice.

It depends on your jurisdiction, your legal basis and what you record, so there is no single answer. Many teams in the EU and UK decide to ask first, because a recorder observes behaviour and stores identifiers on the device. If you make that decision, the engineering job is to make the recorder follow it exactly, including for visitors who never touch the banner.

Does session recording use cookies?

The AnyReplay recorder sets no cookies. It does use browser storage: a random visitor id in localStorage, which keeps sampling stable per visitor, and session state in sessionStorage, which lets a session continue across reloads in the same tab. The React Native SDK stores ids only if you pass it a storage such as AsyncStorage.

With requireConsent: true that storage waits too. The visitor id and session state are created when consent(true) is called, not when the recorder loads, and consent(false) removes them. So "nothing before consent" covers storage as well as recording, and you do not have to delay loading the script to get it.

Which banner category?

Consent platforms group trackers into categories such as analytics, statistics or performance. Session replay does not have a universal home. Put it in the category your privacy policy describes it under, and make sure the banner text says that sessions may be recorded, what is masked, and how long recordings are kept.

How do I stop recording until a user accepts cookies?

Initialise with requireConsent: true. The recorder loads and waits: no snapshot, no events, no requests and nothing in browser storage until you grant consent. Then connect both hooks your consent platform offers, the stored choice on page load and the change event.

anyreplay('init', { projectKey: 'YOUR_PROJECT_KEY', requireConsent: true });

// 1. Every page load: the visitor may have accepted on an earlier visit.
if (bannerHasStoredChoice()) {
  anyreplay('consent', bannerAllows('analytics'));
}

// 2. When the visitor decides, or changes their mind.
onBannerChoiceChanged(() => {
  anyreplay('consent', bannerAllows('analytics'));
});
Replace the three banner functions with your consent platform's API.

Step 1 is the one teams forget. AnyReplay does not remember the choice for you: each page load starts out waiting again. A visitor who accepted last week will not be recorded today unless your code calls consent(true) again on load.

If you would rather not download the recorder at all until acceptance, keep the queue stub and your init call and add the script to the page only once the visitor has accepted. Calls queued before the script arrives are replayed when it loads. Both approaches store nothing before consent; the difference is only whether the script is fetched.

Call consent(false). The recorder stops for the rest of that page, or that app session on mobile, and removes the visitor id and session state it stored — on this visit or an earlier one — so the person is not recognised next time, and a later acceptance starts them as a new visitor. Anything already sent stays in the session; nothing more is recorded. A later consent(true) on the same page does not restart it; recording resumes from the next page load where your code grants consent again.

Withdrawal stops future recording. It does not delete sessions already recorded. Those expire under your organisation's retention setting, and a moderator or above can delete a session sooner if your process requires it.

Google Tag Manager: the two-tag pattern

  1. Keep the AnyReplay Custom HTML tag on All Pages, with requireConsent: true in its init options.
  2. Add a second Custom HTML tag containing only anyreplay('consent', true);.
  3. Fire the second tag from your consent platform's trigger for the category you chose, such as "analytics granted". Many platforms fire that trigger on every page load for visitors who already accepted, which also covers returning visitors; check that yours does.

If you prefer not to load the recorder until acceptance, fire the AnyReplay tag itself only from the consent trigger instead, and leave out requireConsent.

Next.js and React

With the npm package, create the recorder once in a client module and expose a function for the banner. The Next.js integration and Next.js install guide show the full component.

'use client';

import type { RecorderHandle } from '@anyreplay/browser';

let starting: Promise<RecorderHandle> | null = null;

function getRecorder(): Promise<RecorderHandle> {
  starting ??= import('@anyreplay/browser').then(({ createRecorder }) =>
    createRecorder({
      projectKey: process.env.NEXT_PUBLIC_ANYREPLAY_KEY!,
      requireConsent: true,
    }),
  );
  return starting;
}

/** Call on load for visitors who already accepted, and from the banner. */
export async function setRecordingConsent(granted: boolean): Promise<void> {
  if (!granted && !starting) return; // never created, nothing to stop
  (await getRecorder()).consent(granted);
}
app/anyreplay.tsx
useEffect(() => {
  if (readStoredChoice() === 'accepted') void setRecordingConsent(true);
}, []);

<button onClick={() => { saveChoice('accepted'); void setRecordingConsent(true); }}>Accept</button>
<button onClick={() => { saveChoice('rejected'); void setRecordingConsent(false); }}>Reject</button>
In your banner or a layout effect

Because the recorder is only created inside setRecordingConsent, this version also satisfies the strict variant: a visitor who never accepts never has a recorder created. The dynamic import keeps it out of server rendering. In Next.js, client-side navigation keeps the same recorder; a full reload runs the effect again.

React Native and Expo

Mobile apps usually show their own consent screen, so storing the choice is your job. Keep it in AsyncStorage and grant consent on each launch. init returns a promise, so wait for it before calling consent; a call made before the recorder exists has nothing to act on.

import AsyncStorage from '@react-native-async-storage/async-storage';
import { init, consent, identify } from '@anyreplay/react-native';

// init is asynchronous: wait for it before calling consent or identify.
const ready = init({ projectKey: process.env.EXPO_PUBLIC_ANYREPLAY_KEY!, requireConsent: true, storage: AsyncStorage });

export async function restoreConsent(user?: { id: string }) {
  await ready;
  if ((await AsyncStorage.getItem('recording-consent')) === 'yes') {
    consent(true);
    if (user) identify({ userId: user.id });
  }
}

export async function setConsent(granted: boolean) {
  await AsyncStorage.setItem('recording-consent', granted ? 'yes' : 'no');
  await ready;
  consent(granted);
}

If your policy forbids storing the visitor id before consent, leave out the storage option until the visitor has agreed, or call init only after consent. See the Expo integration for the rest of the setup.

Queued events and identify

Custom events you send with track while the web recorder waits are queued, up to 32 of them, and emitted right after the first snapshot once consent is granted. They are stamped when emitted, so they appear at the start of the recording rather than at the moment you called track. After consent(false) new events are dropped.

Hold identify until after consent. Attaching a user id or email to a visitor who has not agreed to recording defeats the point of waiting. See identify users.

Test it before you ship

  1. Start clean

    Open a private window, or clear site data, so no stored choice exists.

  2. Watch the network panel

    Filter requests by in.anyreplay.com and browse a few pages without touching the banner. There should be no requests.

  3. Check storage if it matters to you

    In the Application panel, look at local and session storage to confirm your chosen variant behaves as your policy says.

  4. Accept

    Requests to the ingest host should start. With the npm package, recorder.status() changes from awaiting-consent to recording.

  5. Reload and withdraw

    Reload to confirm returning visitors are recorded, then reject and confirm requests stop.

Add debug: true while testing and the recorder logs why it is not recording, including consent_withdrawn after a rejection. Masking applies regardless of consent, so review consent in the docs and masking together.

Frequently asked questions

Does session replay require cookie consent?

It depends on your jurisdiction, legal basis and what you record. Many EU and UK teams ask first because recorders store identifiers on the device. This is not legal advice; confirm with your own counsel.

Does session recording use cookies?

The AnyReplay recorder sets no cookies. It stores a visitor id in localStorage and session state in sessionStorage, and rules about storing information on a device may still apply to that.

How do I stop recording until a user accepts cookies?

Initialise the recorder with requireConsent set to true, call consent(true) when the visitor accepts and on every later page load for visitors who already accepted. For no device storage before consent, load the recorder only after acceptance.

What happens if a user withdraws consent?

Calling consent(false) stops recording for the rest of the page or app session. Data already sent stays until retention expires or someone deletes the session, and nothing new is recorded.

Start free — 1 project, 500 sessions a month

Install the snippet, record a session, and watch it back in your own language.