skip to content

What does the W3C `traceparent` HTTP header carry, field by field, and what should a receiving service do with its sampled flag and with the accompanying `tracestate` header?

level: juniorimportance: must knowfreq 68%

answer

  1. version-traceid-spanid-flags
  2. 32 hex trace, 16 hex span, all-zero invalid
  3. flags bit 0 = sampled, a hint not a command
  4. Third field is the CALLER's span id → becomes parent
  5. tracestate: your entry first, forward the rest untouched

basics

~20 s

traceparent is four hyphen-separated fields: version, 32-hex trace id, 16-hex span id of the caller, and 2-hex flags whose lowest bit means sampled. The receiver continues the same trace id, parents its span on that span id, and forwards tracestate unchanged apart from its own entry.

solid answer

~50 s

The header looks like `00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01`. Field one is the version (`00` for W3C Trace Context Level 1), field two the 16-byte trace id in hex, field three the 8-byte span id of the *caller's* span — the parent for anything the callee creates — and field four the trace flags, where bit 0 set (`01`) means the caller sampled this trace. All-zero trace or span ids are invalid and the header must be ignored. On receipt the SDK extracts a remote span context, so the callee's new spans keep the same trace id and point at the received span id as parent; with the default parent-based sampler the callee honours the sampled bit rather than re-deciding, which is what keeps a trace whole. `tracestate` is an ordered vendor key/value list; you may add or move your own entry to the front, but you must forward the rest untouched, since it carries other systems' correlation state.

code

text · 8 lines
text
service A span  id=00f067aa0ba902b7  trace=4bf92f...4736  sampled
  --> HTTP GET /orders
      traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
      tracestate:  congo=t61rcWkgMzE

service B extracts -> remote parent {trace 4bf92f...4736, span 00f067aa0ba902b7, sampled}
service B server span: trace=4bf92f...4736  parent=00f067aa0ba902b7  id=<new>
service B outbound:   tracestate: mine=abc,congo=t61rcWkgMzE   (own entry moved to front)

go deeper

for a junior

Recite the four fields and their meaning, and say that the receiver continues the same trace id with the received span id as parent.

for a middle

Explain the parent-based sampler honouring the flag, and the forwarding rules for tracestate.

for a senior

Add operational failure modes — gateways stripping headers, per-service samplers fragmenting traces, and whether to trust inbound context at the perimeter.

for a principal

Discuss trust boundaries for inbound context, head versus tail sampling strategy, and interoperability policy when parts of the estate emit a different propagation format.

## The header, field by field ``` traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 | | | | | | | +-- trace-flags (1 byte hex) | | +------------------- parent-id / span-id (8 bytes hex) | +---------------------------------------------------- trace-id (16 bytes hex) +------------------------------------------------------- version (1 byte hex) ``` **version** — `00` is the only defined version of W3C Trace Context Level 1. The specification defines forward compatibility: an implementation seeing a higher version should still attempt to parse the first three fields it understands rather than discard the header, so future extensions do not break existing hops. Version `ff` is invalid. **trace-id** — 16 bytes, 32 lowercase hex characters, globally unique for the whole distributed operation and constant across every hop. All zeroes is invalid, which is why an implementation that has no trace must omit the header rather than send zeros. **parent-id** (also called span-id) — 8 bytes, 16 hex characters: the id of the span *in the sender* that made this call. The receiver uses it as the parent of the spans it creates, which is how the tree is stitched. All zeroes is invalid. **trace-flags** — one byte of flags. Only bit 0 is defined: `01` means the caller recorded/sampled this trace, `00` means it did not. It is explicitly *not* a command and not a guarantee that data was exported; it is a hint about the caller's sampling decision. The header is case-insensitive by HTTP rules and must appear at most once; a duplicated or malformed header is treated as absent, in which case the receiver starts a new trace as a root. ## What the receiver does The SDK's propagator *extracts* a remote span context: `{trace_id, span_id, flags, trace_state, remote=true}`. That context becomes the current context, so when instrumentation starts the server span it inherits the trace id and sets its parent to the received span id. Nothing else about the caller crosses — no span name, no attributes, no timing. Correlation happens in the backend, which assembles a trace by grouping spans that share a trace id and linking by parent id. The sampled bit interacts with sampling. The default OpenTelemetry sampler is parent-based: if a remote parent exists, follow its decision; only when there is no parent does the configured root sampler (for example a ratio sampler) decide. This is what makes head-based sampling produce *whole* traces — the decision is taken once at the edge and every downstream service obeys it. Configuring an independent ratio sampler at every service instead produces partial traces, because each service tosses its own coin. ## `tracestate` `tracestate: vendorA=abc123,vendorB=xyz` is a comma-separated ordered list of up to 32 key/value entries, each key namespaced to a vendor or tenant. It exists because different tracing systems need to carry a little of their own state alongside the standard ids without colliding. Two rules matter operationally: your system may add or update *its own* key and should move it to the leftmost position (most recently mutated first), and you must forward all other entries unchanged. Dropping unknown entries silently breaks correlation for whichever system owns them — a common bug in hand-written gateway code that rebuilds headers from an allowlist. Entries have size limits (the list should be kept under 512 bytes; implementations may drop the rightmost, least recently used entries when trimming), so `tracestate` is not a general-purpose data channel. Application data belongs in Baggage, not here. ## Common operational notes - If `traceparent` is absent, the service becomes a trace root and generates a new trace id. Traces starting at the wrong hop usually means the header was stripped by a proxy, gateway or a strict forwarded-header allowlist, not that instrumentation is missing. - Accepting `traceparent` from the public internet lets outsiders choose your trace ids and set the sampled bit, which can be used to force expensive sampling or to pollute traces. Many teams strip and re-root at the edge, accepting inbound context only from partners they trust. - The header is transport-specific by design. The same span context travels over gRPC metadata, message-broker record headers or any other carrier through the same propagator API; only the carrier changes. ## In an interview Recite the four fields with their sizes, say that the third field is the *caller's* span id and becomes the parent, explain that the flag is a sampling hint honoured by the parent-based sampler, and note that `tracestate` must be forwarded intact except for your own entry.

  • A service receives `traceparent` with the sampled bit clear but its own configured sampler is "10% of traces". What should happen, and why?
    With the default parent-based sampler the service honours the parent's decision and does not record, because the local ratio sampler only applies when there is no remote parent. That is deliberate: sampling decided once at the trace root and obeyed downstream yields complete traces, whereas each service sampling independently yields fragments with missing middles. Override it only with a deliberate strategy such as always-sampling a critical service or using a tail sampler in the collector.
  • Your API gateway rebuilds outbound requests from an allowlist of headers and traces now start at the backend. What is wrong and how do you fix it?
    `traceparent` (and `tracestate`, and `baggage` if you use it) is not in the allowlist, so each backend sees no incoming context and becomes a trace root. Add the trace-context headers to the forwarded set, or instrument the gateway itself so it participates as a span and injects context on the outbound leg. Decide separately whether context arriving from untrusted clients should be accepted or re-rooted at the perimeter.

saying these in an interview costs you the question

  • Reading the third field as the receiver's own span id rather than the caller's.
  • Treating the sampled flag as a command that forces the callee to export data.
  • Dropping or rewriting `tracestate` entries owned by other systems when proxying.
  • Assuming span names or attributes travel in the header — only ids, flags and state do.
  • Believing an absent `traceparent` means the callee is uninstrumented, rather than that the header was stripped upstream.

context