Session recordings in apps

On the web X3 records the DOM with rrweb (recordings). Apps have no DOM, so the app SDKs record a wireframe: the layout of the screen as boxes, texts as masked shapes, images as placeholders, inputs never. The wireframe is turned into rrweb events on the device and plays in the same X3 player as your web recordings, with a phone frame, the screen path instead of a URL and an App line (platform, OS and app version).

ApproachHowPrivacyCost
Wireframe (default)the SDK reads the view tree and sends frames, colours, masked texts and placeholders; only changes are sentcontent never enters the packetlow: a tree walk only after changes
Screenshot (opt-in per screen)a low-resolution image at most once per second after changes, masked regions blacked out before encodinggood only with clean maskingnoticeable CPU and memory
videonot offered––
Recording pipeline for appsOn the device the SDK reads the view tree, masks inputs always and texts and images by default, builds wireframe diffs with taps and friction markers, buffers and uploads compressed packets to /v1/m/r through your domain. At X3 the packets get a second PII scan, sampling and keep rules, encrypted storage, and play in the same rrweb player as web recordings, with a phone frame.ON THE DEVICEAT X3 (AS ON THE WEB)Read view treeUIKit, SwiftUI hosting,Compose, React NativeMaskinputs always, texts andimages by defaultWireframe diffplus taps, gestures,rage and dead tapsBuffer, gzipmax 8 MB, Wi-Fionly optionalUpload /v1/m/revery 10 s and whengoing to backgroundPII scansecond lineof defenceSampling, keeprecording level,daily capsStorageencrypted,project retentionrrweb playerphone frame,isolated stageVisit and leadscreen path,app version, OSMCP and AImasked logs,never pixelsHTTPS, your domain
Recording pipeline for apps

Turning recordings on

Recordings need all of these:

  1. the recorder module: iOS X3KitReplay, Android io.x3growth:sdk-replay, React Native and Flutter bring it along;
  2. recording.mode = wireframe in configure;
  3. the purpose recordings used (purposes.recordings = true) and granted in your consent mode (consent);
  4. recordings enabled in the project (Einstellungen › Aufnahmen & Heatmaps, Settings › Recordings & heatmaps), with the project's level, sampling, daily caps and screen exclusions.

The stricter side always wins. Recordings use the same smart keep rules as the web: converted, lead, hot, friction, a sample and your watch rules decide which sessions are kept after they end.

