01What Is Azure Event Grid?
Azure Event Grid is a fully managed event routing service that enables reactive, event-driven architectures by connecting event sources to event handlers with near-zero latency. Unlike messaging systems that store data, Event Grid is a notification backbone — it tells subscribers that something happenedand where to find the details. Built on a serverless model, it scales to ~10 million events per second with no infrastructure to manage.
02Core Concepts & Terminology
Understanding Event Grid's vocabulary is essential before building event-driven systems. These terms define the building blocks — from the events themselves to the topics they flow through and the handlers that process them. Mastering this terminology helps you design clean subscription topologies and troubleshoot delivery issues quickly in production. Think of it as the mental model that maps directly to the Azure portal UI and CLI commands you'll use daily.
| Term | Definition |
|---|---|
| Event | The smallest unit of information — what happened. Max 1 MB. Includes subject, type, data, and metadata. |
| Event Source | The service or app that generated the event (Blob Storage, Key Vault, your custom app, etc.) |
| Topic | An endpoint where event sources publish events. Subscribers attach event subscriptions to topics. |
| System Topic | A built-in topic representing an Azure service (Storage account, Service Bus namespace, etc.) |
| Custom Topic | A user-created topic endpoint for publishing events from your own applications |
| Event Domain | A management container for thousands of related topics — for multi-tenant SaaS scenarios |
| Event Subscription | Defines where and how events from a topic are delivered. Has filters and a handler endpoint. |
| Event Handler | The destination that receives and processes events (Function App, Logic App, webhook, Service Bus, etc.) |
| Dead-Letter Destination | A Blob Storage container where undeliverable events are sent after retry exhaustion |
| Publisher | Any application or Azure service that sends events to a topic |
| Subscriber | Any application or service that creates an event subscription and receives events |
| Namespace (new) | The new resource model for Event Grid supporting pull delivery and MQTT — separate from classic topics |
03Event Grid Schema vs CloudEvents Schema
Event Grid supports two event envelope formats: the original Azure-proprietary schema and the CNCF CloudEvents 1.0 standard. Choosing the right schema impacts interoperability — CloudEvents lets your event handlers work across Azure, AWS, GCP, and open-source platforms like Knative and Dapr without modification. For new projects, always prefer CloudEvents; for legacy integrations already using the Event Grid schema, migration is straightforward but optional. The schema you choose is set at the topic level and determines how subscribers parse incoming payloads.
Event Grid Schema (Classic)
[
{
"id": "b4a8e5e2-1234-4c3f-a9b0-12345abc6789",
"topic": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/myaccount",
"subject": "/blobServices/default/containers/uploads/blobs/report.pdf",
"eventType": "Microsoft.Storage.BlobCreated",
"eventTime": "2026-05-04T10:30:00.000Z",
"dataVersion": "1.0",
"metadataVersion": "1",
"data": {
"api": "PutBlob",
"clientRequestId": "abc123",
"requestId": "def456",
"eTag": "0x8D4BCC2E4835CD0",
"contentType": "application/pdf",
"contentLength": 524288,
"blobType": "BlockBlob",
"url": "https://myaccount.blob.core.windows.net/uploads/report.pdf",
"sequencer": "00000000000004420000000000028963"
}
}
]CloudEvents Schema (Recommended)
{
"specversion": "1.0",
"type": "Microsoft.Storage.BlobCreated",
"source": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Storage/storageAccounts/myaccount",
"id": "b4a8e5e2-1234-4c3f-a9b0-12345abc6789",
"time": "2026-05-04T10:30:00.000Z",
"subject": "/blobServices/default/containers/uploads/blobs/report.pdf",
"datacontenttype": "application/json",
"data": {
"api": "PutBlob",
"url": "https://myaccount.blob.core.windows.net/uploads/report.pdf",
"contentType": "application/pdf",
"contentLength": 524288,
"blobType": "BlockBlob"
}
}| Dimension | Event Grid Schema | CloudEvents 1.0 |
|---|---|---|
| Standard | Azure-proprietary | CNCF open standard |
| Interoperability | Azure services only | Any CloudEvents-compatible system |
| Validation | Event Grid subscription validation handshake | HTTP 200 with no body required |
| Recommended for | Legacy integrations | All new architectures |
| SDK support | Azure.Messaging.EventGrid | Azure.Messaging.EventGrid + CloudNative.CloudEvents |
04Topics — System, Custom & Domain
Topics are the central routing endpoints in Event Grid — every event flows through a topic before reaching subscribers. System topics are automatically managed by Azure services (like Storage or Key Vault), custom topics are endpoints you create for your own application events, and event domains group thousands of topics under one management umbrella for multi-tenant scenarios. Choosing the right topic type determines your publishing model, access control boundaries, and how you scale your event architecture across teams and tenants.
System Topics
System topics are automatically available for Azure services. You create a system topic on an existing Azure resource — you don't publish to it yourself, the Azure service does.
| Azure Service | Event Types (examples) |
|---|---|
| Azure Blob Storage | BlobCreated, BlobDeleted, BlobRenamed, DirectoryCreated |
| Azure Service Bus | ActiveMessagesAvailableWithNoListeners, DeadletterMessagesAvailableWithNoListeners |
| Azure Key Vault | SecretNewVersionCreated, SecretNearExpiry, SecretExpired, CertificateNearExpiry |
| Azure Container Registry | ImagePushed, ImageDeleted, ChartPushed, ChartDeleted |
| Azure Event Hubs | CaptureFileCreated |
| Azure Maps | GeofenceEntered, GeofenceExited, GeofenceResult |
| Azure Resource Manager | ResourceWriteSuccess, ResourceDeleteSuccess, ResourceActionSuccess |
| Azure App Configuration | KeyValueModified, KeyValueDeleted |
| Azure SignalR | ClientConnectionConnected, ClientConnectionDisconnected |
| Azure Machine Learning | RunCompleted, ModelRegistered, DatasetDriftDetected |
| IoT Hub | DeviceTelemetry, DeviceCreated, DeviceDeleted, DeviceConnected |
| Azure Communication Services | SMSReceived, SMSDeliveryReportReceived, CallEnded |
Create System Topic via CLI
# Create a system topic for a Storage Account
az eventgrid system-topic create \
--resource-group myRG \
--name myStorageSystemTopic \
--location eastus \
--topic-type Microsoft.Storage.StorageAccounts \
--source /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.Storage/storageAccounts/mystorageaccountCustom Topics
Custom topics are endpoints you create and publish to from your own applications. You get an endpoint URL and an access key (or use Managed Identity).
# Create a custom topic
az eventgrid topic create \
--resource-group myRG \
--name order-events \
--location eastus \
--input-schema cloudeventschemav1_0 \
--public-network-access enabled05Event Handlers & Subscriptions
Event handlers are the destinations that receive and process events — they define what happens when an event arrives. Event Grid supports a wide range of handler types from serverless functions to message brokers, giving you flexibility to match each event to the right processing model. Subscriptions tie a handler to a topic with optional filters, creating a declarative routing rule that Event Grid enforces automatically. In production, you'll typically have multiple subscriptions per topic, each routing a subset of events to a specialized handler.
| Handler Type | Description | Best For |
|---|---|---|
| Azure Function | Serverless compute — scales automatically per event | Custom processing logic, transformations |
| Logic App | Low-code workflow orchestration | Multi-step integrations, approvals, SaaS connectors |
| Webhook (HTTPS) | Any HTTPS endpoint — your own API or third-party | Custom microservices, external systems |
| Service Bus Queue | Reliable message delivery with ordering and DLQ | Decoupled processing, reliable task dispatch |
| Service Bus Topic | Fan-out to multiple subscribers via topic filters | Multiple downstream consumers per event |
| Event Hub | High-throughput streaming of events into a hub | Analytics pipelines, aggregation |
| Storage Queue | Simple, durable queue for background tasks | Low-cost, simple task dispatch |
| Azure Relay | Bridge to on-premises endpoints | Hybrid cloud/on-prem event routing |
Create Event Subscription (Function Handler)
# Create subscription pointing to an Azure Function
az eventgrid event-subscription create \
--name blob-created-sub \
--source-resource-id /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.Storage/storageAccounts/mystorageaccount \
--endpoint-type azurefunction \
--endpoint /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.Web/sites/myFunctionApp/functions/ProcessBlob \
--included-event-types Microsoft.Storage.BlobCreated \
--subject-begins-with /blobServices/default/containers/uploads/ \
--deadletter-endpoint /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.Storage/storageAccounts/mydeadletter/blobServices/default/containers/deadletterCreate Event Subscription — ARM / Bicep
resource eventSubscription 'Microsoft.EventGrid/eventSubscriptions@2022-06-15' = {
name: 'order-created-subscription'
scope: customTopic
properties: {
destination: {
endpointType: 'ServiceBusTopic'
properties: {
resourceId: serviceBusTopic.id
}
}
filter: {
includedEventTypes: [ 'Orders.Created', 'Orders.Updated' ]
subjectBeginsWith: '/orders/'
advancedFilters: [
{
operatorType: 'NumberGreaterThan'
key: 'data.amount'
value: 100
}
]
}
deadLetterDestination: {
endpointType: 'StorageBlob'
properties: {
resourceId: storageAccount.id
blobContainerName: 'deadletter'
}
}
retryPolicy: {
maxDeliveryAttempts: 30
eventTimeToLiveInMinutes: 1440
}
eventDeliverySchema: 'CloudEventSchemaV1_0'
}
}06Event Filtering & Routing
Event filtering is what makes Event Grid a smart router rather than a dumb pipe — each subscription can declare exactly which events it wants based on type, subject path, or deep JSON properties in the event data. This server-side filtering means your handlers only receive relevant events, reducing compute costs and simplifying handler logic. In production, effective filtering is the difference between a clean architecture and a noisy one where every handler must discard irrelevant events. Combine subject prefix filters with advanced property filters to create precise, cost-efficient routing rules.
Filter Types
| Filter Type | Property | Description |
|---|---|---|
| Event Type Filter | includedEventTypes | Only deliver specific event types (e.g. BlobCreated) |
| Subject Begins With | subjectBeginsWith | Filter by subject prefix — e.g. /containers/uploads/ |
| Subject Ends With | subjectEndsWith | Filter by subject suffix — e.g. .pdf or .jpg |
| Advanced — StringContains | data.* property | Filter on a JSON property inside the event data |
| Advanced — NumberGreaterThan | data.* property | Numeric comparison on event data properties |
| Advanced — BoolEquals | data.* property | Boolean match on event data properties |
| Advanced — StringIn | data.* property | Value must be in a specified list |
| Advanced — IsNullOrUndefined | data.* property | Property is absent or null |
Advanced Filters — JSON
{
"filter": {
"includedEventTypes": ["Orders.Created"],
"subjectBeginsWith": "/orders/",
"advancedFilters": [
{
"operatorType": "StringIn",
"key": "data.region",
"values": ["EU-West", "EU-North"]
},
{
"operatorType": "NumberGreaterThanOrEquals",
"key": "data.amount",
"value": 500
},
{
"operatorType": "BoolEquals",
"key": "data.isPriority",
"value": true
},
{
"operatorType": "StringNotContains",
"key": "data.status",
"values": ["cancelled", "refunded"]
}
]
}
}07Delivery, Retry & Dead-Letter
Event Grid guarantees at-least-once delivery with a sophisticated retry mechanism that uses exponential backoff over a configurable window (default 24 hours, up to 30 attempts). Understanding the retry and dead-letter behavior is critical for production reliability — without proper dead-letter configuration, failed events are silently dropped after retry exhaustion. The retry policy distinguishes between transient errors (retried) and permanent client errors (dropped immediately), so your handlers should return appropriate HTTP status codes to signal whether a retry would help.
Retry Schedule
Event Grid retries delivery with an exponential backoff with jitter strategy. The total retry window is configurable (default 24 hours) and the max delivery attempts are also configurable (default 30).
| Attempt | Retry Delay (approx) |
|---|---|
| 1st retry | 10 seconds |
| 2nd retry | 30 seconds |
| 3rd retry | 1 minute |
| 4th retry | 5 minutes |
| 5th retry | 10 minutes |
| 6th retry | 30 minutes |
| 7th+ retry | 1 hour (max interval) |
| After max attempts or TTL | Dead-letter (if configured) or drop |
Events Are Dropped (Not Retried) When
| Condition | Behavior |
|---|---|
| Handler returns 400 Bad Request | Dropped immediately — not retried (client error) |
| Handler returns 413 Payload Too Large | Dropped immediately |
| Handler returns 403 Forbidden | Dropped immediately |
| Handler returns 404 Not Found | Dropped — subscription endpoint invalid |
| Handler returns 503 / 500 | Retried with backoff — transient error |
| Handler returns 429 Too Many Requests | Retried — respects Retry-After header |
| TTL expired (default 24h) | Dead-lettered or dropped |
| Max delivery attempts reached | Dead-lettered or dropped |
Dead-Letter Configuration
# Add dead-letter storage to an existing subscription
az eventgrid event-subscription update \
--name my-subscription \
--source-resource-id <topic-resource-id> \
--deadletter-endpoint \
/subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.Storage/storageAccounts/mydeadletter/blobServices/default/containers/dead-events
# Dead-letter blob path format:
# <container>/<topic-name>/<subscription-name>/<year>/<month>/<day>/<hour>/<eventId>.jsonDeadLetteredCount metric.08Push Delivery vs Pull Delivery
Classic Event Grid uses push delivery — Event Grid calls your handler endpoint. The new Event Grid Namespaces resource introduces native pull delivery — your consumer requests events on its own schedule, like Service Bus. This is ideal when your consumer is behind a firewall or needs full flow control.
| Dimension | Push Delivery (Classic) | Pull Delivery (Namespace) |
|---|---|---|
| Model | Event Grid calls handler webhook | Consumer polls Event Grid for events |
| Consumer location | Must be publicly reachable | Can be behind firewall / VNet |
| Flow control | Event Grid controls pace | Consumer controls pace |
| Backpressure | Not supported — can overwhelm handler | Full consumer-side control |
| Lock model | No locking | Lock, acknowledge, reject, release |
| Competing consumers | Not supported (fan-in) | Supported — multiple consumers share queue |
| Handler types | Function, Logic App, SB, webhook, etc. | Any consumer via SDK / REST |
| Max event size | 1 MB | 1 MB |
| Latency | Sub-second | Depends on poll interval |
| Resource type | topics / systemTopics | namespaces / topics / eventSubscriptions |
Pull Delivery — Receive & Acknowledge
using Azure.Messaging.EventGrid.Namespaces;
var client = new EventGridReceiverClient(
new Uri("https://<namespace>.eastus-1.eventgrid.azure.net"),
"<topic-name>",
"<subscription-name>",
new DefaultAzureCredential());
// Receive a batch of events (with lock — must acknowledge within lock duration)
ReceiveResult result = await client.ReceiveAsync(maxEvents: 10, maxWaitTime: TimeSpan.FromSeconds(30));
var lockTokens = new List<string>();
foreach (ReceiveDetails detail in result.Value)
{
CloudEvent cloudEvent = detail.Event;
BrokerProperties broker = detail.BrokerProperties;
Console.WriteLine($"Event: {cloudEvent.Type}, LockToken: {broker.LockToken}");
// Process the event...
bool success = await ProcessEventAsync(cloudEvent);
if (success)
lockTokens.Add(broker.LockToken); // Acknowledge
else
await client.ReleaseAsync(new[] { broker.LockToken }); // Return to queue
}
// Acknowledge successfully processed events
if (lockTokens.Any())
await client.AcknowledgeAsync(lockTokens);09Security & Authentication
Securing Event Grid involves two sides: authenticating publishers who send events to topics, and validating that Event Grid can legitimately deliver events to your handler endpoints. In production, always use Managed Identity or Entra ID service principals instead of access keys — keys are shared secrets that are difficult to rotate and easy to leak. The webhook validation handshake ensures Event Grid only delivers to endpoints you actually own, preventing unauthorized subscriptions from exfiltrating your events to attacker-controlled URLs.
Publishing Authentication
| Method | Use Case | Recommended? |
|---|---|---|
| Access Key (SAS) | Quick dev/test publishing | ⚠️ Avoid in production |
| SAS Token (time-limited) | Short-lived publishing access | ✓ Acceptable |
| Managed Identity (Entra ID) | App Service, Functions, AKS publishing | ✅ Recommended |
| Service Principal (Entra ID) | CI/CD pipelines | ✅ Recommended |
Webhook Validation Handshake
When you create a subscription pointing to a webhook, Event Grid sends a validation event. Your endpoint must respond with thevalidationCode within 5 minutes or the subscription creation fails.
// ASP.NET Core webhook handler with validation
[HttpPost("webhook")]
public async Task<IActionResult> ReceiveEvent()
{
string requestContent = await new StreamReader(Request.Body).ReadToEndAsync();
// CloudEvents uses HTTP OPTIONS for handshake — different from Event Grid schema
if (Request.Headers["WebHook-Request-Origin"].Count > 0)
{
// CloudEvents webhook validation
Response.Headers["WebHook-Allowed-Origin"] = Request.Headers["WebHook-Request-Origin"].ToString();
return Ok();
}
// Event Grid schema validation
var events = JsonSerializer.Deserialize<JsonElement[]>(requestContent);
foreach (var evt in events!)
{
if (evt.GetProperty("eventType").GetString() == "Microsoft.EventGrid.SubscriptionValidationEvent")
{
var validationCode = evt.GetProperty("data")
.GetProperty("validationCode").GetString();
return Ok(new { validationResponse = validationCode });
}
// Process real events
await HandleEventAsync(evt);
}
return Ok();
}RBAC Roles for Event Grid
| Role | Permissions |
|---|---|
EventGrid Data Sender | Publish events to a topic |
EventGrid Data Receiver | Pull events from namespace subscriptions |
EventGrid Contributor | Manage Event Grid resources (control plane) |
EventGrid EventSubscription Contributor | Create and manage event subscriptions |
EventGrid EventSubscription Reader | Read event subscription properties |
10Network Security & Private Endpoints
For enterprise deployments, Event Grid supports private endpoints and IP firewall rules to ensure events never traverse the public internet. Private endpoints allow publishers inside your VNet to send events through Azure's backbone network, and managed identity-based delivery ensures handlers receive events securely without exposing public webhook URLs. This is essential for compliance-sensitive workloads in healthcare, finance, and government where data must stay within your network boundary. Note that system topics have limited networking options compared to custom topics and namespaces.
| Feature | Custom Topics | System Topics | Namespaces |
|---|---|---|---|
| IP Firewall Rules | ✓ Yes | ✗ No | ✓ Yes |
| Private Endpoints (inbound publishing) | ✓ Yes | ✗ No | ✓ Yes |
| Private Endpoints (pull delivery) | N/A | N/A | ✓ Yes |
| Managed Identity for delivery | ✓ Yes | ✓ Yes | ✓ Yes |
| VNet peering for webhook delivery | Via App Service VNet | Via App Service VNet | ✓ Yes |
Private Endpoint for Custom Topic (Publishing)
# Create private endpoint so publishers inside VNet can publish securely
az network private-endpoint create \
--name egrid-publish-pe \
--resource-group myRG \
--vnet-name myVNet \
--subnet mySubnet \
--private-connection-resource-id \
/subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.EventGrid/topics/order-events \
--group-ids topic \
--connection-name egrid-private-conn
# Private DNS zone for Event Grid
az network private-dns zone create \
--resource-group myRG \
--name "privatelink.eventgrid.azure.net"11Logic Apps Integration
Logic Apps provides a low-code way to subscribe to Event Grid events and orchestrate multi-step workflows in response — no custom code required. The Event Grid trigger connector automatically handles the webhook validation handshake and subscription lifecycle, making it the fastest path from event to action for scenarios like sending Teams alerts, creating tickets, or calling external APIs. This integration shines for operations teams who need to react to infrastructure events (like Key Vault secret expiry or resource changes) without deploying custom functions. Use Logic Apps when the response involves multiple steps, conditional branching, or connectors to SaaS services.
| Connector | Direction | Description |
|---|---|---|
| When an Event Grid event occurs | Trigger | Subscribe to any system or custom topic event type |
| Publish event | Action | Publish a custom event to an Event Grid topic |
Event Grid Trigger in Logic App
{
"triggers": {
"When_a_resource_event_occurs": {
"type": "ApiConnectionWebhook",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['azureeventgrid']['connectionId']"
}
},
"path": "/subscriptions/@{encodeURIComponent('<sub-id>')}/providers/@{encodeURIComponent('Microsoft.KeyVault.vaults')}/resource/eventSubscriptions",
"body": {
"properties": {
"destination": {
"endpointType": "webhook",
"properties": { "endpointUrl": "@{listCallbackUrl()}" }
},
"filter": {
"includedEventTypes": [
"Microsoft.KeyVault.SecretNearExpiry",
"Microsoft.KeyVault.SecretExpired"
]
}
}
}
}
}
},
"actions": {
"Send_Teams_Alert": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "@parameters('teamsWebhookUrl')",
"body": {
"text": "🔑 Key Vault Alert: @{triggerBody()?['subject']} — @{triggerBody()?['eventType']}"
}
}
}
}
}12Function Apps Integration
Azure Functions is the most popular handler for Event Grid events — the Event Grid trigger binding gives you automatic scaling, built-in retry handling, and zero infrastructure management. Functions can both consume events (via trigger bindings) and produce events (via output bindings), making them ideal for event transformation, enrichment, and fan-out scenarios. In production, use the isolated worker model (.NET 8+) for better dependency injection, middleware support, and process isolation. The trigger supports both Event Grid schema and CloudEvents, so your function code stays clean regardless of the envelope format.
Event Grid Trigger (Isolated Worker — .NET 8)
using Azure.Messaging.EventGrid;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class BlobEventProcessor
{
private readonly ILogger<BlobEventProcessor> _logger;
public BlobEventProcessor(ILogger<BlobEventProcessor> logger) => _logger = logger;
[Function("ProcessBlobEvent")]
public async Task Run(
[EventGridTrigger] EventGridEvent eventGridEvent)
{
_logger.LogInformation("Event: {Type}, Subject: {Subject}",
eventGridEvent.EventType, eventGridEvent.Subject);
if (eventGridEvent.EventType == "Microsoft.Storage.BlobCreated")
{
var data = eventGridEvent.Data!.ToObjectFromJson<BlobCreatedData>();
_logger.LogInformation("Blob URL: {Url}, Size: {Size}", data.Url, data.ContentLength);
await ProcessNewBlobAsync(data.Url);
}
}
}
// CloudEvents trigger
public class OrderEventProcessor
{
[Function("ProcessOrderEvent")]
public async Task Run(
[EventGridTrigger] CloudEvent cloudEvent,
ILogger log)
{
log.LogInformation("CloudEvent type: {Type}", cloudEvent.Type);
var order = cloudEvent.Data!.ToObjectFromJson<OrderCreatedData>();
await RouteOrderAsync(order);
}
}
public record BlobCreatedData(string Url, string ContentType, long ContentLength, string BlobType);
public record OrderCreatedData(string OrderId, decimal Amount, string Region, bool IsPriority);Publish Events from a Function
[Function("CreateOrder")]
[EventGridOutput(TopicEndpointUri = "ORDER_EVENTS_ENDPOINT", TopicKeySetting = "ORDER_EVENTS_KEY")]
public static EventGridEvent Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req)
{
var order = JsonSerializer.Deserialize<Order>(req.Body);
return new EventGridEvent(
subject: $"/orders/{order.OrderId}",
eventType: "Orders.Created",
dataVersion: "1.0",
data: order);
}
// CloudEvents output binding
[Function("CreateOrderCloudEvent")]
public async Task<CloudEvent> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req)
{
var order = await req.ReadFromJsonAsync<Order>();
return new CloudEvent(
source: "/myapp/orders",
type: "Orders.Created",
jsonSerializableData: order)
{
Subject = $"/orders/{order!.OrderId}",
Id = Guid.NewGuid().ToString()
};
}13Service Bus Integration
Routing Event Grid events into Service Bus queues or topics is one of the most powerful enterprise patterns — you get Event Grid's real-time push routing combined withService Bus's reliable delivery, DLQ, sessions, and ordered processing.
# Route Event Grid events to a Service Bus topic
az eventgrid event-subscription create \
--name order-to-servicebus \
--source-resource-id <custom-topic-id> \
--endpoint-type servicebustopic \
--endpoint /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.ServiceBus/namespaces/<namespace>/topics/<topic-name> \
--included-event-types Orders.Created Orders.Updated \
--subject-begins-with /orders/| Pattern | When to Use |
|---|---|
| Event Grid → Service Bus Queue | Reliable task dispatch for competitive consumers. One processor gets each order. |
| Event Grid → Service Bus Topic | Fan-out with filtering. Billing, shipping, analytics all get independent copies. |
| Event Grid → Service Bus + Sessions | Ordered processing per entity (e.g. all events for order-456 in sequence). |
| Event Grid → Function App → Service Bus | Transform / enrich events before queuing — add correlation IDs, normalize schema. |
14Event Hubs Integration
Routing Event Grid events into Event Hubs is ideal when you need to feed events into a stream analytics pipeline or need high-throughput event aggregation from multiple sources into a single stream.
# Route to Event Hub for stream analytics ingestion
az eventgrid event-subscription create \
--name resource-changes-to-eventhub \
--source-resource-id /subscriptions/<sub> \
--endpoint-type eventhub \
--endpoint /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.EventHub/namespaces/<namespace>/eventhubs/<hub-name> \
--included-event-types Microsoft.Resources.ResourceWriteSuccess \
Microsoft.Resources.ResourceDeleteSuccess15Blob Storage Integration
Blob Storage system topics are the most commonly used Event Grid integration. Instead of polling Storage with Logic Apps (1-min delay), events are pushed instantly when blobs are created, deleted, or renamed.
| Event Type | Trigger Condition |
|---|---|
| Microsoft.Storage.BlobCreated | A blob is uploaded (PutBlob, PutBlockList, CopyBlob, FlushWithClose) |
| Microsoft.Storage.BlobDeleted | A blob is deleted (DeleteBlob or when soft-deleted blob is permanently removed) |
| Microsoft.Storage.BlobRenamed | A blob in ADLS Gen2 is renamed (HierarchicalNamespace enabled) |
| Microsoft.Storage.DirectoryCreated | A directory in ADLS Gen2 is created |
| Microsoft.Storage.DirectoryDeleted | A directory in ADLS Gen2 is deleted |
| Microsoft.Storage.BlobTierChanged | A blob's access tier is changed (Hot → Cool → Archive) |
Pattern: Blob Upload → Process → Store Result
// Function triggered by blob-created event from Event Grid
[Function("ProcessUploadedDocument")]
public async Task Run(
[EventGridTrigger] EventGridEvent eventGridEvent,
[BlobInput("{data.url}", Connection = "StorageConnection")] Stream blobStream)
{
if (eventGridEvent.EventType != "Microsoft.Storage.BlobCreated") return;
var data = eventGridEvent.Data!.ToObjectFromJson<StorageBlobCreatedEventData>();
// Only process PDFs from the 'uploads' container
if (!data.Url.Contains("/uploads/") || data.ContentType != "application/pdf")
return;
_logger.LogInformation("Processing uploaded PDF: {Url}", data.Url);
// Call Azure Document Intelligence
var result = await _documentClient.AnalyzeDocumentAsync(blobStream);
// Store extracted data to Cosmos DB
await _cosmosContainer.UpsertItemAsync(new
{
id = data.BlobUrl.Split('/').Last(),
extractedText = result.Content,
uploadedAt = eventGridEvent.EventTime,
blobUrl = data.Url
});
}16Publishing Custom Events
Publishing custom events lets your own applications participate in the Event Grid ecosystem alongside Azure services. You send events to a custom topic endpoint using the SDK, REST API, or output bindings, and Event Grid handles routing, filtering, and delivery to all subscribers. This is the foundation of domain event patterns in microservices — each service publishes business events (OrderCreated, PaymentProcessed) and other services react without direct coupling. Always use Managed Identity for authentication in production, batch events when publishing at high volume, and prefer the CloudEvents format for portability.
Publish via SDK — .NET
using Azure.Messaging.EventGrid;
using Azure;
// With access key
var client = new EventGridPublisherClient(
new Uri("https://order-events.<region>.eventgrid.azure.net/api/events"),
new AzureKeyCredential("<topic-key>"));
// With Managed Identity (recommended)
var client = new EventGridPublisherClient(
new Uri("https://order-events.<region>.eventgrid.azure.net/api/events"),
new DefaultAzureCredential());
// Publish Event Grid schema events
var events = new List<EventGridEvent>
{
new EventGridEvent(
subject: "/orders/ORD-12345",
eventType: "Orders.Created",
dataVersion: "1.0",
data: new
{
orderId = "ORD-12345",
amount = 299.99,
region = "EU-West",
customer = new { id = "C001", email = "user@example.com" }
}),
new EventGridEvent(
subject: "/orders/ORD-12346",
eventType: "Orders.Created",
dataVersion: "1.0",
data: new { orderId = "ORD-12346", amount = 149.00, region = "US-East" })
};
await client.SendEventsAsync(events);
// Publish CloudEvents (recommended)
var cloudEvents = new List<CloudEvent>
{
new CloudEvent(
source: "/myapp/orders",
type: "Orders.Created",
jsonSerializableData: new { orderId = "ORD-12347", amount = 99.99 })
{
Subject = "/orders/ORD-12347",
Id = Guid.NewGuid().ToString(),
Time = DateTimeOffset.UtcNow
}
};
await client.SendCloudEventsAsync(cloudEvents);Publish via REST API
# Publish a CloudEvent via curl
curl -X POST https://order-events.<region>.eventgrid.azure.net/api/events \
-H "Content-Type: application/cloudevents+json" \
-H "aeg-sas-key: <topic-access-key>" \
-d '{
"specversion": "1.0",
"type": "Orders.Created",
"source": "/myapp/orders",
"id": "unique-event-id-1",
"time": "2026-05-04T10:00:00Z",
"subject": "/orders/ORD-12345",
"datacontenttype": "application/json",
"data": { "orderId": "ORD-12345", "amount": 299.99 }
}'17Event Domains (Multi-tenant)
An Event Domain is a single management endpoint that contains thousands of topics— one per tenant or per entity. Instead of creating individual topics for each customer, you publish to one domain endpoint with a topic name, and each customer subscribes only to their own topic. Perfect for SaaS platforms.
// Publish to a domain — topic name is per-tenant
var domainClient = new EventGridPublisherClient(
new Uri("https://my-domain.<region>.eventgrid.azure.net/api/events"),
new DefaultAzureCredential());
// Include topic in the event — routes to tenant's topic automatically
var events = new[]
{
new EventGridEvent(
subject: "/invoices/INV-001",
eventType: "Invoice.Created",
dataVersion: "1.0",
data: new { invoiceId = "INV-001", amount = 500.0 })
{
// Topic routes to tenant-a's topic within the domain
Topic = "tenant-a"
},
new EventGridEvent(
subject: "/invoices/INV-002",
eventType: "Invoice.Created",
dataVersion: "1.0",
data: new { invoiceId = "INV-002", amount = 750.0 })
{
Topic = "tenant-b"
}
};
await domainClient.SendEventsAsync(events);| Feature | Detail |
|---|---|
| Max topics per domain | 100,000 |
| Topics auto-created | Yes — topic is created on first event publish |
| Per-topic access keys | Yes — each tenant gets scoped access to only their topic |
| Event subscriptions | Created per topic within the domain |
| Use case | SaaS: one domain per product, one topic per customer/tenant |
18Event Grid vs Service Bus vs Event Hubs
One of the most common architecture questions in Azure is when to use Event Grid versus Service Bus versus Event Hubs — they serve fundamentally different purposes despite all handling "events" or "messages." Event Grid is a reactive notification router (something happened), Service Bus is a reliable message broker (do this task), and Event Hubs is a high-throughput stream ingestion platform (capture everything). In practice, production systems often combine all three: Event Grid triggers the workflow, Service Bus ensures reliable task processing, and Event Hubs captures the full event stream for analytics and replay.
| Dimension | Event Grid | Service Bus | Event Hubs |
|---|---|---|---|
| Primary purpose | Reactive event routing — notify that something happened | Reliable message brokering — task dispatch | High-throughput stream ingestion |
| Model | Push-based routing (pub/sub) | Queue or topic (P2P or pub/sub) | Partitioned log — consumers pull |
| Throughput | ~10M events/sec | ~1M messages/sec (Premium) | Millions/sec |
| Retention | 24h (retry), then DLQ | Up to 14 days | 1–90 days |
| Message ordering | No guarantee | FIFO with sessions | Per-partition ordering |
| Replay | ✗ No | ✗ No | ✓ Yes |
| Dead-letter | Blob Storage | Native DLQ sub-queue | ✗ No |
| Max event/msg size | 1 MB | 256 KB / 100 MB (Premium) | 1 MB |
| Competing consumers | ✗ No | ✓ Yes | Via consumer groups |
| Fan-out | Multiple subscriptions | Multiple topic subscriptions | Multiple consumer groups |
| Kafka compatible | ✗ No | ✗ No | ✓ Yes |
| Use when | Reacting to Azure resource or app events | Reliable task queues, workflows | IoT, telemetry, clickstreams |
19Monitoring & Diagnostics
Monitoring Event Grid in production means tracking the full event lifecycle — from publish success through delivery to dead-lettering. Azure Monitor exposes key metrics that tell you whether events are flowing correctly, and diagnostic logs give you per-event visibility when things go wrong. Set up alerts on DeadLetteredCount and DeliveryFailureCount as your first line of defense — these indicate events that your handlers never processed. Use KQL queries in Log Analytics to investigate delivery failures, identify problematic subscriptions, and track event latency trends over time.
| Metric | Description | Alert Condition |
|---|---|---|
| PublishSuccessCount | Successfully published events | Drop = publisher issue |
| PublishFailCount | Failed publish attempts | > 0 — alert immediately |
| DeliverySuccessCount | Successfully delivered to handlers | Low vs publish count = handler issue |
| DeliveryFailureCount | Delivery failures to handlers | > 0 — alert |
| DeadLetteredCount | Events sent to dead-letter destination | > 0 — alert immediately |
| DroppedEventCount | Events dropped (no DLQ configured) | > 0 — configure DLQ |
| MatchedEventCount | Events matching at least one subscription | Compare to published count |
| UnmatchedEventCount | Events with no matching subscription | > 0 — check filter config |
KQL — Dead-Lettered Events in Last 24h
AzureDiagnostics
| where ResourceType == "TOPICS" or ResourceType == "SYSTEMTOPICS"
| where OperationName == "DeadLetterEvent"
| where TimeGenerated > ago(24h)
| project TimeGenerated, Resource, subject_s, eventType_s,
deadLetterReason_s, deliveryAttemptCount_d
| order by TimeGenerated descKQL — Delivery Failure Analysis
AzureMetrics
| where ResourceType == "MICROSOFT.EVENTGRID/TOPICS"
| where MetricName in ("DeliveryFailureCount", "DeadLetteredCount")
| where TimeGenerated > ago(1h)
| summarize TotalFailures = sum(Total) by bin(TimeGenerated, 5m), MetricName, Resource
| where TotalFailures > 0
| order by TimeGenerated desc20Architecture Patterns
Event Grid enables a variety of proven architecture patterns for event-driven systems — from simple file-processing triggers to complex multi-service choreography and saga orchestration. These patterns represent battle-tested approaches used in production by organizations ranging from startups to enterprises. The key insight is that Event Grid acts as the connective tissue between services, eliminating direct dependencies and enabling each component to evolve independently. Choose the pattern that matches your reliability, latency, and scalability requirements.
21Pricing Overview
Event Grid follows a pure consumption-based pricing model with a generous free tier — the first 100,000 operations per month cost nothing, making it essentially free for development and low-volume production workloads. An "operation" includes publishing, delivery attempts, and filter evaluations, so efficient filtering directly reduces your bill. For high-volume scenarios processing millions of events daily, costs remain remarkably low compared to always-on compute alternatives. The key cost optimization lever is minimizing unnecessary filter evaluations by using specific event type filters and subject prefixes.
| Resource | Price (approx) |
|---|---|
| First 100,000 operations/month | Free |
| Operations (publish + delivery + filter) | ~$0.60 per million operations |
| Advanced filter evaluations | ~$0.60 per million filter evaluations |
| Dead-letter storage | Standard Azure Blob Storage pricing |
| Event Grid Namespaces (pull) | ~$0.40 per million operations |
| MQTT messaging (Namespace) | ~$0.40 per million messages |
includedEventTypes filters to avoid evaluating events that will never match. Use subject filters to reduce the event volume reaching subscriptions. Batch events in custom topics where possible.22Quick Reference Cheat Sheet
This cheat sheet consolidates the most frequently needed Event Grid endpoints, CLI commands, and service limits into a single quick-reference section. Keep this handy during development and troubleshooting — it covers the exact URL patterns for publishing, the CLI commands you'll run most often, and the hard limits that constrain your architecture decisions. Bookmark this section for those moments when you need to quickly look up a dead-letter path format or check the maximum number of subscriptions per topic.
# Custom Topic endpoint
https://<topic-name>.<region>.eventgrid.azure.net/api/events
# Event Domain endpoint
https://<domain-name>.<region>.eventgrid.azure.net/api/events
# Namespace (pull delivery)
https://<namespace>.<region-key>.eventgrid.azure.net
# Dead-letter blob path
<container>/<topic>/<subscription>/<year>/<month>/<day>/<hour>/<eventId>.json# Create custom topic (CloudEvents)
az eventgrid topic create -g myRG -n my-topic -l eastus \
--input-schema cloudeventschemav1_0
# Create system topic for Storage
az eventgrid system-topic create -g myRG -n my-storage-topic \
--topic-type Microsoft.Storage.StorageAccounts \
--source <storage-account-resource-id>
# Create subscription to Function App
az eventgrid event-subscription create \
--name my-sub --source-resource-id <topic-id> \
--endpoint-type azurefunction --endpoint <function-resource-id> \
--included-event-types MyApp.OrderCreated
# List subscriptions on a topic
az eventgrid event-subscription list --source-resource-id <topic-id>
# Test delivery (send a test event)
az eventgrid topic event-subscription show --name my-sub --topic-name my-topic -g myRG
# View dead-letter events
az storage blob list --account-name mydeadletter --container-name dead-events| Limit | Value |
|---|---|
| Max event size | 1 MB |
| Max batch size | 1 MB total |
| Max events per batch | Unlimited (constrained by batch size) |
| Max subscriptions per topic | 500 |
| Max advanced filters per subscription | 25 |
| Max filter values per advanced filter | 25 |
| Delivery retry window | 24 hours (configurable) |
| Max delivery attempts | 30 (configurable) |
| Max topics per domain | 100,000 |
| Max domains per subscription | 100 |
| Throughput (events/sec) | ~10 million |