newsletter

Azure Event Grid and Event Hubs in .NET: When to Use Each

8 min read

Newsletter Sponsors

Copied

Rider 2026.2 Is Out: Your Agent Gets the IDE's Intelligence

AI agents burn tokens rediscovering what your IDE already knows. Rider 2026.2 fixes that with agent skills: Claude, Codex, Copilot, and others get exact answers from Rider's code model, so refactoring, debugging, and profiling run on real evidence, and quality hooks validate every code edit. The release also brings natively integrated GitHub Copilot, support for third-party completion models, and noticeably faster debugging and branch switching.

๐Ÿ‘‰ Get the full picture of the release

Copied

Label a GitHub Issue, Get Back a Pull Request (Sponsored)

Your backlog is full of small, well-scoped issues that nobody has time to pick up. With Coder Agents, you add a label like "coder" to a GitHub issue and a background agent reads the context, writes the code, and opens a pull request for you to review โ€” all running on infrastructure you control.

๐Ÿ‘‰ See how Coder Agents work

Azure offers three services with similar names but very different roles: Service Bus, Event Hubs, and Event Grid.

They all move data between parts of your system, so it is easy to assume they are interchangeable. They are not.

I have already covered Azure Service Bus in detail - the reliable message broker for commands and workflows between services.

This post covers the other two and then shows you exactly when to reach for each.

In this post, we will explore:

  • Azure Event Grid: how it works
  • Using Azure Event Grid in .NET
  • Azure Event Hubs: how it works
  • Using Azure Event Hubs in .NET
  • Service Bus vs Event Hubs vs Event Grid: when to use each

Let's dive in.

Copied

Azure Event Grid: How It Works

Azure Event Grid is a fully managed event routing service.

It follows a simple idea: something happens, and Event Grid delivers a notification to everyone who cares.

That "something" is an event - a small message that says a change occurred:

  • A blob was uploaded
  • A resource was created
  • An order was placed.

Event Grid does not store your data or run your logic. It takes events from publishers and pushes them to subscribers, fast and at scale.

Here is the model:

Loading diagramโ€ฆ

Azure Event Grid consists of four parts:

  • Publishers - where events come from. Azure services like Blob Storage and Resource Groups emit system events automatically. Your own application publishes custom events to a custom topic.
  • Topic - the endpoint events are sent to. A system topic for Azure events, or a custom topic for your own.
  • Event subscriptions - the rules that connect a topic to a handler. Each subscription can be filtered by event type or subject, so a handler receives only the events it requested.
  • Event handlers - where events are delivered: an HTTP webhook, an Azure Function, a Service Bus queue, an Event Hub, and more.
Copied

How Event Grid Differs from Service Bus

The key difference is push versus pull.

Service Bus is a broker you pull from: your consumer connects, locks a message, processes it, and completes it. The consumer controls the pace.

Event Grid pushes: when an event occurs, Event Grid makes an HTTP call to your handler. You do not poll - you expose an endpoint and wait to be called.

A few more differences matter:

  • Reactive notifications, not message transport. An Event Grid event is a lightweight signal ("blob X was created"), not a payload meant to carry megabytes of data. Service Bus messages can carry the full payload your consumer needs.
  • No ordering, no sessions. Event Grid does not guarantee order. Service Bus offers FIFO ordering through sessions.
  • Retries and dead-lettering work differently. Event Grid retries a failed HTTP delivery with backoff for up to 24 hours, then dead-letters to a Storage account you configure. Service Bus has a built-in dead-letter sub-queue on every queue and subscription.
  • Serverless and pay-per-event. Event Grid has no namespace to provision or scale - you pay per operation. Service Bus runs in a provisioned namespace.

Use Event Grid when you want many parts of your system to react to something that happened, especially Azure resource changes.

Copied

Using Azure Event Grid in .NET

Working with Event Grid in .NET has two sides: publishing events and handling them.

First, install the following client NuGet package:

bash
dotnet add package Azure.Messaging.EventGrid
Copied

Publishing Events

To publish custom events, create an EventGridPublisherClient with your topic endpoint and key, then send one or multiple EventGridEvent objects:

csharp
var client = new EventGridPublisherClient( new Uri("https://my-topic.westeurope-1.eventgrid.azure.net/api/events"), new AzureKeyCredential(topicKey)); var orderPlaced = new EventGridEvent( subject: $"orders/{order.Id}", eventType: "Shop.OrderPlaced", dataVersion: "1.0", data: new OrderPlacedEvent(order.Id, order.Total, order.CustomerId)); await client.SendEventAsync(orderPlaced);

