End-to-end observability: follow one user interaction from browser to backend
What is end-to-end observability?
End-to-end observability is the practice of collecting and correlating telemetry from every layer a user request touches: the browser, the CDN, API gateways, backend services, and databases. The defining capability is that one user interaction can be followed as a single trace across all of those layers, because each layer attaches its telemetry to the same trace identifier.
This distinguishes it from siloed monitoring, where the frontend team looks at Real User Monitoring data, the backend team looks at APM dashboards, and nobody can connect a slow checkout click in the browser to the database query that caused it. With end-to-end observability the connection is explicit: the browser starts a trace, passes its identifier to the backend in an HTTP header, and every service adds its own spans to the same trace.
End-to-end observability includes frontend observability as its browser-side component. If you are new to the topic, that guide covers the client side in detail.
Why the browser is the starting point
A user interaction begins in the browser, so that is where the trace has to begin. Starting traces at the API gateway leaves the client side invisible: DNS lookup, connection setup, JavaScript execution, rendering, and third-party scripts all happen before or after the backend is involved.
The gap this creates shows up in a specific, common situation: backend dashboards report healthy response times while users experience a slow site. The difference lives in the parts the backend never sees. In our client work the client side regularly accounts for the majority of total user-perceived latency, which is consistent with the fact that Core Web Vitals are defined and measured in the browser, not on the server.
Measured from the browser, a single page load decomposes into segments that backend monitoring cannot attribute:
- Network setup: DNS resolution, TCP and TLS handshakes, measurable via the Navigation Timing API
- Server response: Time to First Byte, the only segment backend monitoring also sees
- Resource loading: images, scripts, stylesheets, fonts, and third-party resources
- Execution and rendering: JavaScript parse and execute time, layout, and paint
- Interactions after load: measured by INP, attributable per script since the Long Animation Frames API shipped in Chrome 123
The three telemetry signals
OpenTelemetry, the CNCF project that standardises observability instrumentation, defines three core signals. End-to-end observability uses all three on both sides of the network boundary:
- Traces: the tree of timed operations (spans) that one request produces across services. The browser contributes spans for the page load, fetch calls, and user interactions; each backend service contributes spans for its own work.
- Metrics: aggregated numeric measurements over time. In the browser these include Core Web Vitals (LCP, INP, CLS) and custom timings; on the backend, request rates, error rates, and latency percentiles.
- Logs: timestamped event records. Client-side errors and backend log lines become correlatable when both carry the trace identifier.
The signals are useful individually, but the correlation is what answers debugging questions: a metric tells you INP degraded, the traces for slow interactions show which fetch call blocked the response, and the backend spans and logs inside that trace show which service and which query were responsible.
How trace context propagation works
The mechanism that links browser and backend telemetry is standardised in the W3C Trace Context specification, a W3C Recommendation since February 2020. It defines the traceparent HTTP header, which carries a trace ID and the ID of the calling span:
A traceparent header
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01The four dash-separated fields, in order:
00: the specification version.4bf92f3577b34da6a3ce929d0e0e4736: the trace ID, identical for every span in this user's journey across every service.00f067aa0ba902b7: the parent ID, identifying the calling span, which makes the receiving service a child of it.01: the trace flags. Only one bit is defined so far, the sampled flag, which records whether this trace is kept.
The flow for a single user action:
- The user clicks. The browser SDK starts a span and records the interaction.
- The application calls the backend. The SDK's fetch instrumentation adds the
traceparentheader to the request, carrying the trace ID. - The backend service reads the header, continues the same trace, and creates child spans for its own work. Any service it calls receives the header in turn.
- All spans arrive at the observability backend, which reassembles them by trace ID into one waterfall: click, network, gateway, services, database, response, render.
Two implementation details matter in the browser specifically. Cross-origin API calls only carry the header if the API's CORS configuration lists traceparent in Access-Control-Allow-Headers; otherwise the browser strips it and traces break at the origin boundary. And the sampling decision recorded in the header's final flag should be made in the browser (head-based sampling), so frontend and backend keep the same subset of traces.
Questions end-to-end observability answers
These are questions that neither frontend-only nor backend-only monitoring can answer, because each requires connecting data from both sides:
- Is this slow checkout caused by the API, the network, or client-side rendering? The trace waterfall shows which segment holds the time.
- Which backend deployment caused the INP regression that started on Tuesday? Traces carry release versions from both frontend and backend.
- Do the users reporting errors share a backend path? Client-side errors carry trace IDs that lead to the failing service.
- What did this specific user's failed session look like across the stack? One trace ID retrieves browser spans, service spans, and logs together.
- Which services are actually on the critical path of the pages users see first? Traces started at page load reveal the real dependency chain.
Practical limits and challenges
Reported factually, these are the constraints teams run into when implementing end-to-end tracing from the browser:
- Third-party boundaries: trace context only propagates into systems you control. Calls to external APIs, payment providers, and third-party scripts appear as opaque client-side spans.
- CORS configuration: every cross-origin API you own must allow the
traceparentheader, or traces silently break at that boundary. - Content blockers: telemetry sent to known collector domains is blocked for users running ad blockers. First-party collection endpoints reduce this data loss.
- Sampling consistency: independent sampling decisions on frontend and backend produce incomplete traces. The sampling decision must be made once, at the trace root in the browser.
- Data volume and cost: browser telemetry scales with traffic, not with server count. High-traffic sites need a sampling strategy before full rollout, not after the first invoice.
- Privacy: browser telemetry is personal data under the GDPR when it can identify users. Consent management, PII masking, and EU data residency need to be part of the design, not an afterthought.
Implementation steps
The implementation order that keeps each step verifiable:
- Instrument the browser first. Deploy a RUM or OpenTelemetry browser SDK collecting Core Web Vitals, errors, and fetch spans. This alone closes the visibility gap between backend dashboards and user experience.
- Propagate context to your own APIs. Enable fetch/XHR instrumentation with
traceparentpropagation, and add the header toAccess-Control-Allow-Headerson every API origin. Verify with a single request that the trace ID appears on both sides. - Continue traces in the backend. Backend frameworks with OpenTelemetry auto-instrumentation read incoming trace context without custom code. Confirm that browser-initiated traces contain backend spans.
- Unify the destination. Send frontend and backend telemetry to the same platform, or to backends that share trace IDs, so correlation works in one interface.
- Standardise attributes. Tag telemetry on both sides with the same release version, environment, and user or session identifiers. Cross-layer queries depend on consistent naming.
- Set the sampling strategy. Decide sampling at the trace root in the browser. Common practice is to keep all traces containing errors or poor Core web Vitals and sample the remainder.
For platform selection, see our frontend observability tools comparison. We implement end-to-end observability for clients using OpenTelemetry: details on the observability service page, or contact us directly.
Related resources
- Frontend Observability Guide: the browser-side component in detail
- Frontend Observability Tools: comparison of platforms and open-source options
- Observability Service: OpenTelemetry implementation by Iron/Out
- Core Web Vitals Guide: LCP, INP, and CLS explained
Frequently asked questions
What is the difference between end-to-end observability and frontend observability?
Frontend observability covers the browser layer: Core Web Vitals, JavaScript errors, resource timing, and user interactions. End-to-end observability links that browser telemetry to backend telemetry through shared trace context, so a single user interaction can be followed from the click in the browser through your APIs, services, and databases. Frontend observability is a component of end-to-end observability, not a synonym for it.
Do I need OpenTelemetry for end-to-end observability?
No. Commercial platforms such as Datadog, Dynatrace, and Sentry propagate trace context with their own SDKs. OpenTelemetry is the vendor-neutral option: it is a CNCF project that defines a shared standard for traces, metrics, and logs, and it lets you switch backends without re-instrumenting. Which route fits depends on whether vendor independence is a requirement for your organisation.
Can I trace requests into third-party services?
Only into systems you control. Trace context propagation requires the receiving service to read the incoming trace headers and continue the trace. A third-party API, CDN, or script that does not participate appears in your trace as a single client-side span: you see how long the call took from the browser's perspective, but not what happened inside it.
Does browser instrumentation slow down the page?
Instrumentation adds JavaScript that must be downloaded and executed, so the overhead is not zero. It is kept low by loading the SDK after the initial render, sending data asynchronously via the Beacon API or fetch with keepalive, and sampling. The overhead of a specific setup is measurable: compare Core Web Vitals with and without the SDK in an A/B test before rolling out fully.
Why is some browser telemetry missing from my backend data?
Two common causes. First, content blockers: telemetry sent to known third-party collector domains is blocked for users running ad blockers, so a share of sessions never reaches your platform. Routing telemetry through a first-party endpoint reduces this loss. Second, sampling: if the frontend samples at a different rate than the backend, traces arrive incomplete. Head-based sampling decided in the browser keeps front and back consistent.
Need help implementing these optimisations?
We can audit your site and create a custom performance improvement plan.