Skip to content
Back to Blog
1 min read

Azure Event Grid Custom Topics: Event-Driven Architecture

Service Bus is fine for service-to-service messaging, but when you want “anything in the system can listen for the order created event,” Service Bus topics get awkward. Event Grid custom topics fit that shape natively—publish your domain events, let consumers subscribe with filters, and let Event Grid handle delivery (retries, dead-lettering, throughput). I keep recommending it as the backbone for event-driven architectures where the publisher shouldn’t know the consumer list.

Event Grid Concepts

  • Topic: Endpoint where events are sent
  • Event Subscription: Routes events to handlers
  • Event Handler: Service that receives events
  • Event Schema: Structure of event data

Create Custom Topic

# Create Event Grid topic
az eventgrid topic create \
    --name my-custom-topic \
    --resource-group myRG \
    --location eastus

# Get topic endpoint and key
az eventgrid topic show \
    --name my-custom-topic \
    --resource-group myRG \
    --query endpoint

az eventgrid topic key list \
    --name my-custom-topic \
    --resource-group myRG \
    --query key1

Event Schema

[
    {
        "id": "unique-event-id",
        "eventType": "Order.Created",
        "subject": "/orders/12345",
        "eventTime": "2021-01-11T10:30:00Z",
        "data": {
            "orderId": "12345",
            "customerId": "cust-789",
            "amount": 299.99,
            "items": [
                { "productId": "prod-1", "quantity": 2 }
            ]
        },
        "dataVersion": "1.0"
    }
]

Publish Events (.NET)

using Azure;
using Azure.Messaging.EventGrid;

var endpoint = new Uri("https://my-custom-topic.eastus-1.eventgrid.azure.net/api/events");
var credential = new AzureKeyCredential("your-key");
var client = new EventGridPublisherClient(endpoint, credential);

// Create event
var orderEvent = new EventGridEvent(
    subject: "/orders/12345",
    eventType: "Order.Created",
    dataVersion: "1.0",
    data: new
    {
        OrderId = "12345",
        CustomerId = "cust-789",
        Amount = 299.99m,
        CreatedAt = DateTime.UtcNow
    }
);

// Publish event
await client.SendEventAsync(orderEvent);
Console.WriteLine("Event published");

// Publish batch
var events = new List<EventGridEvent>
{
    new EventGridEvent("/orders/123", "Order.Created", "1.0", orderData1),
    new EventGridEvent("/orders/124", "Order.Created", "1.0", orderData2),
    new EventGridEvent("/orders/125", "Order.Shipped", "1.0", orderData3)
};

await client.SendEventsAsync(events);

Create Event Subscription

# Subscribe Azure Function to topic
az eventgrid event-subscription create \
    --name order-processor-subscription \
    --source-resource-id /subscriptions/.../topics/my-custom-topic \
    --endpoint-type azurefunction \
    --endpoint /subscriptions/.../functions/ProcessOrder

# Subscribe webhook
az eventgrid event-subscription create \
    --name webhook-subscription \
    --source-resource-id /subscriptions/.../topics/my-custom-topic \
    --endpoint https://myapi.com/webhooks/eventgrid

# Subscribe with filters
az eventgrid event-subscription create \
    --name filtered-subscription \
    --source-resource-id /subscriptions/.../topics/my-custom-topic \
    --endpoint https://myapi.com/webhooks/orders \
    --event-types Order.Created Order.Updated \
    --subject-begins-with /orders/ \
    --subject-ends-with /priority

Handle Events (Azure Function)

using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.EventGrid;
using Azure.Messaging.EventGrid;

public static class OrderProcessor
{
    [FunctionName("ProcessOrder")]
    public static async Task Run(
        [EventGridTrigger] EventGridEvent eventGridEvent,
        ILogger log)
    {
        log.LogInformation($"Event type: {eventGridEvent.EventType}");
        log.LogInformation($"Subject: {eventGridEvent.Subject}");
        log.LogInformation($"Data: {eventGridEvent.Data}");

        // Deserialize event data
        var orderData = eventGridEvent.Data.ToObjectFromJson<OrderCreatedData>();

        // Process the order
        await ProcessOrderAsync(orderData);
    }
}

public record OrderCreatedData(
    string OrderId,
    string CustomerId,
    decimal Amount,
    DateTime CreatedAt
);

Advanced Filtering

# Advanced filter with multiple conditions
az eventgrid event-subscription create \
    --name advanced-filtered \
    --source-resource-id /subscriptions/.../topics/my-custom-topic \
    --endpoint https://myapi.com/webhooks \
    --advanced-filter data.amount NumberGreaterThan 100 \
    --advanced-filter data.priority StringIn high critical

Dead-lettering

# Enable dead-letter destination
az eventgrid event-subscription create \
    --name with-deadletter \
    --source-resource-id /subscriptions/.../topics/my-custom-topic \
    --endpoint https://myapi.com/webhooks \
    --deadletter-endpoint /subscriptions/.../storageAccounts/mystorage/blobServices/default/containers/deadletter

CloudEvents Schema

// Use CloudEvents format
var cloudEvent = new CloudEvent(
    source: "/myapp/orders",
    type: "order.created",
    data: new { OrderId = "12345", Amount = 99.99 }
);

var client = new EventGridPublisherClient(endpoint, credential);
await client.SendEventAsync(cloudEvent);

Event Grid: reliable event delivery at massive scale.\n\n## Takeaways\n\nAdd a concise, personal takeaway and recommended next steps here.\n

Michael John Peña

Michael John Peña

Senior Data Engineer based in Sydney. Writing about data, cloud, and technology.