How Does Service B Know It's Part of the Same Trace?
When Service A calls Service B over HTTP, they share no memory. Service B has no inherent knowledge that this incoming request is part of a larger trace, or who its parent span is. Without a mechanism to communicate this, every service would start a brand-new, disconnected trace — and you'd have 20 isolated traces instead of one unified picture.
This is solved through Context Propagation: the practice of passing trace context from one service to the next as part of the request itself.
When Service A makes an HTTP call to Service B, the OTel SDK automatically injects the current trace context into the HTTP request headers. Service B's OTel SDK automatically extracts those headers, reads the Trace ID and Parent Span ID, and uses them to create its own child spans that are correctly linked into the existing trace. This injection/extraction happens transparently — you don't write this code yourself when using auto-instrumentation.
W3C Trace Context Standard
Historically, every vendor used different header names: Zipkin used x-b3-traceid and x-b3-spanid, Jaeger used uber-trace-id, AWS X-Ray used x-amzn-trace-id. This meant services instrumented with different libraries couldn't interoperate.
OTel has standardized on the W3C Trace Context specification, primarily using the traceparent header:
traceparent: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01
This single string encodes four fields, separated by hyphens:
- 00 — version (always 00 currently)
- 4bf92f3577b34da6a3ce929d0e0e4736 — the 128-bit Trace ID (shared across all services)
- 00f067aa0ba902b7 — the Parent Span ID (the span in Service A that made this call)
- 01 — trace flags (the last bit indicates whether this trace is sampled: 01 = sampled, 00 = not sampled)
For non-HTTP protocols (gRPC, message queues like Kafka), context propagation works the same way — the trace context is injected into message metadata or gRPC headers.
Baggage: Propagating Business Context
Sometimes you need to carry more than just IDs. You might want every downstream service to know the customer_tier, user_id, or experiment_id of the originating request — without each service having to look it up independently.
Baggage is a mechanism defined by the W3C Baggage specification that propagates arbitrary key-value pairs alongside the trace context, using the baggage HTTP header:
baggage: customer_tier=gold,user_id=12345,experiment_id=checkout-v2
Every OTel-instrumented service in the chain automatically forwards this header. Any service can read the baggage values and use them — for logging, for routing decisions, or as attributes on their spans.
Critical caveat: Baggage is propagated in-band with every request. Keep it small. Putting large payloads in baggage adds overhead to every single HTTP call in your system. Use it for small, high-value identifiers only.
A common interview question: "How do you correlate a trace across services that use different messaging systems — some HTTP, some Kafka?" The answer is context propagation via message metadata. When producing a Kafka message, the OTel SDK injects the traceparent value into the Kafka message headers. The consumer extracts it and creates a child span linked to the producer's span. The trace is continuous even across async boundaries. This is called a linked span or async trace continuation, and it's a key architectural pattern for event-driven systems.