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
| Detection | From traces | With a collector |
|---|---|---|
| Status shifts: error rates and auth failures | Yes | Yes |
| Latency shifts | Yes | Yes |
| Retry anomalies | No | Yes, the collector hashes an idempotency key |
| Missing traffic: a connection that goes quiet | Yes | Yes |
| Field changes: added, removed, renamed or retyped | No | Yes |
| Value changes: units, formats, enumerations, blanks | No | Yes, with value profiles |
| Contract comparison against a registered schema | No | Yes |
| Reconciliation rules: missing, duplicated or mismatched records | No | Yes, the collector reports outcomes |
| Replay fixtures and repair validation | No | Yes |
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]endpointis the base URL; the exporter appends/v1/traces, so spans are posted tohttps://api.seamward.com/v1/otlp/v1/traces.encoding: jsonis required. Seamward accepts the OTLP JSON encoding only; a protobuf request is refused with415 otlp_json_required.X-Seamward-Span-Hostis 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:
| Read | Used for |
|---|---|
http.request.method, http.method | The method. GET, POST, PUT, PATCH and DELETE are accepted |
http.response.status_code, http.status_code | The status code. Without one, the call is recorded as having no known status |
url.template, http.route | The route template, preferred when present |
url.path, http.target, url.full, http.url | The path, when no template is declared. The query string, fragment and any user information are discarded |
server.address, net.peer.name, http.host | Matching X-Seamward-Span-Host only. The host is never stored |
http.request.resend_count | The attempt number shown on the observation |
| Span kind, start and end time, status code | Direction, duration, and whether a call without a status failed |
Resource service.name, service.version | The 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:
| Path | Template |
|---|---|
/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" }}| Reason | Meaning |
|---|---|
not-http | The span has no HTTP method; database, queue and internal spans are ignored |
wrong-direction | A server span on an outbound connection, or a client span on an inbound one |
unsupported-method | A method other than the five accepted |
other-host | The span's host is not listed in X-Seamward-Span-Host |
no-route | No template or path could be read |
malformed | Missing IDs or timestamps |
over-limit | More than 1,000 spans in one request |
Skipped spans are not an error. Your exporter should not retry them.
| Status | Body | Cause |
|---|---|---|
| 400 | invalid_otlp_request | The body is not an OTLP trace request |
| 400 | invalid_span_host | X-Seamward-Span-Host is not a list of host names |
| 401 | unauthenticated | The Connection key or ingest token is missing, wrong or revoked |
| 403 | observation_monthly_quota_exhausted | The workspace's monthly observation allowance is used up |
| 415 | otlp_json_required | The request is not JSON; set encoding: json on the exporter |
| 429 | workload_rate_limited | The 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
- Open the connection in the console. The Detection coverage card should say "Receiving OpenTelemetry traces".
- Open Observations and filter to the connection. Each call appears with its method, route template, status and duration, and no payload shape.
- 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
- Drift detection: the behavioural detectors traces feed.
- Node.js collector: add structural and value detection to the connections that matter most.
- Ingest observations: the signed endpoint collectors use.
