Herald

Architecture

Herald is a WebSocket event server with HTTP management APIs. Clients connect over WebSocket for real-time events. Your backend manages streams, members, and events over HTTP. Herald fans events out to connected subscribers and delivers webhooks back to your app.

Core primitives

PrimitiveDescription
StreamNamed channel with a member list and opaque meta JSON. Streams are the unit of subscription.
EventOpaque body + opaque meta + sender + monotonic sequence number. Herald never inspects event content.
MemberUser ID + role (owner, admin, member). Roles are informational — authorization is handled by Sentry.
PresencePer-user state: online, away, dnd, or offline. Resolved from manual overrides and connection count.
CursorPer-user, per-stream read position as a sequence number.

Fan-out model

When an event is published, Herald:

  1. Assigns a monotonic sequence number within the stream
  2. Stores the event in the buffer
  3. Looks up each subscribed user's active connections
  4. Sends the event frame over each connection's bounded channel
  5. Delivers the webhook to your app backend

The sender receives their own event for multi-tab consistency. Events are totally ordered within a stream by sequence number. No ordering guarantee across streams.

Backpressure

Each connection has a 256-frame bounded channel. If a client falls behind, Herald attempts delivery for 100ms, then drops the frame and increments a counter. The client catches up on reconnect via sequence-based replay.

Event retention

Events are buffered with a configurable retention window (default 7 days) for reconnect replay. After expiry, events are pruned. Herald returns nothing for expired events — no error, no tombstone.

Chat mutations (edits, deletes, reactions) are stored in the buffer intentionally. A client that missed a delete during a brief disconnect gets it via reconnect replay.

Two authorities

  • Inside the buffer window: Herald is authoritative for deltas — new events and mutations.
  • Across the buffer window: Your app database is authoritative for state. Cold loads, long absences, and new-device hydrates go through your API.

Connection lifecycle

Connections move through four states:

CONNECTING → ACTIVE → [EXPIRED or CLOSING] → CLOSED
  • CONNECTING: WebSocket upgrade, HMAC token validation
  • ACTIVE: Subscribed, receiving events
  • EXPIRED: Token expired — 60-second warning sent via system.token_expiring
  • CLOSED: Connection dropped, presence linger period begins (10 seconds)

Reconnect

On reconnect, the client passes its highest received sequence per stream. Herald replays all events after that sequence. Events are deduplicated by ID on the client side. The SDKs handle this automatically.

Presence model

Presence state is resolved by:

state = manual_override ?? (connections > 0 ? "online" : "offline")
  • Manual overrides: Users can set away, dnd, or appear offline while connected. Overrides persist across disconnects.
  • Override expiry: Each override has an optional TTL (default 4 hours). After expiry, presence reverts to connection-derived state.
  • Linger period: 10-second delay before broadcasting offline to prevent flicker during brief disconnects.
  • Broadcast scope: Presence changes only broadcast to streams the user is a member of.

Integration pattern

The recommended write path is persist-first: your app writes to its database, then publishes through Herald for real-time delivery.

Persist-first (recommended)Optimistic-first
Commit pointApp DB writeWebSocket publish
Failure modeEvent not sent (retry from DB)Message lost if mid-flight failure
Best forProduction appsDemos, ephemeral chat

Herald mirrors every event back to your app via webhook for reconciliation, search indexing, and analytics.

Authorization

WebSocket connections authenticate with HMAC-SHA256 signed tokens passed as query parameters. Tokens include the user ID, permitted stream IDs, and expiry. Your backend generates tokens using the API key's secret.

Stream-level authorization is handled by Sentry (embedded). Sentry evaluates policies on each connection and operation. When Sentry is unavailable, Herald fails open — connections are permitted while it recovers.