Skip to main content
Metadata enriches your traces with contextual information — who made the request, which conversation it belongs to, and any custom data relevant to your application. Labels help you categorize and filter traces in the dashboard. This guide provides a unified reference for sending metadata across all integration methods. For SDK-specific details, see the tutorials linked below.

Quick Reference

ConceptOTEL AttributeREST APIDescription
Thread/Conversationgen_ai.conversation.idmetadata.thread_idGroups messages in a conversation
User IDlangwatch.user.idmetadata.user_idIdentifies the end user
Customer IDlangwatch.customer.idmetadata.customer_idYour platform’s customer/tenant
Labelslangwatch.labelsmetadata.labelsCategorization tags
Custom Metadatametadata attributemetadata.*Any additional context
For OTEL, gen_ai.conversation.id follows the OpenTelemetry GenAI semantic conventions. The legacy langwatch.thread.id attribute is also supported.

SDK Examples

For detailed SDK-specific tutorials, see:
import { setupObservability } from "langwatch/observability/node";
import { getLangWatchTracer } from "langwatch";

setupObservability();
const tracer = getLangWatchTracer("my-service");

async function handleUserMessage(userId: string, conversationId: string) {
  return await tracer.withActiveSpan("HandleMessage", async (span) => {
    // Thread/conversation ID (OTEL semconv)
    span.setAttribute("gen_ai.conversation.id", conversationId);

    // User and customer identification
    span.setAttribute("langwatch.user.id", userId);
    span.setAttribute("langwatch.customer.id", "tenant-123");

    // Labels for filtering (JSON array)
    span.setAttribute("langwatch.labels", JSON.stringify(["production", "premium-user"]));

    // Custom metadata (JSON object)
    span.setAttribute("metadata", JSON.stringify({
      feature_flags: ["new-ui", "beta-model"],
      request_source: "mobile-ios"
    }));

    // Your application logic...
  });
}

Raw OpenTelemetry

If you’re using vanilla OpenTelemetry without the LangWatch SDK:
import { trace } from "@opentelemetry/api";

const tracer = trace.getTracer("my-service");

tracer.startActiveSpan("operation", (span) => {
  // OTEL semconv for conversation/thread
  span.setAttribute("gen_ai.conversation.id", "conv-456");

  // LangWatch-specific attributes
  span.setAttribute("langwatch.user.id", "user-123");
  span.setAttribute("langwatch.customer.id", "customer-789");
  span.setAttribute("langwatch.labels", JSON.stringify(["urgent", "support"]));

  // Custom metadata as JSON string
  span.setAttribute("metadata", JSON.stringify({
    priority: "high",
    department: "engineering"
  }));

  // ... your code ...
  span.end();
});
Exporter configuration:
import { OTLPTraceExporter } from "@opentelemetry/exporter-trace-otlp-http";

const exporter = new OTLPTraceExporter({
  url: "https://app.langwatch.ai/api/otel/v1/traces",
  headers: {
    Authorization: `Bearer ${process.env.LANGWATCH_API_KEY}`,
  },
});
The OTEL endpoint is /api/otel/v1/traces (not /v1/traces).

REST API

Send traces directly via HTTP. See REST API for full details.
curl -X POST "https://app.langwatch.ai/api/collector" \
  -H "X-Auth-Token: $LANGWATCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "trace_id": "trace-123",
    "spans": [
      {
        "type": "llm",
        "span_id": "span-456",
        "name": "chat-completion",
        "model": "gpt-4",
        "input": {"type": "text", "value": "Hello"},
        "output": {"type": "text", "value": "Hi there!"},
        "timestamps": {
          "started_at": 1699900000000,
          "finished_at": 1699900001000
        }
      }
    ],
    "metadata": {
      "user_id": "user-123",
      "thread_id": "conversation-456",
      "customer_id": "customer-789",
      "labels": ["production", "premium"],
      "any_custom_field": "any value"
    }
  }'

Reserved vs Custom Fields

In the REST API metadata object:
FieldTypeDescription
user_idstringEnd user identifier
thread_idstringConversation/session ID
customer_idstringYour tenant/customer ID
labelsstring[]Categorization tags
other keysanyStored as custom metadata

Best Practices

Always set user_id

Required for user-level analytics and filtering by specific users.

Use thread_id for conversations

Groups related messages together. Essential for chatbots and multi-turn interactions.

Labels for categorization

Use consistent labels like production, staging, support for filtering.

Custom metadata for context

Add any relevant context: feature flags, A/B variants, request sources.

What You Get

Once traces include metadata:
  • Filter by user — Find all traces for a specific user
  • View conversations — See all messages in a thread grouped together
  • Filter by labels — Quickly filter to specific categories
  • Search custom fields — Find traces by any custom metadata value
  • User analytics — View per-user metrics and patterns