X3.configure(key: "pk_3x_YOUR_APP_KEY", options: X3Options(
  consent: X3ConsentOptions(mode: .optIn, purposes: .init(recordings: true)),
  recording: X3RecordingOptions(mode: .wireframe, indicator: .pill, sampleRate: 1.0, wifiOnly: false)
))
OptionDefault
modeoffwireframe turns the recorder on
maskAllTexttruetexts as shapes; false shows texts unless masked (the server's PII scan still runs)
maskAllImagestrueimages stay placeholders in wireframe mode in any case
screenshotScreens[]screens recorded as screenshots (below)
indicatorpillpill, dot or hidden (below)
sampleRate1.0share of sessions recorded, decided per session
wifiOnlyfalseupload only on Wi-Fi or ethernet (the buffer keeps up to 8 MB meanwhile)

What is recorded

  • screens with their wireframe, scrolling, taps with position and tap target name, swipes, the keyboard opening and closing (height only), rotation;
  • friction markers: rage taps (3 taps within 1 second within 30 pt), dead taps (a tap on an interactive element without any change within 500 ms);
  • your tracked events as markers (names only), background and foreground.

Never recorded, under every option: what users type (inputs appear as empty or "••••" fields), keystrokes, secure and password fields, payment and card fields, WebViews, maps, camera and video previews, SurfaceView / TextureView, the clipboard, network traffic and logs.

Masking

Masking happens on the device, before anything is sent; the server scans the packets a second time for personal data. The default (strict) masks every text and image. Unmask on purpose, view by view:

iOSAndroidReact NativeFlutter
always maskview.x3Masking = .mask, SwiftUI .x3Mask()X3.setMasking(view, X3Masking.MASK), Modifier.x3Mask()<X3Mask>not in 1.0.0 (the Flutter view is one masked area)
show textview.x3Masking = .unmask, SwiftUI .x3Unmask()X3.setMasking(view, X3Masking.UNMASK), Modifier.x3Unmask()<X3Unmask>not in 1.0.0
tap target nameview.x3Anchor = "buy_button", .x3Anchor("buy_button"), else accessibilityIdentifierX3.setAnchor(view, "buy_button"), Modifier.x3Anchor(…) / testTag, else the resource entry nametestIDnot in 1.0.0

The closest marked ancestor wins (.mask inside an .unmask subtree masks again). Inputs stay masked inside unmasked subtrees. The project can also unmask or mask by tap target name for all apps (unmaskAnchors, maskAnchors in the recording settings), without a new app release.

Excluded screens. The project's recording exclusions apply to screen paths (*login*, *account*, *checkout*, *payment*, *password* … by default): the SDK does not record them, and the server refuses them again.

SwiftUI. SwiftUI draws text into internal layers that X3 never reads. In wireframes SwiftUI text is always a placeholder shape, also with .x3Unmask(); .x3Unmask() makes it visible only in screenshot mode. .x3Mask() wins over .x3Unmask(). UIKit, React Native and Android Views are read directly, so .unmask shows their text in wireframes.

Jetpack Compose. sdk-replay reads Compose through the semantics tree (texts, editable and password fields, test tags). Without Compose on the classpath a Compose host is one box.

Flutter. Flutter draws its own pixels; the native recorder of 1.0.0 records a Flutter screen as one masked area with taps and screen changes, not its widgets (Flutter).

Screenshot mode

Wireframes are the default for every screen and need no setup.

For screens the view tree cannot describe (custom drawing, canvas, charts, maps you want to see), list them in screenshotScreens and allow them for the app (Einstellungen › Apps › Nordlicht › Aufnahmen, switch Screenshot-Modus erlauben, Allow screenshot mode). Both are needed.

recording: X3RecordingOptions(mode: .wireframe, screenshotScreens: ["Chart", "/plans/compare"])

On those screens the SDK captures the window at most once per second and only after changes, blacks out every masked region, input and (with masking on) every text and image before encoding, scales to at most 360 pt wide and stores a JPEG of at most 60 KB. Each image is sent once and stored as a replay asset; the player shows a Screenshot chip for such recordings. Use it sparingly: it costs more CPU, memory and data than wireframes, and custom drawings can contain personal data the masking cannot know about.

Recording indicator

Apple's App Review Guideline 2.5.14 requires apps that record or log user activity to get explicit consent and to show a clear visual indication while they record. The SDK therefore shows an indicator while recording, top trailing inside the safe area, not captured in the recording, labelled for VoiceOver and TalkBack:

indicatorLook
pill (default)a small pill with a red dot and the text Aufnahme / Recording
dotonly the red dot
hiddennothing

Turning the indicator off is your decision and your risk in App Review and towards your users. Combine it with consent: in mode opt_in the recorder never runs before the user granted Sitzungen aufzeichnen (Session recordings).

Performance

BudgetValueWhen exceeded
main thread per capturep95 below 4 ms, at most 2 captures per secondthrottle to 1 per second, then pause until the next screen
CPU on averagebelow 2 percentage points extraLow Power Mode, battery saver or thermal state serious: recording pauses
memorybuffer at most 8 MBoldest packets dropped (the recording shows a gap)
networkbelow 50 KB per minute compressedupload every 10 seconds and when the app goes to the background; optional Wi-Fi only
binary sizeiOS X3KitReplay ≤ 300 KB, Android sdk-replay ≤ 200 KBseparate module: 0 bytes without recordings

Per session at most 20 MB and 400 packets. Captures happen only after the view tree changed, so a static screen costs nothing.

Watching, retention and deletion

App recordings appear in Sitzungen next to web recordings, with the same playlists, filters, summaries and sharing rules; the visit shows the App card. Retention, keep decisions and deletion follow the project's recording settings (what is kept, deleting). When a user revokes Sitzungen aufzeichnen, the SDK stops at once and X3 deletes the recordings of the current session.