Skip to main content
Pairs with: LangWatch OpenTelemetry guide. Same wire shape; this page uses the governance ingest endpoint instead of a project-scoped one, so events get langwatch.origin.kind = "ingestion_source" instead of app.
otel_generic is the production-ready governance ingest path. Anything that speaks OTLP/HTTP, your own agents, third-party SDKs, tracing libraries, points at the source’s URL with the source’s bearer secret and events flow end-to-end into the governance dashboard, the per-source detail page, and the langwatch ingest tail CLI.
Not sending your own app’s traces here. This endpoint is for third-party OTel feeds your org wants in the governance audit trail. For your own application’s LLM traces, use /api/otel/v1/traces with your project API key, see Choosing the right OTel endpoint for the full comparison.

Currently implemented

  • Receiver: POST /api/ingest/otel/:sourceId (also accepts claude_cowork sources, see Anthropic Cowork).
  • Auth: Authorization: Bearer lw_is_<secret>.
  • Body: OTLP/HTTP JSON envelope (resource_spans, scope_spans, spans). snake_case and camelCase both accepted.
  • OTLP shape: Spans (parent-child + duration). Lands in recorded_spans.
  • Pipeline: Receiver lazy-ensures the hidden Governance Project, stamps langwatch.origin.* + langwatch.governance.retention_class on every span, hands off to the existing trace pipeline (same traces.collection.handleOtlpTraceRequest that powers /api/otel/v1/traces). No parallel write path.
  • Status flip: receiver calls recordEventReceived(sourceId) after any successful 202; source flips from awaiting_first_eventactive.

What the admin configures upstream

After clicking Create on the composer, the one-time-reveal modal exposes: The composer also persists one optional config field:
  • allowedSourceTypeLabel, when set, only events whose LangWatchSourceType resource attribute matches this label are accepted. Leave blank to accept any.
In the upstream platform’s OTLP exporter config:

Event shape we accept today

OTLP spans land in recorded_spans with langwatch.origin.kind = "ingestion_source" stamped at the receiver edge. The governance fold projection (governance_kpis + governance_ocsf_events) extracts the canonical KPI + OCSF dimensions from these attribute precedences. Span resource attributes are merged with span-level attributes (span-level wins on collision): If a field doesn’t resolve from the precedence list, the fold projection stores an empty string, 0, the span is still persisted and queryable in the trace viewer.

What is still envelope-only, follow-up work

  • gRPC. Only OTLP/HTTP JSON + protobuf is implemented (gzip, deflate, brotli accepted via the shared parser). OTLP/gRPC is not yet wired. Use the JSON or protobuf HTTP exporter on the upstream side.
  • Span events, span links. Not extracted into separate fold dimensions in this slice; the parent span’s data is surfaced. Span events appear in the trace viewer’s drill-down.

Verify with CLI + web

After wiring the upstream exporter:
The web equivalent is /settings/governance/ingestion-sources/<sourceId>, same data, same poll cadence semantics. A successful smoke test produces output like:
If eventType shows agent.action instead of api.call, tool.invocation, the upstream span isn’t carrying the attributes the heuristic looks for, the source still works, but you’ll get less filtering precision in the admin dashboard. The fix is on the upstream side: emit gen_ai.request.model and (if applicable) tool.name.

Test it now: copy-paste curl

The five attributes that make the canonical first-event flow useful (and that the secret-reveal modal’s auto-generated curl pre-fills): A complete OTLP/HTTP JSON body that hits the full normaliser and produces one row in langwatch ingest tail, the per-source detail page. Substitute <otlpUrl> and <secret> from the one-time-reveal modal:
Expected response:
events:1 confirms the body parsed and the row landed. events:0 with bytes>0 means the body was accepted but no spans were extracted, common causes:
  • resource_spans, resourceSpans not present at the top level (wrong nesting)
  • spans array empty within a scope_spans entry
  • Body is not OTLP-shaped JSON (e.g. you posted a Workato-style envelope to the OTLP endpoint).
After the curl succeeds, verify with:

OTLP attribute value keys are camelCase

The OTLP/HTTP JSON spec uses camelCase for attribute value field names, stringValue, intValue, doubleValue, boolValue. Snake_case (string_value, int_value) is not accepted by the normaliser today. If you’re hand-authoring OTLP bodies for tests, match the casing exactly or you’ll see events:0 despite a 202.