Tracing OpenAI Realtime API with WebSockets
Last updated: February 6, 2026
This issue is applicable to customers matching these conditions
Plans: Any
Deployments: Any
Summary
Configure tracing and observability for OpenAI Realtime API WebSocket sessions using Braintrust's OpenTelemetry integration. This approach allows you to log session-based traces, capture event metadata, and maintain full control over event schemas without relying on the Braintrust proxy.
Configuration Steps
Step 1: Configure OpenTelemetry for Braintrust
Set up OpenTelemetry to send traces directly to Braintrust's OTEL endpoint with proper authentication and project context.
OTEL_EXPORTER_OTLP_ENDPOINT=https://api.braintrust.dev/otel
OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer , x-bt-parent=project_name:"Step 2: Initialize session-based tracing
Create a root span for the entire WebSocket session to track the long-lived connection and session-level metadata.
// Java example using OpenTelemetry
Span sessionSpan = tracer.spanBuilder("voice_interaction")
.setAttribute("braintrust.input", userSpeechText)
.setAttribute("braintrust.metadata", JSON.toJson(Map.of(
"user_id", "user-123",
"session_id", "session-456",
"audio_duration_ms", 2500
)))
.setAttribute("braintrust.tags", JSON.toJson(List.of("voice", "realtime-api")))
.startSpan();Step 3: Log individual events as nested spans
Create child spans for specific WebSocket events like RAG retrieval and LLM generation to maintain granular observability.
// Nested span for RAG retrieval
Span retrievalSpan = tracer.spanBuilder("rag_retrieval")
.setParent(Context.current().with(sessionSpan))
.setAttribute("braintrust.metadata", JSON.toJson(Map.of(
"documents_considered", List.of(
Map.of("id", "kb_123", "title", "Product FAQ", "relevance", 0.92),
Map.of("id", "kb_456", "title", "User Guide", "relevance", 0.78)
),
"retrieval_method", "semantic_search"
)))
.startSpan();Step 4: Capture conversation data in root span
Store complete conversation context and metadata in the root span to support custom scorers and analysis tools.
// Store conversation data for scoring
sessionSpan.setAttribute("braintrust.output", llmResponse);
sessionSpan.setAttribute("braintrust.metadata", JSON.toJson(Map.of(
"conversation_history", conversationMessages,
"rag_documents", retrievedDocuments,
"total_tokens", tokenCount,
"session_duration_ms", sessionDuration
)));Step 5: Close spans and flush data
Properly end spans when events complete and ensure data is sent to Braintrust.
// End nested spans first
retrievalSpan.end();
// End session span when WebSocket closes
sessionSpan.setAttribute("braintrust.metrics", JSON.toJson(Map.of(
"total_messages", messageCount,
"total_duration_ms", sessionDuration
)));
sessionSpan.end();
// Flush telemetry data
OpenTelemetry.getGlobalOpenTelemetry().shutdown();