Connect OpenTelemetry traces

If your services already export OpenTelemetry traces, you can point a copy of your HTTP spans at Seamward and get error-rate, latency and missing-traffic detection on a connection without changing or deploying application code. It works for any language your tracing covers.

A span describes a call, not its contents. That decides what this path can do, so read what traces can and cannot detect before you rely on it.

What traces can and cannot detect

DetectionFrom tracesWith a collector
Status shifts: error rates and auth failuresYesYes
Latency shiftsYesYes
Retry anomaliesNoYes, the collector hashes an idempotency key
Missing traffic: a connection that goes quietYesYes
Field changes: added, removed, renamed or retypedNoYes
Value changes: units, formats, enumerations, blanksNoYes, with value profiles
Contract comparison against a registered schemaNoYes
Reconciliation rules: missing, duplicated or mismatched recordsNoYes, the collector reports outcomes
Replay fixtures and repair validationNoYes

Everything in the "No" rows needs a payload shape or a business outcome, and a span has neither. Seamward does not estimate them. In the console, a connection fed only by traces shows a Detection coverage card that marks those detectors as Needs a collector.

A sensible path is to start with traces on every connection, then add the Node.js, PHP or Laravel collector, or REST ingestion, on the connections where a silent change would cost you most. A connection can receive both.

Before you start

  • A connection for each provider. A connection is one direction of traffic with one provider, such as outbound calls to your payments API. Create it in the console and note its Connection key and ingest token. See Connect an integration.
  • An OpenTelemetry Collector (or any exporter) that can send OTLP over HTTP with the JSON encoding and custom headers.
  • HTTP spans. Seamward reads HTTP client spans for outbound connections and HTTP server spans for inbound ones.

Send spans

Add one exporter per connection to your OpenTelemetry Collector configuration, and a filter so each exporter receives only that provider's spans:

Code example

exporters:  otlphttp/seamward-payments:    endpoint: https://api.seamward.com/v1/otlp    encoding: json    headers:      Authorization: "Bearer ${env:SEAMWARD_PAYMENTS_INGEST_TOKEN}"      X-Seamward-Connection: "${env:SEAMWARD_PAYMENTS_CONNECTION_KEY}"      X-Seamward-Span-Host: "api.payments.example"
processors:  filter/payments:    error_mode: ignore    traces:      span:        - 'attributes["server.address"] != "api.payments.example"'
service:  pipelines:    traces/seamward-payments:      receivers: [otlp]      processors: [filter/payments, batch]      exporters: [otlphttp/seamward-payments]
  • endpoint is the base URL; the exporter appends /v1/traces, so spans are posted to https://api.seamward.com/v1/otlp/v1/traces.
  • encoding: json is required. Seamward accepts the OTLP JSON encoding only; a protobuf request is refused with 415 otlp_json_required.
  • X-Seamward-Span-Host is optional and recommended. It lists up to 20 hosts, separated by commas, and Seamward keeps only spans whose server host is one of them. It is a second guard behind your own filter, so a misrouted pipeline cannot fill a connection with another provider's calls.
  • Keep the token and key in your collector's environment, not in the configuration file.

Requests are limited to 1 MiB and 1,000 mapped spans; use the batch processor with a send_batch_max_size of a few hundred.

What Seamward reads from a span

The mapping is an allow-list. These attributes are read and every other attribute, event, link and resource attribute is dropped before anything is stored:

ReadUsed for
http.request.method, http.methodThe method. GET, POST, PUT, PATCH and DELETE are accepted
http.response.status_code, http.status_codeThe status code. Without one, the call is recorded as having no known status
url.template, http.routeThe route template, preferred when present
url.path, http.target, url.full, http.urlThe path, when no template is declared. The query string, fragment and any user information are discarded
server.address, net.peer.name, http.hostMatching X-Seamward-Span-Host only. The host is never stored
http.request.resend_countThe attempt number shown on the observation
Span kind, start and end time, status codeDirection, duration, and whether a call without a status failed
Resource service.name, service.versionThe deployment labels, when they are plain labels

Trace IDs and span IDs are used only to derive a stable event ID, so a batch your exporter sends twice is stored once. They are not stored.

Route templates from URLs

Detection groups calls by route template, such as /v1/payment_intents/{}. When a span declares url.template or http.route, Seamward uses it. Otherwise it derives one from the path by replacing every segment that could be an identifier with a placeholder:

PathTemplate
/v1/payment_intents/pi_3Nf8/v1/payment_intents/{}
/v2/orders/1001/items/7/v2/orders/{}/items/{}
/users/3f2b8c1e-9d4a-4f6b-8c2d-1e5f7a9b0c3d/users/{}
/accounts/ada%40example.com/keys/accounts/{}/keys

A segment is kept only when it is a plain word of letters, underscores and hyphens up to 32 characters, or a version such as v1. Anything with a digit, a dot, or an encoded character is replaced.

Paths that carry names

A path segment made only of letters is kept, because it looks like a resource name. If your URLs carry values that are plain words, such as a username in /users/alice, that word would be stored as part of the template. Set url.template on those spans, or drop them in your collector, before sending them.

Responses

A request whose spans were all stored returns 200 with {}. When some spans were left out, the response reports how many and why, as the OTLP partial-success shape:

Code example

{  "partialSuccess": {    "rejectedSpans": 42,    "errorMessage": "skipped: not-http=40, other-host=2"  }}
ReasonMeaning
not-httpThe span has no HTTP method; database, queue and internal spans are ignored
wrong-directionA server span on an outbound connection, or a client span on an inbound one
unsupported-methodA method other than the five accepted
other-hostThe span's host is not listed in X-Seamward-Span-Host
no-routeNo template or path could be read
malformedMissing IDs or timestamps
over-limitMore than 1,000 spans in one request

Skipped spans are not an error. Your exporter should not retry them.

StatusBodyCause
400invalid_otlp_requestThe body is not an OTLP trace request
400invalid_span_hostX-Seamward-Span-Host is not a list of host names
401unauthenticatedThe Connection key or ingest token is missing, wrong or revoked
403observation_monthly_quota_exhaustedThe workspace's monthly observation allowance is used up
415otlp_json_requiredThe request is not JSON; set encoding: json on the exporter
429workload_rate_limitedThe connection's minute budget is used up; honour Retry-After

Unlike POST /ingest, this endpoint does not require a signed body, because an exporter cannot sign one. It authenticates with the Connection key and ingest token over TLS. Trace-sourced observations count toward the same monthly allowance and minute budgets as any other observation.

Check that it works

  1. Open the connection in the console. The Detection coverage card should say "Receiving OpenTelemetry traces".
  2. Open Observations and filter to the connection. Each call appears with its method, route template, status and duration, and no payload shape.
  3. Behavioural findings need a baseline first: an error-rate or latency shift is reported only after enough ordinary traffic has been seen to compare against.

What is not available

  • OTLP over gRPC and the protobuf encoding of OTLP over HTTP.
  • Metrics and logs. Only traces are read.
  • Importers for webhook gateways and API gateways, a traffic-mirroring sidecar, and collectors for Python, Go, Ruby, Java and .NET. These are planned and not built.

Next steps