Engineering

Building Typed Event Pipelines

By Journal

Birds flying across a pale blue sky

Product analytics becomes easier to trust when every event has a clear contract. This sample post walks through a small typed pipeline for collecting, validating, and routing application events.

Dummy content note: this article is placeholder copy for testing the blog content model and Markdown rendering.

Why typed events help

Untyped event payloads usually fail in quiet ways: missing identifiers, renamed fields, unexpected enum values, or inconsistent timestamps. A typed pipeline makes those mistakes visible earlier.

A minimal event contract can be expressed as a discriminated union:

type SignupStarted = {
type: 'signup.started';
userId: string;
source: 'web' | 'mobile' | 'invite';
occurredAt: string;
};
type BuildQueued = {
type: 'build.queued';
buildId: string;
repository: string;
queuedBy: string;
occurredAt: string;
};
type ProductEvent = SignupStarted | BuildQueued;

Intake checklist

Before publishing an event, the client should confirm:

  • The event name is stable and documented.
  • The payload includes a durable actor or entity identifier.
  • The timestamp is generated once, close to the source action.
  • Optional fields have clear defaults downstream.
  • Sensitive values are removed before the event leaves the app.

Example validator

function isIsoTimestamp(value: string) {
return !Number.isNaN(Date.parse(value));
}
function validateProductEvent(event: ProductEvent) {
if (!isIsoTimestamp(event.occurredAt)) {
throw new Error(`Invalid timestamp for ${event.type}`);
}
switch (event.type) {
case 'signup.started':
return event.userId.length > 0;
case 'build.queued':
return event.buildId.length > 0 && event.repository.includes('/');
default:
return false;
}
}

Routing table

Event typePrimary sinkRetentionNotes
signup.startedProduct analytics24 monthsUsed for activation funnels.
build.queuedOperational metrics12 monthsUsed for latency and reliability views.
error.loggedObservability90 daysRedact user-entered text first.

Batch shape

Events can be batched before leaving the browser. A compact JSON envelope keeps transport details separate from the event contract.

{
"batchId": "evt_batch_01jz9n5q",
"schemaVersion": 3,
"events": [
{
"type": "signup.started",
"userId": "user_123",
"source": "web",
"occurredAt": "2026-06-13T18:22:00.000Z"
}
]
}

Operational notes

  1. Reject invalid payloads at the edge instead of silently dropping fields.
  2. Log validation failures with the event type, not the full payload.
  3. Version the schema when removing or changing a field.
  4. Keep replay tooling close to the consumer that owns the projection.

A typed event pipeline does not need to be complex. The useful baseline is a stable vocabulary, predictable payloads, and a small amount of validation at each boundary.