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