Subscribe to Blob Events with Azure Event Grid
Overview
Azure Event Grid provides a fully managed event-routing service that lets you react to changes in your Azure Blob Storage accounts in near real-time. When a blob is created, deleted, or modified, Event Grid publishes an event that your applications can subscribe to and handle — enabling powerful event-driven architectures without polling.
This guide covers how blob events work, how to set up subscriptions, filtering strategies, and real-world patterns for building reactive systems.
What Are Blob Events?
Blob events are notifications published by Azure Storage whenever something happens to a blob. Instead of continuously polling your storage account to detect changes, Event Grid pushes events to your subscribers the moment they occur.
Key characteristics:
- Near real-time delivery — Events are typically delivered within seconds of the blob operation
- At-least-once delivery — Event Grid guarantees delivery but events may arrive more than once
- Push-based — No polling required; events are pushed to your endpoint
- Scalable — Handles millions of events per second across your storage accounts
Supported Event Types
| Event Type | Trigger | Common Use |
|---|---|---|
Microsoft.Storage.BlobCreated | Blob uploaded or created (including copy, PutBlob, PutBlockList, FlushWithClose) | Process new files, trigger pipelines |
Microsoft.Storage.BlobDeleted | Blob deleted (including soft-delete) | Audit logging, cleanup dependent resources |
Microsoft.Storage.BlobTierChanged | Blob access tier changed | Track cost optimization actions |
Microsoft.Storage.BlobRenamed | Blob renamed (Data Lake Storage Gen2 only) | Update references, sync metadata |
Microsoft.Storage.DirectoryCreated | Directory created (Data Lake Gen2 only) | Folder-level automation |
Microsoft.Storage.DirectoryDeleted | Directory deleted (Data Lake Gen2 only) | Cascade cleanup |
Note: For
BlobCreatedevents, the event fires only after the blob is fully committed. For block blobs, this means afterPutBlockListcompletes — not after individualPutBlockcalls.
Enabling Event Grid on Your Storage Account
Before subscribing to events, ensure your storage account supports Event Grid:
- Navigate to your Storage Account in the Azure Portal
- Click Events in the left menu
- If prompted, enable the Event Grid resource provider
CLI Setup
# Register the Event Grid resource provider (one-time per subscription)
az provider register --namespace Microsoft.EventGrid
# Verify registration
az provider show --namespace Microsoft.EventGrid --query "registrationState"
Creating an Event Subscription
Using Azure CLI
# Create a subscription that sends blob-created events to an Azure Function
az eventgrid event-subscription create \
--name blob-upload-handler \
--source-resource-id "/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{account}" \
--endpoint "https://my-function-app.azurewebsites.net/runtime/webhooks/EventGrid?functionName=BlobEventHandler&code={key}" \
--included-event-types Microsoft.Storage.BlobCreated \
--subject-begins-with "/blobServices/default/containers/uploads/" \
--subject-ends-with ".jpg"
Using C# (Azure SDK)
using Azure;
using Azure.Messaging.EventGrid;
using Azure.ResourceManager;
using Azure.ResourceManager.EventGrid;
using Azure.ResourceManager.EventGrid.Models;
var armClient = new ArmClient(new DefaultAzureCredential());
var subscription = await armClient.GetDefaultSubscriptionAsync();
var storageAccountId = new ResourceIdentifier(
"/subscriptions/{sub-id}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{account}");
var eventSubscriptionData = new EventGridSubscriptionData
{
Destination = new WebHookEventSubscriptionDestination
{
Endpoint = new Uri("https://my-function-app.azurewebsites.net/api/BlobEventHandler")
},
Filter = new EventSubscriptionFilter
{
SubjectBeginsWith = "/blobServices/default/containers/uploads/",
SubjectEndsWith = ".pdf",
IncludedEventTypes = { "Microsoft.Storage.BlobCreated" }
}
};
Filtering Events
Filtering is critical to avoid processing irrelevant events and reducing noise. Event Grid supports three filtering mechanisms:
Subject Filtering (Prefix/Suffix)
Filter by the blob path using prefix and suffix matching:
# Only events from the "images" container, for .png files
az eventgrid event-subscription create \
--name png-processor \
--source-resource-id $STORAGE_ID \
--endpoint $FUNCTION_URL \
--subject-begins-with "/blobServices/default/containers/images/" \
--subject-ends-with ".png"
Advanced Filtering
Use advanced filters for more complex conditions:
# Only blobs larger than 1MB
az eventgrid event-subscription create \
--name large-blob-handler \
--source-resource-id $STORAGE_ID \
--endpoint $FUNCTION_URL \
--advanced-filter data.contentLength NumberGreaterThan 1048576
Filtering Best Practices
- Always filter by container — Avoid processing events from unrelated containers
- Use suffix filters for file types — Process only the file types your handler supports
- Combine prefix + suffix — Narrow down to specific paths and extensions
- Use advanced filters sparingly — They add complexity; prefer simple subject filters when possible
Handling Events in Azure Functions
Event Grid Trigger (Recommended)
using Azure.Messaging.EventGrid;
using Azure.Storage.Blobs;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Extensions.Logging;
public class BlobEventHandler
{
private readonly BlobServiceClient _blobServiceClient;
private readonly ILogger<BlobEventHandler> _logger;
public BlobEventHandler(BlobServiceClient blobServiceClient, ILogger<BlobEventHandler> logger)
{
_blobServiceClient = blobServiceClient;
_logger = logger;
}
[Function("BlobEventHandler")]
public async Task Run([EventGridTrigger] EventGridEvent eventGridEvent)
{
_logger.LogInformation("Event type: {Type}, Subject: {Subject}",
eventGridEvent.EventType, eventGridEvent.Subject);
var data = eventGridEvent.Data.ToObjectFromJson<StorageBlobCreatedEventData>();
// Extract container and blob name from the URL
var blobUri = new Uri(data.Url);
var containerName = blobUri.Segments[1].TrimEnd('/');
var blobName = string.Join("", blobUri.Segments.Skip(2));
_logger.LogInformation("Processing blob: {Container}/{Blob}, Size: {Size} bytes",
containerName, blobName, data.ContentLength);
// Download and process the blob
var containerClient = _blobServiceClient.GetBlobContainerClient(containerName);
var blobClient = containerClient.GetBlobClient(blobName);
var response = await blobClient.DownloadContentAsync();
var content = response.Value.Content;
// Your processing logic here
await ProcessBlobAsync(containerName, blobName, content);
}
private async Task ProcessBlobAsync(string container, string blob, BinaryData content)
{
// Implementation depends on your use case
_logger.LogInformation("Successfully processed {Container}/{Blob}", container, blob);
}
}
Handling Multiple Event Types
[Function("MultiEventHandler")]
public async Task Run([EventGridTrigger] EventGridEvent eventGridEvent)
{
switch (eventGridEvent.EventType)
{
case "Microsoft.Storage.BlobCreated":
var createdData = eventGridEvent.Data.ToObjectFromJson<StorageBlobCreatedEventData>();
await HandleBlobCreated(createdData);
break;
case "Microsoft.Storage.BlobDeleted":
var deletedData = eventGridEvent.Data.ToObjectFromJson<StorageBlobDeletedEventData>();
await HandleBlobDeleted(deletedData);
break;
default:
_logger.LogWarning("Unhandled event type: {Type}", eventGridEvent.EventType);
break;
}
}
Real-World Use Cases
1. Image Processing Pipeline
When users upload images, automatically generate thumbnails and extract metadata:
User uploads image → BlobCreated event → Azure Function →
├── Generate thumbnail → Save to "thumbnails" container
├── Extract EXIF metadata → Store in Cosmos DB
└── Run content moderation → Flag inappropriate content
2. Data Lake Ingestion
Trigger ETL pipelines when raw data lands in your data lake:
Partner drops CSV → BlobCreated event → Azure Function →
├── Validate schema
├── Transform to Parquet
├── Move to "processed" container
└── Update data catalog
3. Document Processing
Automatically process uploaded documents:
PDF uploaded → BlobCreated event → Azure Function →
├── Extract text with Form Recognizer
├── Index in Azure Cognitive Search
└── Notify user via SignalR
4. Compliance and Auditing
Track all blob deletions for compliance:
Blob deleted → BlobDeleted event → Azure Function →
├── Log to immutable audit storage
├── Check if deletion was authorized
└── Alert security team if suspicious
Event Schema
A typical BlobCreated event looks like this:
{
"id": "b9a1e3c4-5d6f-7a8b-9c0d-1e2f3a4b5c6d",
"topic": "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{account}",
"subject": "/blobServices/default/containers/uploads/blobs/report.pdf",
"eventType": "Microsoft.Storage.BlobCreated",
"eventTime": "2026-05-20T10:30:00.1234567Z",
"data": {
"api": "PutBlob",
"clientRequestId": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"requestId": "f1e2d3c4-b5a6-7890-1234-567890abcdef",
"eTag": "0x8D4BCC2E4835CD0",
"contentType": "application/pdf",
"contentLength": 524288,
"blobType": "BlockBlob",
"url": "https://myaccount.blob.core.windows.net/uploads/report.pdf",
"sequencer": "00000000000000000000000000001234"
}
}
Best Practices
-
Idempotent handlers — Events may be delivered more than once. Design your handlers to safely process the same event multiple times without side effects.
-
Use the sequencer field — When ordering matters, use the
sequencerproperty to determine the correct order of events for the same blob. -
Respond quickly — Event Grid expects a response within 30 seconds. For long-running processing, acknowledge the event immediately and process asynchronously (e.g., queue a message to Service Bus).
-
Handle missing blobs gracefully — By the time your handler runs, the blob might have been deleted or moved. Always handle
BlobNotFoundException. -
Set up dead-lettering — Configure a dead-letter destination for events that fail delivery after retries:
az eventgrid event-subscription create \
--name my-subscription \
--source-resource-id $STORAGE_ID \
--endpoint $FUNCTION_URL \
--deadletter-endpoint "/subscriptions/{sub}/resourceGroups/{rg}/providers/Microsoft.Storage/storageAccounts/{dlq-account}/blobServices/default/containers/deadletters"
- Monitor with metrics — Track delivery success/failure rates, latency, and dead-lettered events in Azure Monitor.
Common Pitfalls
| Pitfall | Solution |
|---|---|
| Events fire for all containers | Always use subject prefix filtering |
| Duplicate processing | Make handlers idempotent using event ID |
| Timeout on large file processing | Acknowledge event, process async via queue |
| Missing events for block blobs | Events fire on PutBlockList, not PutBlock |
| Events for copy operations | BlobCreated fires for copies too — filter by api field if needed |
| Subscription validation fails | Ensure your endpoint responds to the validation handshake |
Retry and Delivery Policy
Event Grid retries delivery with exponential backoff:
- Max retries: 30 attempts over 24 hours (default)
- Retry schedule: 10s, 30s, 1min, 5min, 10min, 30min, 1hr (then hourly)
- Dead-letter: Events that exhaust retries are dead-lettered (if configured) or dropped
You can customize the retry policy:
az eventgrid event-subscription create \
--name my-subscription \
--source-resource-id $STORAGE_ID \
--endpoint $FUNCTION_URL \
--max-delivery-attempts 10 \
--event-ttl 1440 # minutes (24 hours)
Next Steps
- Event Grid Custom Topics — Publish your own events
- Service Bus Integration — Combine Event Grid with Service Bus for reliable processing
Azure Integration Hub - Intermediate Level