Each event carries:

  • subject - a path that describes what the event is about. Subscribers can filter on it.
  • eventType - the category of event, used for routing and filtering.
  • dataVersion - the schema version of your payload, so handlers can evolve safely.
  • data - your payload, serialized to JSON.

In production, never hard-code the topic key. Pull it from configuration, or use DefaultAzureCredential with a managed identity.

Note: Event Grid can also send and receive events in the standard CloudEvents 1.0 schema instead of its native EventGridEvent schema. Use CloudEvents when your events cross systems that expect that format.

Copied

Handling Events in a Webhook

Because Event Grid pushes over HTTP, a handler is just an endpoint that accepts a POST.

Before it delivers any events, Event Grid sends a one-time validation event to prove you own the endpoint. You must echo back the validation code.

Here is a minimal API endpoint that handles both the handshake and real events:

csharp
app.MapPost("/events", async (HttpRequest request) => { var events = EventGridEvent.ParseMany( await BinaryData.FromStreamAsync(request.Body)); foreach (var gridEvent in events) { if (gridEvent.TryGetSystemEventData(out var systemEvent)) { switch (systemEvent) { case SubscriptionValidationEventData validation: // Echo the code back to complete the handshake return Results.Ok(new SubscriptionValidationResponse { ValidationResponse = validation.ValidationCode }); case StorageBlobCreatedEventData blobCreated: await ProcessNewBlobAsync(blobCreated.Url); break; } } } return Results.Ok(); });

What happens here:

  • EventGridEvent.ParseMany deserializes the request body into one or more events - Event Grid can batch them.
  • TryGetSystemEventData recognizes built-in Azure events and gives you a strongly typed object.
  • On a SubscriptionValidationEventData, we return the ValidationCode. Event Grid sees it and activates the subscription.
  • On a StorageBlobCreatedEventData, we react - here, processing a blob that was just uploaded.

This is the canonical Event Grid scenario: a file lands in Blob Storage, Storage emits a BlobCreated event, and your service reacts with no polling at all.

Note: there is no first-party local emulator for Event Grid push delivery. To develop locally, expose your endpoint with a tunneling tool and point an event subscription at it, or run the Azure "Event Grid Viewer" sample to watch events arrive.

If you host on Azure Functions, the EventGridTrigger binding handles the validation handshake for you, so your function receives only real events:

csharp
[Function("OnBlobCreated")] public void Run([EventGridTrigger] EventGridEvent gridEvent) { // The validation handshake is handled by the binding }

A newer option, Event Grid Namespaces, flips the model to pull delivery and adds MQTT support, which suits IoT and high-volume scenarios.

It uses a different client, Azure.Messaging.EventGrid.Namespaces. The classic push model above is the right default for reacting to Azure and domain events.

Copied

Azure Event Hubs: How It Works

Azure Event Hubs is a big-data streaming platform built to ingest millions of events per second.

Where Service Bus moves discrete messages and Event Grid routes notifications, Event Hubs is a pipe for high-volume event streams - telemetry, logs, clickstreams, IoT signals, and metrics.

The core idea is an append-only log. Producers append events to the end, and consumers read forward from a position they track themselves.

Here is the model:

Loading diagramโ€ฆ

The building blocks are:

  • Partitions - the event hub is split into partitions, each an ordered log. More partitions mean more parallelism. Events with the same partition key always land in the same partition, which preserves their order.
  • Consumer groups - a named, independent view over the whole stream. A real-time dashboard and a batch analytics job each get their own consumer group and read the same data at their own pace.
  • Offsets and checkpoints - each event has an offset, its position in the partition. A consumer periodically saves a checkpoint so it can resume where it left off after a restart.
  • Retention - events stay in the hub for a configured window, from one day to several. Within that window, any consumer can replay them.
Copied

How Event Hubs Differ from Service Bus

Service Bus is a queue: messages are delivered, processed, and deleted. Event Hubs is a log: events are retained and can be read repeatedly by different consumers.

The differences follow from that:

  • Replay. Event Hubs lets you rewind and reprocess the stream within the retention window. A Service Bus message is gone once it is completed.
  • The consumer tracks position. With Event Hubs, the client owns its checkpoint. With Service Bus, the broker tracks each message with locks and delivery counts.
  • Ordering by partition. Event Hubs guarantees order within a partition. Service Bus guarantees order through sessions.
  • No per-message acknowledgement, no dead-letter queue. Event Hubs does not lock or dead-letter individual events. If your handler fails, you can either skip ahead or reprocess from a checkpoint.
  • Throughput. Event Hubs targets millions of events per second; Service Bus targets thousands of messages per second with richer per-message features.

Event Hubs also speaks the Apache Kafka protocol, so existing Kafka producers and consumers can connect to it with a connection-string change. If you have run Kafka pipelines, the model will feel familiar.

Copied

Using Azure Event Hubs in .NET

Like Service Bus, Event Hubs works cleanly with .NET Aspire, and it ships with a local emulator, so you can develop without an Azure subscription.

To set up Aspire from scratch, see Getting Started with .NET Aspire. The Aspire and azd deployment flow is the same one used in the Azure Service Bus article.

In the Aspire AppHost, add the Event Hubs resource and run it as an emulator:

bash
dotnet add package Aspire.Hosting.Azure.EventHubs
csharp
var builder = DistributedApplication.CreateBuilder(args); var eventHubs = builder.AddAzureEventHubs("event-hubs") .RunAsEmulator(); eventHubs.AddHub("telemetry"); builder.AddProject<Projects.Telemetry_Producer>("producer") .WithReference(eventHubs); builder.AddProject<Projects.Telemetry_Processor>("processor") .WithReference(eventHubs); builder.Build().Run();

RunAsEmulator() starts the Event Hubs emulator in Docker, and AddHub creates a hub named telemetry. Aspire injects the connection string into both services.

Copied

Publishing Events

In the producer service, register the client, then publish events in batches for throughput:

bash
dotnet add package Aspire.Azure.Messaging.EventHubs
csharp
builder.AddAzureEventHubProducerClient("event-hubs", settings => settings.EventHubName = "telemetry");
csharp
public class TelemetryPublisher(EventHubProducerClient client) { public async Task PublishAsync(IReadOnlyList<DeviceReading> readings) { using EventDataBatch batch = await client.CreateBatchAsync(); foreach (var reading in readings) { var json = JsonSerializer.Serialize(reading); if (!batch.TryAdd(new EventData(json))) { // Batch is full - send it and start a new one await client.SendAsync(batch); batch = await client.CreateBatchAsync(); batch.TryAdd(new EventData(json)); } } await client.SendAsync(batch); } }

Batching is what makes Event Hubs fast.

Instead of one network call per event, you pack many events into a single EventDataBatch and send them together. TryAdd returns false when the batch reaches its size limit, so we flush it and start a fresh one.

Copied

Consuming Events

To read the stream, use EventProcessorClient. It balances partitions across instances and stores checkpoints in Blob Storage, so several copies of your service share the work:

csharp
public class TelemetryProcessor( EventProcessorClient processor, ILogger<TelemetryProcessor> logger) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { processor.ProcessEventAsync += async args => { var json = args.Data.EventBody.ToString(); var reading = JsonSerializer.Deserialize<DeviceReading>(json); // ... process the reading await args.UpdateCheckpointAsync(args.CancellationToken); }; processor.ProcessErrorAsync += args => { logger.LogError(args.Exception, "Error on partition {PartitionId}", args.PartitionId); return Task.CompletedTask; }; await processor.StartProcessingAsync(stoppingToken); while (!stoppingToken.IsCancellationRequested) { } await processor.StopProcessingAsync(CancellationToken.None); } }

Two handlers drive the processor:

  • ProcessEventAsync fires for each event. We deserialize it, process it, then call UpdateCheckpointAsync to record our position. After a restart, the processor resumes from the last checkpoint instead of replaying everything.
  • ProcessErrorAsync fires on partition-level errors, like a lost lease.

Checkpointing on every single event is expensive.

In a high-throughput pipeline, checkpoint every few seconds or every N events, and accept that a restart may reprocess a small batch - which is exactly why your processing should be idempotent.

Note: EventProcessorClient needs a Blob Storage container to hold its checkpoints and partition leases. Aspire wires one up alongside the emulator locally; in production, it is a real Storage account.

Copied

Service Bus vs Event Hubs vs Event Grid: When to Use Each

The three services overlap just enough to be confusing. Here is how they line up side by side:

Azure Service BusAzure Event HubsAzure Event Grid
Primary purposeReliable message brokerHigh-throughput streamingReactive event routing
CommunicationPull (competing consumers)Pull (partitioned log)Push (HTTP to handlers)
PatternQueues, Topics/SubscriptionsPartitions + consumer groupsTopics + event subscriptions
ThroughputThousands/secMillions/secHigh, per-event
OrderingFIFO with sessionsPer partitionNone
Delivery / retriesAt-least-once, locks, DLQAt-least-once, checkpoints, no DLQAt-least-once, retry + dead-letter to Storage
ReplayNo (consume-and-delete)Yes (retention window)No
Max event size256 KB (Premium: 100 MB)1 MB1 MB
ProtocolsAMQP, HTTPAMQP, Kafka, HTTPHTTP, MQTT (Namespaces)
Pricing modelProvisioned namespaceThroughput / processing unitsPay per operation
Best forCommands and workflowsTelemetry, logs, analyticsReacting to Azure and domain events

The quickest way to choose is to ask what you actually need:

Loading diagramโ€ฆ
Copied

Azure Service Bus

Use it when you need reliable, ordered, transactional messaging between services - commands, workflows, and business events where every message matters and must not be lost.

Avoid it when you are ingesting massive event streams (Event Hubs is built for that), or simply reacting to Azure resource changes (Event Grid is simpler and cheaper).

Copied

Azure Event Hubs

Use it when you ingest high-volume telemetry, logs, metrics, or clickstream data and want multiple independent consumers plus the ability to replay.

Avoid it when you need per-message retries, dead-lettering, or guaranteed FIFO across the whole stream - those are Service Bus features.

Copied

Azure Event Grid

Use it when you want to react to events with minimal infrastructure - a blob is uploaded, a resource is created, or your app raises a domain event that several handlers should receive.

Avoid it when you need to carry large payloads, guarantee order, or pull at your own pace - reach for Service Bus or Event Hubs instead.

A few concrete scenarios make it click:

  • A checkout that must update stock, charge a payment, and notify the customer - Service Bus. Each step is a command that must not be lost, and order matters.
  • Tracking every page view and click across your site for a live dashboard - Event Hubs. Millions of events, multiple consumers, replayable.
  • Generating a thumbnail whenever a product image is uploaded - Event Grid. React to the Storage BlobCreated event, with no polling and nothing to run.

Many real systems use all three together: Event Grid reacts to infrastructure events, Event Hubs ingests the telemetry firehose, and Service Bus runs reliable business workflows.

Copied

Summary

Azure's three messaging services solve different problems.

Let's recap the key takeaways:

  • Azure Service Bus is the reliable broker. Use it for commands and workflows between services, where ordering, retries, and dead-lettering matter. You pull messages and complete them one by one.
  • Azure Event Hubs is the streaming pipe. Use it to ingest millions of events per second, with partitions, consumer groups, and replay. The consumer tracks its own position with checkpoints.
  • Azure Event Grid is the reactive router. Use it to push lightweight notifications to handlers when something happens, especially Azure resource changes. It is serverless and pay-per-event.
  • The one-line rule: react to events with Event Grid, stream events with Event Hubs, and process reliable messages with Service Bus.

There is no single best choice - the right service depends on whether you need reliable delivery, raw throughput, or simple reactivity. Many systems use all three, each for what it does best.


I am building a deep-dive System Design course.

It will be better than anything you have seen so far:

  • Reading
  • Watching videos
  • Designing real systems in the browser
  • Taking tests

Join the waitlist here to get early access and a launch discount.


Hope you find this newsletter useful. See you next time.

Whenever you're ready, here's how I can help you:

The .NET Senior Playbook is built to:

  • Fast-track you from junior or mid-level to senior
  • Keep you growing as a senior
  • Help you beat any .NET interview

Covers everything: C#, ASP.NET Core, EF Core, system design โ€” answer each question first, reveal the solution, and a test after every chapter proves it stuck. Finish, and you earn a verifiable certificate for your LinkedIn.

The .NET Senior Playbook
View the Playbook

Not sure where you stand? Take the free .NET Interview Run:

  • Find out your real level โ€” Junior to Senior+
  • A realistic mock .NET interview โ€” across 13 areas of C#, .NET, ASP.NET Core and System Design

No credit card required. When you finish, you get a personalized report: your level, your strongest and weakest areas, and where to focus next โ€” the perfect way to benchmark yourself before diving into the Playbook.

Start your free run

Enjoyed this article? Share it with your network

Improve Your .NET and Architecture Skills

Join my community of 25,000+ developers and architects.

Each week you will get 1 practical tip with best practices and real-world examples.

Learn how to craft better software with source code available for my newsletter.

Join 25,000+ developers already reading
No spam. Unsubscribe any time.