Event Notification
A lightweight broadcast of "something happened" — the minimal notification that prompts subscribers to act, contrasted with event-carried state transfer, and aligned to the CloudEvents specification.
What it is
Event notification is the pattern of broadcasting a lightweight message that announces a state change, without necessarily including all the data about that change. A notification event says "order 4291 was placed" — not the full order contents. Subscribers who care use the event identifier to fetch fresh data from the source's API if they need more context. This "skinny event" approach deliberately keeps payloads small and keeps data ownership with the originating service: consumers pull the current state rather than receiving a copy baked into the event at publish time.
Its counterpart, event-carried state transfer (ECST), embeds the full entity state in the event: the order event carries all line items, addresses, and totals. ECST makes consumers fully autonomous — they never need to call back to the source — but at the cost of larger payloads, potential data staleness if the event is processed late, and tighter coupling to the source service's data model. The choice between notification and ECST is one of the most important design decisions in an event-driven architecture.
The CloudEvents specification (CNCF) defines a standard envelope for event notifications: specversion, id, source, type, subject, time, and optional data. Adopting CloudEvents simplifies interoperability between services and platforms.
Why it exists
Pure event notification exists to keep the source service as the authoritative owner of its data. If every event carries full state, the source must commit to a stable, public representation of all its internal data structures — which becomes a barrier to internal refactoring. With notification events, the source only commits to the existence and identity of the change; consumers that need data make a point-in-time API call and receive current, validated data. This pattern also reduces message sizes and broker storage, and prevents consumers from caching stale snapshots embedded in old events.
When to use
- Triggering workflows — a payment service fires
payment.completedwith just the payment ID; the fulfilment service reacts by fetching the full order and initiating shipping. - Cache invalidation — a product catalogue service fires
product.updated { productId }; all downstream caches evict the named product and re-fetch on next access. - When source data ownership must be preserved — the notification consumer must always see the current, authoritative state from the source, not a stale snapshot from the event.
- High-fan-out low-data scenarios — millions of devices registering "activity detected" with just a device ID and timestamp, where attaching full device context to every event would be wasteful.
When not to use
- When consumer autonomy is required — if the source service is unavailable or rate-limited and consumers cannot afford a follow-up fetch, use ECST to embed state in the event.
- High-frequency events with stable large payloads — if every notification triggers a full-entity fetch, you pay N API calls for N events; ECST amortises this cost by bundling state with the event.
- Audit or compliance logs — a compliance log needs a point-in-time snapshot of the state at the moment of the event; notifications without embedded state cannot reconstruct history.
Typical architecture
A source service writes a domain event (minimal payload: IDs, event type, timestamp) to a topic. Subscribers receive the notification and, if they need entity data, call the source's query API to fetch current state. A schema registry ensures the notification schema is versioned and subscribers can validate events.
Order Service
│ INSERT order → DB
│ PUBLISH to topic "order-events"
│ Payload (CloudEvents envelope):
│ {
│ "specversion": "1.0",
│ "id": "evt-uuid",
│ "source": "/orders",
│ "type": "com.example.order.placed",
│ "subject": "order/4291",
│ "time": "2026-05-07T10:00:00Z",
│ "data": { "orderId": "4291" } ← minimal
│ }
│
▼
Topic: "order-events"
│
├─ Fulfilment Service
│ receives notification
│ GET /orders/4291 ──► Order Service API (fetch full state)
│ process fulfilment
│
└─ Analytics Service
receives notification
batch-accumulates IDs → query read model
Pros and cons
Pros
- Small event payloads — brokers store less; consumers deserialise less.
- Data ownership stays with the source — no stale embedded copies.
- Source can refactor internal model without changing event schema.
- Consumers always see current, validated state from the authoritative source.
- CloudEvents standard enables cross-platform interoperability.
Cons
- N+1 fetches — each notification triggers a follow-up API call, increasing load on the source service.
- Source availability dependency — consumers cannot process notifications if the source API is down.
- No historical state — cannot replay old events to reconstruct what the entity looked like at the time of the event (use ECST for this).
- Increased latency — notification + fetch round trip adds latency vs reading state embedded in the event.
Implementation notes
Event schema design: The notification payload should carry the minimum needed for a consumer to identify what changed and fetch more if needed: the entity type, entity ID, event type, timestamp, and schema version. Do not include business attributes that change frequently — those belong in the source API response.
CloudEvents specification: Adopting CloudEvents provides a vendor-neutral envelope. Required attributes: specversion (always "1.0"), id (unique per event), source (URI of the service), type (reverse-DNS dotted string like com.example.order.placed). Optional but recommended: subject (entity reference), time (ISO 8601), dataschema (schema URL). CloudEvents is supported natively by Knative, Azure Event Grid, Google Eventarc, and AWS EventBridge.
Event registry: Maintain a central catalogue of event types, schemas, producers, and consumers. An event registry prevents duplicated types, enables discoverability, and is the governance mechanism for preventing breaking schema changes. Tools: AsyncAPI (documentation), Confluent Schema Registry (enforcement), AWS EventBridge Schema Registry.
Schema versioning: Use semantic versioning on event schemas. For backwards-compatible changes (adding optional fields), increment the minor version. For breaking changes (removing/renaming required fields), create a new event type (e.g., order.placed.v2) and run old and new versions in parallel during migration.
Common failure modes
- Notification received but source API unavailable: The consumer cannot fetch entity state; events pile up unprocessed. Mitigate with a circuit breaker on the fetch call and consumer retry with backoff.
- Consumer fetches stale state: Between event publish and consumer fetch, the entity is updated again; the consumer fetches the new state, skipping intermediate changes. This is normal and expected — design consumers to handle current state, not delta.
- Missing schema registry: Different services publish
order.placedwith different schemas; consumers fail randomly depending on which producer's version they receive. - Over-fetching: A consumer fan-out of 10 subscribers each fetching the full entity for every event creates 10× load on the source API; use ECST or a read model cache to reduce fetch pressure.
Decision checklist
- Can consumers tolerate a follow-up API call on each notification? If source availability is poor, consider ECST.
- Is the notification payload structured per the CloudEvents specification?
- Is there a schema registry with event type registration and compatibility enforcement?
- Is the event type string versioned (e.g.,
com.example.order.placed.v2) for breaking changes? - Is there a circuit breaker on the follow-up fetch to prevent notification processing stalls during source outages?
- For high fan-out: is per-consumer fetch load on the source service acceptable under peak conditions?
Example use cases
- Inventory cache invalidation: Product service fires
product.updated { productId }; inventory caches in five regional services evict the named product and fetch fresh stock levels on next request. - User account lifecycle: Auth service fires
user.deactivated { userId }; notification service, CRM, and audit service each react independently using the userId to fetch context relevant to their own domain. - CI pipeline trigger: Source control fires
com.example.repo.pushed { repoId, branch, commitSha }; the CI platform fetches the full diff and configuration to start the build.
Related patterns
- Command Pattern — imperative counterpart; what happens after a command succeeds is often an event notification.
- Publish/Subscribe — the delivery mechanism for event notifications.
- Webhooks — push-based event notification for external system integration.
- Event-Driven Architecture — the broader architectural style built on event notifications and reactions.
Further reading
- CloudEvents Specification — CNCF standard event envelope for interoperability.
- What Do You Mean by Event-Driven? — Martin Fowler distinguishes notification, state transfer, sourcing, and CQRS.
- AsyncAPI Specification — document-driven event schema registry and discovery.