@siteoshq/pulse
v0.9.0
Published
Errors, server Logs, tracing and consented session replay for SiteOS Pulse
Readme
SiteOS Pulse
Capture errors, server Logs, optional browser request traces and masked session replay in SiteOS Pulse. Keep diagnostics connected to your Project environment and deployed release.
Install
pnpm add @siteoshq/pulseEnable Pulse → Errors → Setup & settings for the selected Project environment. Save the exact website origins and obtain the environment-specific collection keys. Browser and server Errors use separate keys; keep server credentials in server environment variables.
| Support | Requirement |
| ---------------------- | ------------------------------------------------------------ |
| Browser | ES2022, Fetch, Web Crypto, AbortController |
| Server | Node.js 22.12+ |
| Next.js request errors | onRequestError instrumentation hook |
| React error boundaries | Manual captureException |
| Recorder dependency | @rrweb/record 2.1.7 (MIT), loaded only for selected replay |
| Errors entry budget | 5 KiB minified + gzip, checked on every build |
| Tracing entry budget | 25 KiB minified + gzip, including dependencies |
| Replay entry budget | 100 KiB minified + gzip, including recorder |
The root entry and /browser provide the same browser-safe API. Node process hooks are isolated in /node; /nextjs contains only
the request-error adapter. Importing the package does not attach handlers or send requests.
SiteOS Cloud: five credentials
For Errors, server Logs/Traces, browser Traces and private source maps in Next.js, configure these five environment variables. Enable only the channels selected for your site; an Errors-only installation does not need Telemetry or browser Traces keys. Use separate values per environment.
| Variable | Scope |
| ------------------------------------- | ----------------------------------------- |
| NEXT_PUBLIC_SITEOS_PULSE_ERRORS_KEY | Public, browser Errors collection only |
| SITEOS_PULSE_ERRORS_KEY | Secret, server Errors collection only |
| NEXT_PUBLIC_SITEOS_PULSE_TRACES_KEY | Public, browser Traces collection only |
| SITEOS_PULSE_TELEMETRY_KEY | Secret, server Logs and Traces collection |
| SITEOS_PULSE_SOURCEMAPS_KEY | Secret, build-only source map upload |
Pass each collection key to its SDK entry; the SDK does not read runtime environment variables
or infer consent. The Next.js build adapter reads the source map key and derives the release
from SITEOS_RELEASE, VERCEL_GIT_COMMIT_SHA, or Git. Use direct
process.env.NEXT_PUBLIC_… accesses in client code so Next.js can inline public values at build time.
No separate endpoint or enablement environment variables are required for SiteOS Cloud.
Collection still requires explicit SDK initialization and enabled settings in the exact environment.
endpoint is optional starting with SDK 0.8.0. SDK 0.7.x and earlier require explicit collection endpoints.
Check the installed SDK before removing existing endpoints. The defaults are:
| Channel | Default endpoint |
| --------------------- | ------------------------------------------------------- |
| Browser/server Errors | https://errors.siteos.sh/errors/v1/collect |
| Browser Traces | https://telemetry.siteos.sh/browser/v1/traces |
| Server Logs / Traces | https://telemetry.siteos.sh/v1/logs / /v1/traces |
| Source maps | https://app.siteos.sh/api/pulse/errors/v1/source-maps |
For self-hosting or a custom collector, pass the exact endpoint returned by that installation's
settings. Explicit endpoints take priority; an empty or invalid endpoint never falls back to Cloud.
Errors and server convenience clients disable invalid configuration. Browser tracing and raw
exporter constructors reject it synchronously; keep initialization inside the application’s
existing fail-open guard. Source map upload configuration errors fail the build. Keys remain scoped to their channel and Project environment. A Cloud default does
not select Production, merge keys, enable a feature, or relax consent/GPC/DNT policy.
Keep enabled: false as an optional application-controlled switch for server Logs/Traces.
Browser errors
Initialize once in a client module. Use a release ID matching the deployed build, such as a Git SHA.
import { init } from "@siteoshq/pulse";
export const errors = init({
key: "YOUR_PUBLIC_COLLECTION_KEY",
release: "YOUR_BUILD_ID",
collection: "after-consent",
applicationOrigins: ["https://cdn.example.com"], // optional application bundle origins
});
// Connect these calls to your consent manager's current state and changes.
errors.setConsent(true);
// errors.setConsent(false);Browser capture waits for consent by default. No reports or breadcrumbs are buffered while consent
is absent. Withdrawal clears breadcrumbs and aborts pending requests; already delivered reports
cannot be recalled. A site owner may explicitly select collection: "essential" according to the
site's policy. The SDK does not determine a legal basis for collection.
Unhandled exceptions and Promise rejections are captured automatically. Report handled errors,
including errors received by React's componentDidCatch or a Next.js error boundary, explicitly:
errors.captureException(error, { handled: true, route: "/contact" });
errors.addBreadcrumb({ category: "action", name: "contact.submit" });Pass static action names and route templates. The SDK does not record DOM text, form values,
cookies, request bodies, headers or visitor IDs. Query strings and common secret patterns are
removed before transmission. Application messages can still contain sensitive content; use
beforeSend to remove unsuitable reports:
init({
key,
release,
beforeSend: (event) => (event.route?.startsWith("/private") ? null : event),
ignoreErrors: ["Known optional widget failure"],
sampleRate: 1,
});Initialize a single client per page. Call await errors.close() before replacing it during hot
reload or teardown. Equivalent errors are reported once per client/page, with at most 20 reports
per minute and four requests in flight. Extension errors, known browser noise and known bots are
filtered by default. filterNoise: false and ignoreBots: false are available for controlled tests.
Third-party scripts remain visible; applicationOrigins identifies your own CDN bundles.
Server
import { createClient } from "@siteoshq/pulse/node";
const errors = createClient({ key: process.env.SITEOS_PULSE_ERRORS_KEY!, release });
try {
await performOperation();
} catch (error) {
errors.captureException(error, { route: "/api/contact" });
await errors.flush();
throw error;
}The return value of captureException is a local report ID, not a delivery receipt. Verify accepted reports in Pulse.
Await flush() before a serverless request ends. Transport failures are swallowed and each request
has a three-second deadline. Server deduplication resets each minute. The optional Node init()
also observes fatal exceptions without changing the process crash policy; delivery during process
termination is best effort.
For Next.js, export the adapter from instrumentation.ts:
import { createRequestErrorHandler } from "@siteoshq/pulse/nextjs";
export const onRequestError = createRequestErrorHandler({
key: process.env.SITEOS_PULSE_ERRORS_KEY!,
release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
});The handler awaits bounded delivery and reads only the route template from Next.js context. Use
runtime: "edge" when configuring an Edge-specific handler. Keep the secret key out of browser
modules and public environment variables.
Automatic source maps
Set this up once with SDK 0.2+ and CLI 2.48+. Select the intended SiteOS Project and environment, then issue a build-only upload key:
siteos pulse errors upload-key issue --environment production \
--name "Website build" --output /private/path/pulse-maps.jsonSave the returned key in your build environment as SITEOS_PULSE_SOURCEMAPS_KEY. It is secret,
bound to one environment's Pulse resource and can only upload maps. It cannot read errors or change
settings. Keep it out of Git, browser variables and runtime configuration. Keys expire after a year;
use upload-key list and upload-key revoke --credential <id> to inspect or replace them.
For Next.js 16.3+ (16.x), wrap the final configuration. SDK 0.2.1+ supports both
next.config.ts and ESM configuration files:
// next.config.ts
import { withPulseErrors } from "@siteoshq/pulse/nextjs/config";
export default withPulseErrors(nextConfig);
// With another plugin: withPulseErrors(withMDX(nextConfig))Use process.env.NEXT_PUBLIC_SITEOS_RELEASE for both browser and server reporting. The wrapper sets
it from SITEOS_RELEASE, VERCEL_GIT_COMMIT_SHA, or the checked-out Git revision, in that order.
Source archives without Git must provide SITEOS_RELEASE. It must identify the exact build.
The normal next build generates browser/server maps, uploads them after compilation and removes
them from the build output before packaging. Existing compile hooks are preserved. Upload failures
stop the build; rebuild after fixing the key, quota or connection. Identical uploads are safe to
repeat; different artifacts cannot overwrite an existing release/file pair. Without an upload key,
local builds remain offline and the wrapper does not enable source map generation.
Other build tools can call the same Node-only uploader from their post-build hook:
import { uploadSourceMaps } from "@siteoshq/pulse/build";
await uploadSourceMaps({
directories: [{ directory: "dist/assets", prefix: "/assets" }],
release: process.env.APP_RELEASE,
key: process.env.SITEOS_PULSE_SOURCEMAPS_KEY,
});Enable map generation in that build tool. prefix must match the generated stack URL path. The
uploader strips embedded source text, uploads JavaScript maps and removes JS/CSS maps by default,
including after upload failure. removeAfterUpload: false is only for artifacts kept outside public
output. Do not deploy a failed or partial build. The build entries are excluded from browser bundles
and add no runtime dependencies. A self-hosted installation can pass the endpoint returned by CLI.
Source-map validation is shared with the server. The build accepts at most 25 MB per input map, 8 MB per upload and 6 MB per normalized map; embedded sources are discarded before transfer. A map may contain up to 1,000,000 segments and 5,000,000 mapping characters, 200,000 lines, 4,000 sources and 50,000 names. Indexed sections share these budgets (2,000 nodes, five nested levels). Long generated names are shortened to 120 characters while preserving mapping indices. Validation failures identify the exceeded limit or invalid map; split oversized generated bundles into smaller chunks and rebuild. Upload failures continue to stop the build.
Manual repairs remain available through siteos pulse errors upload-map --release <id>
--file <generated-path> --input <map-file> --environment <slug> using CLI authorization. SiteOS
stores source locations and mappings, never fetches remote maps and discards embedded source text.
Missing maps preserve generated locations; uploads improve diagnostics without changing grouping.
Reports expire after 30 days; inactive issue summaries and source maps expire after 90 days. Counts represent received reports, not all failures or affected users: sampling, deduplication and admission limits intentionally reduce traffic. No reports does not mean the application is healthy.
Correlate server errors with traces
When your server already uses OpenTelemetry, pass its active span identifiers to Errors:
import { context, trace } from "@opentelemetry/api";
import { createClient } from "@siteoshq/pulse/node";
const errors = createClient({
key,
release,
getTraceContext: () => trace.getSpan(context.active())?.spanContext(),
});You can also pass traceId and spanId in captureException(error, context). Pulse links retained
server traces to the error occurrence. Correlation does not change grouping or issue status.
Server Logs can use the optional @siteoshq/pulse/otel adapter below. Server tracing can use
your existing OpenTelemetry provider, or the Next.js server adapter below.
Next.js server Traces (0.7.0+)
The @siteoshq/pulse/nextjs/server entry is Node-only. Install only Pulse; its OpenTelemetry
dependencies install with it. Import it inside the Node branch of instrumentation.register():
export async function register() {
if (process.env.NEXT_RUNTIME === "nodejs") {
const { registerServerTracing } = await import("@siteoshq/pulse/nextjs/server");
registerServerTracing({
key: process.env.SITEOS_PULSE_TELEMETRY_KEY!, // private server key
service: "next-app",
release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
enabled: process.env.SITEOS_PULSE_TELEMETRY_KEY !== undefined,
});
}
}Keep the returned client in a server module and call client.flush() inside Next.js after()
for observed routes, or await it in onRequestError. Timers alone do not guarantee serverless
delivery. flush() and close() resolve to a delivery outcome without throwing collector errors;
onDiagnostic receives bounded codes, never collector response text or credentials.
Registration uses one provider, W3C trace context without baggage and 10% parent-based root
sampling (sampleRate overrides the root rate). Existing sampled/unsampled parents are respected.
It uses Next.js instrumentation without adding another fetch patch, resource auto-detection,
console interception, browser collection or replay. Batches contain at most 32 spans, the queue
holds 256 and flush waits at most three seconds. Configure once per process; repeated registration
returns the same client. Changing settings or restarting a closed client requires a new process.
enabled: false or OTEL_SDK_DISABLED=true installs nothing.
Raw span names, URLs, headers, payloads, arbitrary attributes, status messages, events, links and
tracestate are removed before export. Defaults retain timing, context IDs, method/status and a
generic operation name. Optional describeSpan(span) returns only a reviewed { name, route }
projection, with static names and route templates, or null to drop a span. Application-specific
route/operation labels stay in your application. A throwing policy drops that span. Do not return
user values or concrete dynamic paths. getServerTraceContext supplies the current IDs to Errors
or an existing logger without another direct OpenTelemetry import.
If a provider already exists, registration returns state: "existing_provider" and does not
change it, its sampling or context. Instead, add createServerTraceProcessor(options) to that
provider's spanProcessors when constructing it, and retain its existing sampler and lifecycle.
The processor uses the same redaction and bounded JSON export; it never registers a provider.
Do not call both setup paths. The adapter does not enable SiteOS collection or publish settings.
Manual server stages (0.9.0+)
SDK 0.9.0 adds createServerTracingClient from @siteoshq/pulse/nextjs/server
(or @siteoshq/pulse/node for other Node hosts). SDK 0.8.x and earlier do not expose it.
Use this API for explicit application stages without direct OpenTelemetry imports. Existing
registerServerTracing, createServerTraceProcessor and standard OTLP integrations remain supported.
The manual client owns a private provider and async context; it never replaces an application
provider, reads request headers, patches fetch/console or captures Errors.
import { createServerTracingClient } from "@siteoshq/pulse/nextjs/server";
import { after } from "next/server";
const tracing = createServerTracingClient({
key: process.env.SITEOS_PULSE_TELEMETRY_KEY ?? "",
service: "website-forms",
release: process.env.NEXT_PUBLIC_SITEOS_RELEASE ?? "",
enabled: process.env.VERCEL_ENV === "production",
routes: ["/api/forms/submit"],
steps: ["payload.validate", "wordpress.submit"],
onDiagnostic: ({ code }) => console.warn("Pulse trace delivery:", code),
});
// Inside the route handler. Trust/origin policy is application-owned; pass a
// traceparent only after that policy accepts it. Never forward baggage/tracestate.
return tracing.withRequest(
{
requestId: request.headers.get("X-Request-ID"),
route: "/api/forms/submit",
method: "POST",
parentTraceparent: trustedParentTraceparent,
after,
isError: (response: Response) => response.status >= 500,
statusCode: (response: Response) => response.status,
},
async () => {
await tracing.step("payload.validate", validate);
const result = await tracing.step("wordpress.submit", submit, {
kind: "client",
isError: (value) => !value.ok,
});
const context = tracing.getContext()!;
const response = makeResponse(result);
response.headers.set("X-Request-ID", context.requestId);
if (context.sampled)
response.headers.set("X-Pulse-Traceparent", `00-${context.traceId}-${context.spanId}-01`);
return response;
},
);withRequest accepts or generates a UUID v4 attempt ID even when disabled, invalidly configured,
closed or unsampled. getContext() returns a fresh plain object with requestId and sampled;
traceId, spanId and traceFlags: 1 exist only for a sampled active request. Outside a request it
returns undefined. IDs follow await, nested steps and parallel work. They are diagnostics,
never visitor identity, authorization or idempotency. Put request.id in reviewed Logs yourself;
pass a sampled context to Logs/Errors via getTraceContext. Do not invent a retained-trace link
when sampled is false. Browser Errors consent remains required; no server Errors are enabled.
Root sampling defaults to 10%; strict nonzero W3C version 00 parent IDs with flags 00/01 override
the root decision. sampleRate only controls new roots. OTEL_SDK_DISABLED=true disables tracing.
Routes and steps must come from static reviewed catalogs (at most 128 each). Unknown routes or
steps still run the operation, without recording that span. The SDK cannot determine whether a
syntactically valid catalog label contains personal data: never construct catalogs from requests.
Operations execute once and preserve their exact result or exception. An isError predicate can
mark an unsuccessful result without throwing. Throwing metadata/diagnostic callbacks do not alter
the result. No arguments, results, exception text, arbitrary attributes or raw request data are
serialized. Only approved labels/routes, timing, IDs, span kind, error status and optional validated
HTTP method/status reach the existing sanitized OTLP exporter.
Each client holds at most 128 active/pending request buffers, 32 spans per request including its
root, and four concurrent exports. Overflow drops diagnostics, never business work. Detached steps
that finish after the root are discarded. Buffers expire 60 seconds after request entry; the expiry
timer only discards, never exports. Completed spans stay outside all automatic queues until the
host invokes after. Each callback awaits its own batch, with a two-second exporter timeout and
three-second outer deadline. Repeated callbacks reuse the same delivery promise. Delivery uses
bounded OTLP retry behavior and cannot retry the business operation. Collector/scheduler failures
produce safe diagnostic codes, never keys, collector responses or exception text.
Call close() only at process shutdown: it drops callbacks that have not started and bounds waiting
for active exports. It never sends a pending request early. Local tests do not prove a serverless
host keeps after() alive: verify actual Preview delivery separately before publication acceptance.
Server Logs (0.5.0+)
Install @siteoshq/pulse; its official OpenTelemetry dependencies are installed transitively.
Only the explicit server entry loads them. Browser Errors, tracing and replay bundles do not
include this adapter. Create one client per process in server instrumentation and reuse it:
import { createLogsClient } from "@siteoshq/pulse/otel";
export const logs = createLogsClient({
key: process.env.SITEOS_PULSE_TELEMETRY_KEY!, // private server Telemetry key
service: "website-forms",
release: process.env.NEXT_PUBLIC_SITEOS_RELEASE!,
enabled: process.env.SITEOS_PULSE_TELEMETRY_KEY !== undefined,
});For Next.js, bind the client to the request's supported background lifecycle:
import { after } from "next/server";
import { logs } from "./server-logs";
export async function POST() {
const requestLogs = logs.forRequest(after);
requestLogs.emit({
severity: "warn",
body: "forms.wp_timeout", // fixed event text; never form contents or arbitrary exceptions
attributes: { "event.name": "forms.wp_timeout", "http.route": "/api/contact" },
});
return new Response(null, { status: 503 });
}forRequest buffers at most 32 records, schedules one callback, and emits/flushes only in that
callback. Adapt another platform's waitUntil explicitly; a detached timer does not keep a
serverless invocation alive. emit outside a request uses a 256-record bounded process queue;
call await logs.flush() before a short-lived process exits and await logs.close() when retiring
it. Flush defaults to a three-second deadline and collector requests to two seconds. Concurrent
flushes include records emitted while an earlier flush was pending. Do not await these operations
in a customer's primary request path. A false return or safe onDiagnostic({ code, count?, reason? })
indicates disabled/rejected/bounded work; diagnostic callbacks and export failures never throw
through the convenience client's request logger. No endpoint, key or collector error text is
included in diagnostics. The standalone exporter validates configuration when constructed.
Warn/Error/Fatal are retained by default; set minimumSeverity explicitly to opt into lower levels.
Choose fixed, non-personal event text. SDK 0.6.0+ supports custom server Log attributes:
strings, finite numbers and booleans, including form.name, page.route, feature.variant,
attempt: 0 and retryable: false. Arrays, objects and null are unsupported. Attribute names are
1–80 ASCII characters, starting with a letter, then letters, digits, dots, dashes or underscores.
Each record retains at most 32 attributes, each text value at most 240 UTF-8 bytes, and at most
4 KiB of encoded attribute JSON. At most 128 input keys are inspected; standard correlation keys
are prioritized within that window, followed by lexical key order. Oversized values are dropped.
Credential, body/header, SQL statement and common personal-data keys are rejected. Text scrubbing
is defense in depth, not a guarantee: choose safe context explicitly. Use route templates from a
reviewed catalog for page.route/http.route; query strings and fragments are removed.
onDiagnostic reports attributes_dropped with a count and reason (invalid_key, sensitive_key,
unsupported_value, value_too_large, attribute_limit, byte_limit, scan_limit, or
upstream_limit when an existing OTel provider already removed fields). attributes_sanitized
reports a count when retained values changed during scrubbing. These diagnostics contain no
attribute keys or values and do not indicate a failed export. Keep their handler separate from
export_failed/dropped_records, and avoid logging diagnostics back through the same client.
The exporter opts into the extended server contract with X-SiteOS-Logs-Attributes: custom-v1.
Standard OTLP exporters can send that header explicitly after reviewing their emitted attributes.
Without it, the collector keeps the legacy 12-field technical allowlist. Browser and server traces
keep their existing attribute rules. Deploy the compatible SiteOS origin and Telemetry Edge before
upgrading the SDK. Messages remain capped at 2 KiB UTF-8; actual serialized OTLP batches remain
under 96 KiB and at most 100 records. The service remains lossy, with capacity limits and seven-day
retention. Flush success means exporter completion, not verified SQL retention; read back a
synthetic event for acceptance.
Numeric attributes accept finite numbers; integral values must be within JavaScript’s safe integer range (−9,007,199,254,740,991 through 9,007,199,254,740,991) to survive OTLP int64 serialization without rounding. Unsupported numbers are dropped with diagnostics, and filters reject them.
Logs filters use exact typed values (up to three AND matches). For example, with CLI 3.4.0+:
siteos pulse telemetry logs --attributes '{"form.name":"Book a demo","retryable":false}' --jsonThe Logs screen offers the same key, type and value controls. MCP siteos_pulse_list_logs accepts
an attributes object. Keep filters unchanged when following nextCursor. Text matching is case
sensitive; 0, "0", false and a missing attribute are distinct.
The client captures active trace/span IDs at emit time without registering a global provider.
Use getTraceContext: () => undefined for intentionally uncorrelated Logs, or supply explicit
verified IDs. It never captures baggage, request bodies, headers or console output.
For an existing OpenTelemetry Logs provider, add createLogsExporter({ endpoint, key }) to that
provider's batch processor instead of creating another client/provider. The adapter uses the
official OTLP/HTTP JSON exporter, sanitizes records and splits by actual encoded bytes; an export
call is bounded to 256 records and four concurrent export operations. Existing sampling policy
and other destinations remain application-owned. The supported integration is tested with OTel
Logs 0.223.x and Resources 2.12.x. No server spans are installed automatically.
Browser tracing
Use the optional @siteoshq/pulse/tracing entry. It adds no OpenTelemetry or replay dependency to
an Errors-only installation. In Pulse → Traces → Collection settings → Browser, configure exact
website origins and approved route templates, then create a public browser tracing key.
import { initTracing } from "@siteoshq/pulse/tracing";
export const tracing = initTracing({
key: "YOUR_PUBLIC_BROWSER_TRACING_KEY",
release: "YOUR_BUILD_ID",
routes: [{ origin: "https://example.com", path: "/api/orders/:id" }],
sampleRate: 0.1,
});
// Connect to the same consent manager as Errors.
tracing.setConsent(true);
// tracing.setConsent(false);Only matching fetch/XHR requests receive a W3C traceparent. The instrumented server must accept
that header; cross-origin APIs must allow it through CORS. Existing trace headers are preserved.
Pulse sends route templates, method, duration and status; it does not read request/response bodies,
export headers or retain concrete paths. Server spans with matching IDs appear in the same trace.
Initial navigation timing is available for approved page routes when navigation finishes after
consent. Earlier buffered navigation entries are not read. These load timings are independent
roots, not an inferred causal parent for later requests. SPA navigation does not invent a new
page-load span. Set navigation: false to disable these timings.
Correlate only an error from the actual traced operation:
try {
await fetch("/api/orders/123");
} catch (error) {
errors.captureException(error, { ...tracing.contextFor(error), route: "/orders/:id" });
}contextFor recognizes the same rejected request error, not arbitrary later exceptions. An HTTP
error response does not reject fetch. Server error/log correlation uses the server's active span.
Call await tracing.close() at teardown. Revocation removes hooks, clears pending records and
aborts collection requests. DNT and Global Privacy Control suppress collection. Export is bounded
and best effort; a failed collector never fails the application request.
For existing browser OpenTelemetry, add createTraceExporter(options) to the existing provider's
span processor instead of initTracing. The exporter accepts client request spans with standard
url.full/http.url, method and status attributes and projects only approved routes. It installs
no global provider or fetch/XHR patch. Connect its setConsent method to the CMP and retain the
provider's sampling policy. Unsupported spans are omitted.
| Browser entry | Complete consumer budget (minified + gzip) |
| ------------------------- | ------------------------------------------ |
| Root / browser (Errors) | 5 KiB |
| tracing | 25 KiB |
The build verifies these budgets and dependency isolation. Browser traces expire after seven days. Sampling and bounded delivery mean a trace can be partial; absent data is not evidence of health.
Optional replay
Enable Replay separately in the same Project environment. Use its own public collection key, returned endpoint and approved page templates. The player is part of SiteOS, not this package.
import { initReplay } from "@siteoshq/pulse/replay";
const replay = initReplay({
endpoint: replayEndpoint,
key: replayKey,
release,
routes: ["/", "/products/:id"],
// sampleRate: 0.01 is the default
});
// Wire the current permission and subsequent changes from your consent manager.
replay.setConsent(hasConsent);
// On teardown or withdrawal: replay.close() / replay.setConsent(false).
// Optional: add this callback to your existing Errors and tracing initializers.
const getReplayId = () => replay.getRecordingId();Replay loads its recorder only after consent and sampling and honors DNT/GPC. The identifier exists only in memory for one page lifetime. Withdrawal clears pending data, aborts export and ends this recording; accepted data remains subject to retention. Unsampled errors have no recording.
Text and input values are always masked. Mark private subtrees with data-pulse-private or
.pulse-private. Authentication/account/payment routes, embedded pages, media and canvas are
excluded. The privacy projection removes arbitrary attributes, URLs and CSS. Recordings preserve
limited layout and interactions; fonts and external assets may appear as placeholders. Masking is
not a guarantee of anonymity.
Recordings stop after 5 minutes idle or 15 minutes total and expire after 7 days. Transport is best effort with 96 KiB chunks and a 5 MiB recording limit; incomplete data is shown as gaps. A collector failure never blocks the page. Importing only Errors or tracing does not include the recorder.
