What is Azure Event Grid?
Azure Event Grid is a fully managed event routing service that enables you to build reactive, event-driven applications using a publish-subscribe model. It acts as a central nervous system for your cloud infrastructure, efficiently distributing notifications from various sources to interested subscribers.
At its core, Event Grid receives events from event sources (publishers) and routes them to event handlers (subscribers). An event is simply a lightweight notification containing information about something that happened in your system — a blob being created, a resource being updated, or a custom application event being raised.
Event Grid supports two fundamental event models:
- Event Grid events — The classic model where events contain a standard schema with properties like
subject,eventType,data, andeventTime - CloudEvents v1.0 — An open standard, CNCF-backed specification that provides a common format for event description across platforms
Key Characteristics of Event Grid
- Massively scalable — Handles millions of events per second with near real-time delivery
- Serverless — No infrastructure to manage; you pay only for what you consume
- Built-in retry logic — Automatically retries delivery with exponential backoff for up to 24 hours
- Dead-lettering support — Undeliverable events can be routed to storage accounts for later inspection
- Advanced filtering — Subscribers receive only the events they care about based on type, subject, or data attributes
- 24-hour event batching — Events are buffered and delivered in configurable batches to optimize downstream processing
Why Event Grid Matters
🚀 Deploy your AI agent in 10 minutes
Managed Hermes hosting. Zero DevOps. 100M tokens/mo included.
Try it free →Traditional integration patterns often rely on polling — constantly checking whether something has changed. This creates a trade-off between responsiveness and cost: poll too frequently and you waste compute resources; poll too infrequently and you miss timely reactions to changes.
Event Grid fundamentally shifts this paradigm from polling to pushing. Instead of asking "has anything changed?" repeatedly, you declare "tell me when something changes." This brings several critical advantages:
Reactive Architecture at Cloud Scale
Event Grid enables true event-driven microservices. When a storage blob is uploaded, a Logic App can trigger a processing pipeline. When a resource group is modified, a Function App can audit the change. All of this happens automatically, without any polling overhead. The result is lower latency, reduced compute costs, and simpler codebases that focus on reaction rather than detection.
Decoupling Publishers from Subscribers
With Event Grid, the source system never needs to know about the destination. A storage account simply raises a "blob created" event. Whether that event is handled by one function or fifty functions — the storage account doesn't care. This decoupling allows teams to independently add, remove, or modify subscribers without touching the source system. It's a powerful pattern for large organizations where multiple teams consume the same events for different purposes.
Built-in Azure Service Integration
Event Grid comes with native support for over 30 Azure services as event sources, including Blob Storage, Azure Key Vault, Azure Container Registry, IoT Hub, and Service Bus. This means you can set up sophisticated event-driven workflows with minimal configuration, leveraging the platform's deep integration rather than building custom plumbing.
Dead-Letter and Retry Guarantees
Production systems must handle failure gracefully. Event Grid provides configurable retry policies (up to 24 hours with exponential backoff) and automatic dead-letter routing to storage accounts. If your event handler is temporarily unavailable — perhaps during a deployment or a transient network issue — Event Grid will keep trying. If it ultimately fails, the event is preserved in dead-letter storage for forensic analysis. This gives you confidence that no event is silently lost.
How to Set Up and Configure Event Grid
Let's walk through a complete setup, from creating an Event Grid topic to publishing events and configuring subscribers with advanced filtering. We'll cover both Azure portal steps and infrastructure-as-code approaches.
Step 1: Create an Event Grid Topic
An Event Grid topic provides a custom endpoint where your application publishes events. Think of it as a named channel for a specific category of events in your system.
Via Azure CLI:
# Create a resource group first
az group create --name rg-eventgrid-demo --location westus2
# Create the Event Grid topic
az eventgrid topic create \
--name my-custom-topic \
--resource-group rg-eventgrid-demo \
--location westus2 \
--input-schema CloudEventSchemaV1_0 \
--public-network-access enabled
# Retrieve the topic endpoint and access key
az eventgrid topic show \
--name my-custom-topic \
--resource-group rg-eventgrid-demo \
--query "endpoint" \
--output tsv
az eventgrid topic key list \
--name my-custom-topic \
--resource-group rg-eventgrid-demo \
--query "key1" \
--output tsv
Via ARM/Bicep template:
// eventgrid-topic.bicep
resource eventGridTopic 'Microsoft.EventGrid/topics@2023-06-01-preview' = {
name: 'my-custom-topic'
location: 'westus2'
properties: {
inputSchema: 'CloudEventSchemaV1_0'
publicNetworkAccess: 'Enabled'
inboundIpRules: []
}
}
// Output the endpoint and key for use in deployment pipelines
output topicEndpoint string = eventGridTopic.properties.endpoint
output topicKey string = listKeys(eventGridTopic.id, eventGridTopic.apiVersion).key1
Step 2: Publish Events to Your Topic
Once the topic exists, you can publish events using HTTP requests. Each request must include authentication and properly formatted event payloads.
Publishing CloudEvents v1.0 events via REST API:
// Node.js example: publishing events to Event Grid
const https = require('https');
const topicEndpoint = 'https://my-custom-topic.westus2-1.eventgrid.azure.net/api/events';
const topicKey = 'your-access-key-here';
function publishEvents() {
const events = [
{
id: 'evt-001',
source: '/orders/system',
type: 'OrderPlaced',
subject: 'orders/12345',
specversion: '1.0',
datacontenttype: 'application/json',
time: new Date().toISOString(),
data: {
orderId: '12345',
customerId: 'cust-789',
totalAmount: 299.99,
items: [
{ sku: 'WIDGET-001', quantity: 2, unitPrice: 99.99 },
{ sku: 'WIDGET-002', quantity: 1, unitPrice: 100.01 }
]
}
},
{
id: 'evt-002',
source: '/orders/system',
type: 'InventoryUpdated',
subject: 'inventory/WIDGET-001',
specversion: '1.0',
datacontenttype: 'application/json',
time: new Date().toISOString(),
data: {
sku: 'WIDGET-001',
previousCount: 150,
newCount: 148,
thresholdBreached: false
}
}
];
const requestBody = JSON.stringify(events);
const parsedUrl = new URL(topicEndpoint);
const options = {
hostname: parsedUrl.hostname,
path: parsedUrl.pathname + parsedUrl.search,
method: 'POST',
headers: {
'Content-Type': 'application/cloudevents-batch+json; charset=utf-8',
'aeg-sas-key': topicKey,
'Content-Length': Buffer.byteLength(requestBody)
}
};
const req = https.request(options, (res) => {
let responseData = '';
res.on('data', (chunk) => { responseData += chunk; });
res.on('end', () => {
if (res.statusCode === 200) {
console.log('Events published successfully');
} else {
console.error(`Publish failed: ${res.statusCode} - ${responseData}`);
}
});
});
req.on('error', (err) => console.error('Request error:', err.message));
req.write(requestBody);
req.end();
}
publishEvents();
C# example using Azure SDK:
using Azure;
using Azure.Messaging.EventGrid;
using Azure.Messaging.EventGrid.Namespace;
// For namespace-based topics (newer SDK)
var client = new EventGridPublisherClient(
new Uri("https://my-custom-topic.westus2-1.eventgrid.azure.net"),
new AzureKeyCredential("your-access-key-here")
);
var events = new List<EventGridEvent>
{
new EventGridEvent(
subject: "orders/12345",
eventType: "OrderPlaced",
dataVersion: "1.0",
data: new OrderPlacedData
{
OrderId = "12345",
CustomerId = "cust-789",
TotalAmount = 299.99m
}
)
};
await client.SendEventsAsync(events);
Step 3: Create Event Subscriptions with Filtering
Event subscriptions define which events get routed to which handlers. You can attach filters based on event type, subject pattern, or advanced data attributes.
Basic subscription to a Webhook endpoint:
az eventgrid topic event-subscription create \
--name order-processing-sub \
--resource-group rg-eventgrid-demo \
--topic-name my-custom-topic \
--endpoint-type webhook \
--endpoint "https://myapp.azurewebsites.net/api/orderprocessor" \
--max-delivery-attempts 10 \
--event-delivery-schema eventgridschema
Subscription with advanced filtering — only specific event types and subjects:
az eventgrid topic event-subscription create \
--name high-value-orders-sub \
--resource-group rg-eventgrid-demo \
--topic-name my-custom-topic \
--endpoint-type azurefunction \
--endpoint "/subscriptions/{sub-id}/resourceGroups/rg-functions/providers/Microsoft.Web/sites/func-highvalue/functions/ProcessOrder" \
--advanced-filter eventType StringIn "OrderPlaced" \
--advanced-filter subject StringBeginsWith "orders/" \
--advanced-filter data.totalAmount NumberGreaterThan 500.00 \
--max-delivery-attempts 5 \
--event-ttl 1440
Full ARM template for an Event Subscription with dead-lettering:
{
"type": "Microsoft.EventGrid/topics/eventSubscriptions",
"apiVersion": "2023-06-01-preview",
"name": "my-custom-topic/order-deadletter-sub",
"properties": {
"destination": {
"endpointType": "AzureFunction",
"properties": {
"resourceId": "/subscriptions/{subscriptionId}/resourceGroups/rg-functions/providers/Microsoft.Web/sites/func-app/functions/ProcessEvent",
"maxEventsPerBatch": 10,
"preferredBatchSizeInKilobytes": 1024
}
},
"filter": {
"subjectBeginsWith": "orders/",
"subjectEndsWith": "",
"includedEventTypes": ["OrderPlaced", "OrderShipped"],
"enableAdvancedFilteringOnArrays": true,
"advancedFilters": [
{
"operatorType": "NumberGreaterThan",
"key": "data.totalAmount",
"values": [100]
}
]
},
"retryPolicy": {
"maxDeliveryAttempts": 30,
"eventTimeToLiveInMinutes": 1440
},
"deadLetterDestination": {
"endpointType": "StorageBlob",
"properties": {
"resourceId": "/subscriptions/{subscriptionId}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.Storage/storageAccounts/stdeadletter",
"blobContainerName": "deadletter-events"
}
}
}
}
Step 4: Configure Webhook Validation and Security
When Event Grid creates a subscription to a webhook endpoint, it performs a handshake to validate endpoint ownership. Your webhook must respond to the validation event correctly.
Webhook handler that handles validation handshake:
// Express.js webhook handler with validation support
const express = require('express');
const app = express();
app.use(express.json());
app.post('/api/orderprocessor', (req, res) => {
const body = req.body;
// Check if this is a validation handshake
if (body && body[0] && body[0].eventType === 'Microsoft.EventGrid.SubscriptionValidationEvent') {
console.log('Handling subscription validation');
const validationCode = body[0].data.validationCode;
const validationUrl = body[0].data.validationUrl;
// Option 1: Echo back the validation code (simpler)
res.status(200).json({
validationResponse: validationCode
});
// Option 2: Also visit the validation URL for async validation
// fetch(validationUrl) can be called here for async handshake
return;
}
// Handle regular events
console.log('Received events:', JSON.stringify(body, null, 2));
// Acknowledge receipt with HTTP 200 within 30 seconds
// For async processing, return 200 immediately, then process
res.status(200).send('OK');
// Process events asynchronously
processEvents(body);
});
function processEvents(events) {
for (const event of events) {
console.log(`Processing event: ${event.eventType} - ${event.subject}`);
// Your business logic here
}
}
app.listen(8080, () => console.log('Webhook handler listening on port 8080'));
Securing webhook delivery with AAD authentication:
// For Azure Functions, use AAD-based authentication
// Configure the subscription to use AAD instead of client secrets
az eventgrid topic event-subscription create \
--name secure-orders-sub \
--resource-group rg-eventgrid-demo \
--topic-name my-custom-topic \
--endpoint-type azurefunction \
--endpoint "/subscriptions/{sub-id}/resourceGroups/rg-functions/providers/Microsoft.Web/sites/func-app/functions/ProcessEvent" \
--delivery-attribute-mapping Authorization static "not-used" \
--aad-tenant-id "00000000-0000-0000-0000-000000000000" \
--aad-app-id "11111111-1111-1111-1111-111111111111"
Step 5: Monitor Event Delivery and Dead-Letter Events
Understanding what happens to your events is critical. Event Grid exposes metrics and diagnostic logs that reveal delivery success rates, latency, and dead-letter accumulation.
# Enable diagnostic settings to capture delivery failures
az monitor diagnostic-settings create \
--name eventgrid-diagnostics \
--resource "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/my-custom-topic" \
--workspace "/subscriptions/{sub-id}/resourceGroups/rg-monitoring/providers/Microsoft.OperationalInsights/workspaces/central-logs" \
--logs '[{"category": "DeliveryFailures", "enabled": true}, {"category": "PublishFailures", "enabled": true}]' \
--metrics '[{"category": "AllMetrics", "enabled": true}]'
# Query dead-letter events from storage (using storage explorer or SDK)
# Dead-lettered events are stored as blobs with full event payload preserved
Reading dead-lettered events for analysis:
// C# code to read and analyze dead-lettered events
using Azure.Storage.Blobs;
using Azure.Storage.Blobs.Models;
using System.Text.Json;
var blobServiceClient = new BlobServiceClient("connection-string-or-managed-identity");
var containerClient = blobServiceClient.GetBlobContainerClient("deadletter-events");
await foreach (BlobItem blob in containerClient.GetBlobsAsync())
{
var blobClient = containerClient.GetBlobClient(blob.Name);
var response = await blobClient.DownloadContentAsync();
var deadLetteredEvent = JsonSerializer.Deserialize<DeadLetterEvent>(response.Value.Content);
Console.WriteLine($"Dead-lettered event:");
Console.WriteLine($" Type: {deadLetteredEvent.EventType}");
Console.WriteLine($" Subject: {deadLetteredEvent.Subject}");
Console.WriteLine($" Delivery attempts: {deadLetteredEvent.DeliveryAttempts}");
Console.WriteLine($" Last error: {deadLetteredEvent.LastDeliveryError}");
Console.WriteLine($" Original event: {deadLetteredEvent.Event}");
}
public class DeadLetterEvent
{
public string EventType { get; set; }
public string Subject { get; set; }
public int DeliveryAttempts { get; set; }
public string LastDeliveryError { get; set; }
public JsonElement Event { get; set; }
}
Best Practices for Event Grid
Design Events as Facts, Not Commands
An event should describe something that has already happened — a fact. Avoid designing events as commands (things you want to happen). For example, emit OrderPlaced with the order details rather than ProcessOrder which implies an action. This distinction keeps events immutable and allows multiple subscribers to react differently to the same fact. The event carries a past-tense verb and complete contextual data, enabling consumers to make their own decisions about how to respond.
Use CloudEvents Schema for Interoperability
Whenever possible, adopt the CloudEvents v1.0 schema instead of the proprietary Event Grid schema. CloudEvents provides a standardized envelope that works across cloud providers, message brokers, and middleware. It includes well-defined attributes like source, type, id, and specversion that make events self-describing. This future-proofs your architecture: if you later integrate with Kafka, Knative, or another cloud provider, your events remain portable without transformation.
Implement Idempotent Event Handlers
Event Grid guarantees at-least-once delivery. Your handler may receive the same event multiple times — during retries, network glitches, or regional failovers. Always build idempotency into your processing logic. Use the event id field as a deduplication key, storing processed event IDs in a cache or database. Before acting on an event, check whether you've already processed it. This practice prevents duplicate orders, double billing, or corrupted state.
// Idempotent handler example with deduplication
async function handleEvent(event) {
const eventId = event.id;
// Check if already processed (use Redis, CosmosDB, or any persistent store)
const alreadyProcessed = await deduplicationCache.exists(eventId);
if (alreadyProcessed) {
console.log(`Event ${eventId} already processed, skipping`);
return;
}
// Process the event
await processBusinessLogic(event);
// Mark as processed with TTL matching your retry window (e.g., 24 hours)
await deduplicationCache.set(eventId, 'processed', { ttl: 86400 });
}
Configure Dead-Letter Destinations for Every Production Subscription
Never deploy a subscription without a dead-letter path. If your handler is down for an extended period — exceeding the retry window — events will be lost unless dead-lettering is configured. A storage account blob container costs pennies and preserves critical events for forensic analysis. Regularly monitor dead-letter queues and set up alerts. A growing dead-letter backlog signals a systemic issue that needs immediate attention.
Apply Filtering at the Subscription Level
Push filtering logic into the Event Grid subscription rather than implementing it in your handler. Subscription filters reduce noise, lower handler execution costs, and simplify your code. Use subjectBeginsWith, subjectEndsWith, and includedEventTypes for basic filtering. For complex scenarios, leverage advanced filters with operators like NumberGreaterThan, StringContains, or BoolEquals on event data properties. The less irrelevant traffic your handler receives, the more efficient and reliable it becomes.
Batch Events for High-Throughput Handlers
If your handler processes high volumes of events, configure batching in the subscription. Event Grid can deliver up to 10 events per batch (for Azure Functions) or up to 20 events per batch (for webhooks), with configurable maximum batch size in kilobytes. Batching amortizes the HTTP overhead across multiple events and allows your handler to process them in bulk — ideal for database writes, analytics ingestion, or downstream fan-out patterns.
Monitor and Alert on Delivery Metrics
Set up Azure Monitor alerts on key Event Grid metrics: MatchedEvents, DeliverySuccessCount, DeliveryFailedCount, and DeadLetteredCount. A sudden drop in matched events might indicate a publishing issue. A spike in delivery failures could point to a handler outage. Dead-letter growth signals events being permanently lost. Proactive monitoring catches these issues before they impact business processes.
# Create an alert rule for dead-lettered events
az monitor metrics alert create \
--name "DeadLetterAlert" \
--resource-group rg-monitoring \
--scopes "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/my-custom-topic" \
--condition "avg DeadLetteredCount > 5" \
--window-size 5m \
--evaluation-frequency 1m \
--action "/subscriptions/{sub-id}/resourceGroups/rg-monitoring/providers/Microsoft.Insights/actionGroups/ops-team" \
--description "Alert when events are being dead-lettered"
Secure Your Topics with Managed Identity and RBAC
Avoid using access keys for authentication when possible. Instead, grant publishers and subscribers access via Azure RBAC roles using managed identities. For topic publishing, assign the EventGrid Data Sender role to the publishing application's identity. For subscription management, use EventGrid EventSubscription Contributor. This eliminates key rotation headaches and provides fine-grained, auditable access control.
# Grant a Function App's managed identity the Data Sender role
az role assignment create \
--assignee "/subscriptions/{sub-id}/resourceGroups/rg-functions/providers/Microsoft.Web/sites/func-publisher" \
--role "EventGrid Data Sender" \
--scope "/subscriptions/{sub-id}/resourceGroups/rg-eventgrid-demo/providers/Microsoft.EventGrid/topics/my-custom-topic"
Conclusion
Azure Event Grid transforms how cloud applications communicate, replacing brittle polling loops with robust, push-based event distribution. Throughout this guide, we've covered the complete lifecycle — from creating topics and publishing events, to configuring subscriptions with advanced filtering, securing endpoints, handling validation handshakes, and monitoring delivery with dead-letter safeguards.
The real power of Event Grid emerges when you embrace its design philosophy: treat events as immutable facts, build idempotent consumers, push filtering to the infrastructure layer, and always plan for failure with dead-letter destinations. By following these practices and leveraging the code examples provided, you can build truly reactive, decoupled systems that scale effortlessly while maintaining reliability. Whether you're orchestrating microservices, automating infrastructure responses, or building customer-facing event-driven features, Event Grid provides the reliable backbone your architecture needs.