Custom events & input
The built-in channels cover pointer, camera, mesh, and performance. These APIs let your app contribute
the rest of the picture — domain events, non-pointer input, capability fallbacks, and scene changes.
All of them are methods on the client returned by trackScene(...).
Custom events
Section titled “Custom events”Record your own discrete domain events. They’re always captured at 100% — never rate-limited.
const client = trackScene(scene, { projectId, endpoint });
client.track("add_to_cart", { sku: "ABC-123", price: 49 });Keep the property map free of PII. Read per-type counts from
GET /api/v1/event-counts.
Input actions (keyboard, gamepad, XR)
Section titled “Input actions (keyboard, gamepad, XR)”Mouse and touch are captured automatically as pointer events. Discrete input actions from other
devices — keyboard shortcuts, gamepad buttons, XR controller buttons — are recorded as input_action
events. Each carries a semantic action label (what the input did) plus the originating
source and the raw code/button token.
Emit one explicitly whenever you handle a binding:
// In your own keydown / gamepad handler:client.trackInput("next-camera", { source: "keyboard", code: "KeyN", pressed: true });client.trackInput("jump", { source: "gamepad", button: 0 });source defaults to "keyboard". These events are discrete and always captured at 100%.
keyBindings allowlist
Section titled “keyBindings allowlist”The Babylon, three.js, and PlayCanvas connectors can capture bound keys for you. Pass
keyBindings mapping a physical KeyboardEvent.code to an action label — only the
listed keys are recorded (privacy-first), arbitrary typing is never captured, and
auto-repeat is suppressed:
trackScene(scene, { projectId, endpoint, keyBindings: { KeyW: "move-forward", KeyS: "move-back", Space: "jump" },});three.js / PlayCanvas (and react-three-fiber via @uptimizr/r3f) accept the same
option. They have no keyboard observable, so they listen on window — handy for
pointer-lock / FPS scenes where the canvas rarely holds focus.
Capability changes (fallbacks & recovery)
Section titled “Capability changes (fallbacks & recovery)”Rendering capability isn’t constant: some visitors run WebGPU, others fall back to WebGL2; weaker devices auto-downgrade quality/LOD; a lost GPU device may re-initialise at a different capability. These transitions otherwise look like unexplained noise in aggregate metrics.
Engines decide their backend at init and expose no reliable runtime hook, so connectors do not auto-capture this — report it from your app whenever you perform a fallback or recovery:
// after a WebGPU init fails and you fall back:client.reportCapabilityChange({ kind: "graphics-backend", from: "webgpu", to: "webgl2" });// or a runtime quality/LOD auto-downgrade:client.reportCapabilityChange({ kind: "quality", from: "high", to: "low", reason: "low-fps" });kind is one of graphics-backend / quality / device-recovery / feature / other; from /
to / reason are optional, low-cardinality, app-defined tokens (never raw device strings or PII).
This pairs with the raw context_lost / context_restored
events — it’s the higher-level “what we ran as” signal. Read the rollup from GET /api/v1/capabilities.
Changing scenes / levels (setScene)
Section titled “Changing scenes / levels (setScene)”A single session can span multiple scenes, areas, or levels — game levels, a viewer swapping models, or a multi-room walkthrough. You do not stop and restart tracking when the visitor moves between them; you keep one client alive and mark the transition:
const client = trackScene(scene, { projectId, endpoint, meta: { sceneId: "level-1" }, // initial scene/area});
client.setScene("level-2"); // when the next scene loadsSee the multi-scene experiences guide for the full patterns (levels, viewers, rooms), per-scene querying, and how replay crosses scene changes.