Skip to main content
An event moves through Streaming Hub in three stages: the hub ingests it from the internal stream, matches it against your subscriptions, and dispatches it to each matched destination. This page walks through each stage and the delivery guarantees that fall out of it.

Ingesting events from the stream


Streaming Hub consumes the platform stream as CloudEvents 1.0 messages in binary content mode: the CloudEvents context attributes travel in Kafka record headers (each prefixed ce-) and the event body is the record value. The hub reads routing and identity from the headers without deserializing the payload. Every record the hub accepts must carry this context set: For each record the hub returns one of three verdicts: The tenant admitted at ingest comes only from the validated ce-tenantid attribute the parser extracted. An unknown or inactive tenant is dropped at the roster gate — the one point where tenant isolation is enforced on the ingest path.
Three related strings look alike but are distinct — never conflate them:
  • ce-type — the CloudEvents type: studio.lerian.<resource>.<event> (for example, studio.lerian.transaction.posted).
  • Kafka topic — where the event rides on the internal bus. There is no single platform-wide prefix: each producer owns its own topic namespace. Midaz routes to explicit topics with a fixed service segment — ledger events ride lerian.streaming.ledger_<resource>.<event>, the service folded into the first segment (so transaction.posted rides lerian.streaming.ledger_transaction.posted) and any hyphen in the resource or event turned into an underscore on the topic (balance.config-changed rides lerian.streaming.ledger_balance.config_changed). Other producers — Consignado and cross-product commands — derive their namespace from their ce-source instead.
  • X-Lerian-Event-Type — the type stamped on a delivered webhook: the bare <resource>.<event> tail of the ce-type (transaction.posted). It mirrors the ce-type identity, not the Kafka topic — it carries no producer service segment (no ledger_ fold).
See the event streaming overview for the full wire contract.

Consume-once deduplication


The internal stream is at-least-once, so the same record can arrive more than once. On the way into the event inbox the hub deduplicates on ce-id: a duplicate id writes nothing and is counted as a dedup drop. Records are persisted per Kafka partition in one transaction, and the stream position for a partition is committed only after that partition’s write commits — so a failure on one partition never loses or double-commits events on another.

Subscription matching


Once an event is stored, the hub resolves which of your subscriptions should receive it. Subscription matching is internal and catalog-free: it evaluates each event against your subscriptions on (tenant, event type, schema major) directly, without consulting the event catalog. An event type no subscription wants produces zero deliveries — a correct outcome, not an error. A subscription is admitted by matching only when it is both enabled and in verification_state = active. These are two independent conditions — see the subscription model — and matching requires both.

Dispatching to your destinations


For every match, the hub creates a delivery job and hands it to a worker pool (eight workers by default). Workers claim due jobs atomically with a short lease and interleave across tenants, so no single tenant’s backlog starves the others. Each worker loads the stored payload, decrypts the destination’s signing secret or credential in memory, and makes exactly one delivery attempt to the sink. Delivery is at-least-once: the hub may deliver the same event more than once (through retries or redeliveries after a worker restart). Every delivery carries a stable X-Lerian-Event-Id (the ce-id) for you to deduplicate on, and a per-attempt X-Lerian-Delivery-Id that changes on every retry. Treat a repeated X-Lerian-Event-Id as a duplicate and acknowledge it without reprocessing. See Consuming events for the consumer side of this contract.

Retries and back-off


When an attempt fails, the hub schedules the next one on a fixed back-off curve with full jitter:
The curve spans roughly 28 hours across its eight steps. Each subscription can override it with its own schedule (up to 12 steps, each capped at 10 hours); a malformed override falls back to the default curve rather than failing delivery.

Dead-lettering


An event whose retries are exhausted is dead-lettered — the hub stops attempting it and records the terminal outcome. A job that instead keeps crashing a worker mid-attempt — never recording an outcome — is reclaimed a bounded number of times (five by default) and then dead-lettered as poison, so a single toxic job can never occupy a worker forever.

Circuit breaker


Each (tenant, destination) pair has a circuit breaker. When a destination fails repeatedly, its breaker opens and further jobs for that destination are shed — rescheduled without an attempt — so a single broken endpoint does not burn worker capacity or hammer a struggling target. The breaker recovers on its own once the destination starts succeeding again.

Auto-disabling a broken destination


A destination that stays broken for a long time is automatically disabled. Auto-disable trips only when the open failure span is both:
  • sustained — it has been failing for at least the failure window (120 hours by default); and
  • spread — at least 12 hours separate the first and last failure in the span.
Both gates must hold, and a single successful delivery clears the span, so a brief blip never accumulates toward the verdict. When auto-disable trips it flips the subscription’s enabled flag to false — it never touches verification_state. Delivery stops (matching requires enabled), and the hub records why. You recover an auto-disabled subscription with POST /v1/subscriptions/:id/verify, which re-probes the destination and, on success, re-enables it in place. Auto-disable is governed by a kill switch (STREAMING_HUB_AUTODISABLE_ENABLED), so an operator can ship it dark. See recovering an auto-disabled subscription.

Ordering guarantees


The internal stream is partitioned by tenant, so events for one tenant normally ride a single partition and the hub preserves their first-in, first-out order end to end. The hub attributes and deduplicates on the CloudEvents headers, not the Kafka record key, so a producer that spreads (salts) a hot tenant across several partitions changes only physical placement — the hub still attributes and dedups correctly. The one trade-off is ordering: a salted tenant’s events span multiple partitions with no cross-partition order guarantee, so that tenant forfeits strict FIFO. The arrival sequence the hub assigns reflects the order events arrived at the hub, not the order they were produced. A tenant that is not salted keeps single-partition FIFO throughout.

Next steps


Managing subscriptions

The subscription model, onboarding flows, and secret rotation.

Consuming events

Verify signatures, deduplicate, and pull events.