Skip to main content

Overview

WHAWIT learns about your deliveries from outcome events — small JSON records you emit as a unit of work starts, moves through its steps, and resolves. You can emit them two ways. Both speak the same contract, and you can mix them:
  • The Node SDKtrackDelivery() from @usewhawit/collector-node. Batching, retry, tenant exclusion and OpenTelemetry trace linking are handled for you.
  • The HTTP endpointPOST /ingest/events/v1/outcomes. Any language, any runtime.
The v1 contract is frozen. New optional fields may be added; existing field names will not be renamed or removed. Any breaking change ships as a separate v2 route, so instrumentation written today keeps working.

The model in one paragraph

Every event carries a deliveryKey — your own identifier for the unit of work, unique within your project. All events sharing a key describe one delivery. WHAWIT assembles them into a single record: the steps it went through, when it resolved, whether it made its deadline, and what failed if anything did.

Guarantees you can build on

These are the properties that matter when you write the instrumentation:
Assembly is idempotent and order-independent. A retried HTTP call, a duplicated queue message, or a delivery.resolved that beats its own step.completed all converge to the same delivery. You do not need to order or de-duplicate on your side.
step.name is a free string. WHAWIT has no built-in pipeline and no list of valid steps. Whatever you send is what the delivery is made of.
If your product is single-tenant, omit it. Inventing a placeholder value only creates a meaningless dimension in every breakdown.
The SDK never throws into your code. If tracking is unconfigured, the tenant is excluded, or the network is down after retries, you get a handle that does nothing and your delivery proceeds untouched. Events lost after retries are counted and readable via getDroppedEventCount().
Events are queued for asynchronous assembly. A 202 means accepted, not yet processed.

Option 1 — the Node SDK

The handle mirrors the shape of the work: open a delivery, open a step, close the step, close the delivery.
delivery.fail(error) accepts anything — an Error, a string, or a plain object. The SDK extracts the message, the type and a status code where it can find one.

Configuration

trackDelivery() configures itself from initWhawIt() or from WHAWIT_API_KEY / WHAWIT_PROJECT_ID. Call initDeliveryTracking() when you need options that have no equivalent there — above all, tenant exclusion.
string[] | (tenant, tenantKind) => boolean
Glob patterns matched against the tenant name, or a predicate. Matching deliveries never leave your process — no event is buffered and nothing is sent.
'production' | 'test' | 'internal'
default:"production"
Applied when trackDelivery() does not name one.
boolean
default:"false"
Emit an event when a step starts, not only when it completes. Roughly triples event volume for a three-step delivery, so enable it only if you need the in-flight view.
string
Falls back to the initialised SDK, then WHAWIT_API_KEY.
string
Falls back to the initialised SDK, then WHAWIT_PROJECT_ID.
string
Base URL of your WHAWIT ingestion API. Set this explicitly to point at your own instance.
BatchConfig
Batch size, flush cadence and retry behaviour. Shares the collector’s defaults.

Handle reference

DeliveryStepHandle
Opens a step. options.service names the service that owns it, which is what makes cross-team attribution work. options.attempt is a 0-based retry counter. The handle exposes succeed(), fail(error?) and skip().
DeliveryHandle
Sets or revises the deadline before the delivery resolves.
void
Resolves the delivery. A delivery resolves exactly once; later calls are ignored.
boolean
True when this delivery was filtered at source and nothing will be sent.
Before your process exits, flush what is buffered:
Steps automatically pick up the active OpenTelemetry span, so each step carries the traceId and spanId of the work it describes and links straight back to your existing traces.

Option 2 — the HTTP endpoint

The body is a batch of up to 1000 events:

Responses

object
accepted is what was queued; filtered is what was dropped at the edge because it was tagged test or internal.
Invalid payload, or the x-whawit-project-id header is missing.
Missing or invalid API key, or the key has no access to that project.
GET /ingest/events/v1/outcomes/schema returns a machine-readable description of the contract — event types, enums, required fields and the semantics above. Useful as a fixture in your own test suite so a contract change cannot pass unnoticed.

Event types

event
The unit of work began. Carries the identity fields and, ideally, expectedBy.
event
A step began. Optional — send it only if you want the in-flight view.
event
A step finished, in any of its states: succeeded, failed or skipped.
event
Terminal. Requires terminalState (succeeded or failed). This is the event that computes on-time and closes the delivery.

Field reference

Every field of an outcome event.

Required

string
required
Your correlation key for this unit of work — an order id, a payroll run id, a campaign id. Unique per project. Every event for the delivery carries it. Max 400 characters.
string
required
One of delivery.started, step.started, step.completed, delivery.resolved.
string
required
ISO-8601 timestamp of the event itself.

Identity

string
Which of your end customers this delivery belongs to. Optional — omit it in a single-tenant product. Max 200 characters.
string
default:"production"
production, test or internal. Anything not production is dropped at ingestion and never reaches a KPI.
string
Your identifier for the workflow that produced this delivery. Max 200 characters.
string
Your own category for this kind of work — order, payroll_run, campaign. Drives the per-type breakdowns, the expected steps and the default deadline. Max 120 characters.
string
When one workflow resolves into several deliveries, which one this is — an order line, a payroll batch, a journey send-node. Max 200 characters.

Steps

object
Present on step.started and step.completed.

Outcome and timeliness

string
Required on delivery.resolved. succeeded or failed. A delivery is failed if any step failed.
string
The deadline, ISO-8601. Falls back to the configured default for this workflowType when absent. Without either, the delivery is not tracked for timeliness.
boolean
Send it explicitly to override; otherwise derived from expectedBy.
string
Name of the first step that failed. Max 120 characters.

Failure detail

string
transient, permanent or unknown. Omit and WHAWIT classifies it; send it and your value wins.
string
platform, customer, upstream or unknown — whose fault it was. Omit and WHAWIT attributes it; send it and your value wins.
object

Extras

object
Free-form dimensions carried onto the delivery — a carrier name, a destination, a region.
string
default:"v1"
The contract version this event was written against.

Worked examples

The third example is dropped at ingestion. The response reports it under filtered, and it never reaches a delivery record or a KPI.

Keeping internal traffic out

This matters more than it looks. Load tests, staging pipelines and QA runs share your production code path, so without a rule they land in the same reliability number your customers are judged by — inflating it on a good day, wrecking it on a bad one. There are two lines of defence, and you want both:
1

Exclude at source

Configure excludeTenants in the SDK. Matching deliveries never leave your process: nothing is buffered, nothing is sent, no bandwidth is spent.
2

Tag what remains

Send tenantKind: "test" or "internal" on anything you cannot filter by name. WHAWIT drops it at the edge and counts it under filtered in the response.
Deliveries whose tenantKind is test or internal are always excluded, whether or not you configured excludeTenants.

Next steps

Once events are flowing, tell WHAWIT what they mean for your business — the names, the expected steps, the deadlines and your target. See Configure for your business.