Engineering
Building Typed Event Pipelines
By Journal
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 type | Primary sink | Retention | Notes |
|---|---|---|---|
signup.started | Product analytics | 24 months | Used for activation funnels. |
build.queued | Operational metrics | 12 months | Used for latency and reliability views. |
error.logged | Observability | 90 days | Redact 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
- Reject invalid payloads at the edge instead of silently dropping fields.
- Log validation failures with the event type, not the full payload.
- Version the schema when removing or changing a field.
- 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.