Observation envelope

Every observation is one JSON envelope. The schema is strict: unknown keys are rejected, and there is no field anywhere in it for raw payload bodies or request headers, which is what makes the privacy posture provable rather than aspirational. Seamward collector SDKs build envelopes for you; this page is the contract for reading evidence or building envelopes yourself.

Envelope fields

The current envelope version is 0.3. Seamward continues to accept unchanged 0.1 and 0.2 envelopes. Version 0.3 adds an explicit indication that payload inspection was skipped. Node collectors emit 0.3; the current PHP and Laravel collectors continue emitting supported 0.2.

FieldTypeRequiredConstraints
envelopeVersionstringyesLiteral "0.3" for new envelopes; "0.1" and "0.2" remain readable
eventIdstringyesStarts with evt_; the idempotency key for ingestion
tenantIdstringyesStarts with ten_; send ten_authenticated, replaced from your credential
environmentIdstringyesStarts with env_; send env_authenticated, replaced from your credential
integrationIdstringyesYour public Integration key sw_int_... (resolved internally by Seamward)
directionenumyesinbound or outbound
protocolenumyeshttp-webhook, http-api, scheduled-feed, or queue
occurredAtstringyesISO 8601 datetime
operationIdentityVersionstringyes in v0.2+Literal "1"
eventTypestringno1 to 128 chars matching [A-Za-z0-9._:-]; a low-cardinality event label such as candidate.completed, never an identifier
deploymentobjectnoRelease context; see below
correlationobjectyesMay be empty {}; see below
contractobjectyesSee below
transportobjectyesSee below
payloadobjectyesStructural evidence; see below
outcomeobjectyesSee below

transport (all fields required)

FieldTypeConstraints
methodenumGET, POST, PUT, PATCH, DELETE
routeTemplatestringNon-empty. A stable template like /webhooks/candidates, never a concrete id or query string
payloadLocationenumrequest, response, or message; required in v0.2+
statusCodeintegerExactly 0, or 100 through 599; 0 means no protocol response was received
durationMsnumberZero or greater
attemptinteger1 to 2,147,483,647

Protocol identity and compatibility

An operation identity combines direction, protocol, method, normalized routeTemplate, eventType, and payloadLocation. A status selector can participate in registered-contract matching, but the observed status does not split the operation-family key. Keep every identity field stable and low-cardinality.

HTTP observations use HTTP-native values. Queue and scheduled-feed collectors expose protocol-native metadata, then project it into the same transport object for envelope 0.2 compatibility:

Observed operationdirectionprotocolmethodrouteTemplatepayloadLocation
Outbound API calloutboundhttp-apiHTTP verbstable absolute pathresponse or request
Inbound API callinboundhttp-apiHTTP verbstable absolute pathrequest
Inbound webhookinboundhttp-webhookHTTP verbstable absolute pathmessage
Queue publishoutboundqueuePOSTqueue or topic labelmessage
Queue consumeinboundqueuePOSTqueue, topic, or subscription labelmessage
Scheduled importinboundscheduled-feedPOSTfeed or job labelmessage
Scheduled exportoutboundscheduled-feedPOSTfeed or job labelmessage

For HTTP protocols, the route must be an absolute path without a query string. :id and {id} segments normalize to {}. For queues and feeds, the target is a label of at most 256 characters matching letters, numbers, ., _, :, /, or -, and it must start with a letter or number. Never use a concrete id, message id, run id, timestamp, or customer value.

The Node.js, PHP, and Laravel collectors use these compatibility status values for non-HTTP wrappers: queue acknowledgement 202, feed completion 200, explicit rejection 422, and thrown host error 500. These numbers describe the envelope projection. They do not command or replace broker acknowledgement, retry, dead-letter, or scheduler behavior.

payload (structural evidence, never values)

FieldTypeRequiredConstraints
storageenumyesnone (the default posture: the body was dropped in your process) or encrypted-object
schemaFingerprintstringyessha256: plus 64 hex chars
schemaShapeshapeyesThe recursive structure description; grammar below
schemaInspectedbooleannoOnly in v0.3. false marks transport-only evidence whose placeholder shape must not be used for structural drift. Absence means the legacy inspection interpretation applies.
redactionPolicyVersionstringyesThe policy version active when the observation was built
objectRefstringonly with encrypted-objectPresent if and only if storage is encrypted-object

contract

FieldTypeRequiredConstraints
declaredVersionstringnoThe contract version you registered, when one exists
observedFingerprintstringyessha256: plus 64 hex chars

outcome

FieldTypeRequiredConstraints
acceptedbooleanyesWhether the operation reached its intended result
businessObjectTypestringnoDomain type such as candidate or payment
businessObjectIdHashstringnoKeyed hash of the business object id; never the raw id

correlation (all fields optional, hashes only)

FieldConstraints
traceIdNon-empty string; safe to send as-is because trace ids are operational, not personal
sourceEventIdHashKeyed hash of the provider's event id
idempotencyKeyHashKeyed hash of the idempotency key
hashNamespaceNon-secret key-generation identifier; required in v0.2+ when a keyed hash is present

deployment (optional; at least one field when present)

FieldConstraints
service1 to 128 chars, starts alphanumeric, then [A-Za-z0-9._:/+~-]
releaseSame label rules as service
commitSha7 to 64 hex chars (abbreviated through full SHA-256)

An empty deployment: {} is invalid; omit the object instead.

The shape grammar

schemaShape describes structure with six node kinds and no values: {"kind":"null"}, {"kind":"boolean"}, {"kind":"number"}, {"kind":"string"}, {"kind":"array","items":[...]} and {"kind":"object","fields":{...}}. Object keys are sorted; array item shapes are deduplicated.

A payload of {"id":"x","amount":1,"tags":["a"]} produces:

Code example

{  "kind": "object",  "fields": {    "amount": { "kind": "number" },    "id": { "kind": "string" },    "tags": { "kind": "array", "items": [{ "kind": "string" }] }  }}

The fingerprint is the SHA-256 of the shape's canonical signature, here {amount:number,id:string,tags:[string]}:

Code example

sha256:ea4cc46608c9c12efd2466a49251ad85b3893dcf7a846a938a3d00aad5785488

Two payloads with the same structure always share a fingerprint regardless of their values, key order, or array length. That is what lets Seamward detect contract drift by comparing fingerprints without ever seeing your data.

Validating an envelope

The contracts package that ships with the collector exports the schema, so you can validate your own envelopes before sending:

Code example

import { safeParseEnvelope } from "@seamward/contracts";
const result = safeParseEnvelope(envelope);if (!result.success) {  console.error(result.error.issues);}

Version compatibility

Strict readers must understand envelope 0.3 before consuming new Node collector observations. Deploy the accepting API and worker first, then update any external readers using safeParseEnvelope, and finally release the new Node collector. The required @seamward/contracts build exports ENVELOPE_VERSION = "0.3" and accepts all three versions. This change is pending the collector/contracts Changesets release; do not assume the currently published alpha already supports it. A schemaInspected field in a 0.1 or 0.2 envelope is rejected. Existing PHP and Laravel 0.2 output needs no rewrite and remains accepted.

Next steps