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 TypeTriggerCommon Use
Microsoft.Storage.BlobCreatedBlob uploaded or created (including copy, PutBlob, PutBlockList, FlushWithClose)Process new files, trigger pipelines
Microsoft.Storage.BlobDeletedBlob deleted (including soft-delete)Audit logging, cleanup dependent resources
Microsoft.Storage.BlobTierChangedBlob access tier changedTrack cost optimization actions
Microsoft.Storage.BlobRenamedBlob renamed (Data Lake Storage Gen2 only)Update references, sync metadata
Microsoft.Storage.DirectoryCreatedDirectory created (Data Lake Gen2 only)Folder-level automation
Microsoft.Storage.DirectoryDeletedDirectory deleted (Data Lake Gen2 only)Cascade cleanup

Note: For BlobCreated events, the event fires only after the blob is fully committed. For block blobs, this means after PutBlockList completes — not after individual PutBlock calls.


Enabling Event Grid on Your Storage Account

Before subscribing to events, ensure your storage account supports Event Grid:

  1. Navigate to your Storage Account in the Azure Portal
  2. Click Events in the left menu
  3. 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

  1. Idempotent handlers — Events may be delivered more than once. Design your handlers to safely process the same event multiple times without side effects.

  2. Use the sequencer field — When ordering matters, use the sequencer property to determine the correct order of events for the same blob.

  3. 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).

  4. Handle missing blobs gracefully — By the time your handler runs, the blob might have been deleted or moved. Always handle BlobNotFoundException.

  5. 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"
  1. Monitor with metrics — Track delivery success/failure rates, latency, and dead-lettered events in Azure Monitor.

Common Pitfalls

PitfallSolution
Events fire for all containersAlways use subject prefix filtering
Duplicate processingMake handlers idempotent using event ID
Timeout on large file processingAcknowledge event, process async via queue
Missing events for block blobsEvents fire on PutBlockList, not PutBlock
Events for copy operationsBlobCreated fires for copies too — filter by api field if needed
Subscription validation failsEnsure 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


Azure Integration Hub - Intermediate Level