Skip to content

Azure Event Grid

Key Points

  • Event Grid is Azure's system-event broker — pub/sub for discrete events (something happened) from Azure resources, your apps, or SaaS partners.
  • Distinct from siblings: Service Bus = transactional FIFO work queues; Event Hubs = high-throughput streaming / telemetry; Event Grid = discrete reactive events with fan-out filtering. See Service Bus & Event Hubs.
  • Topic types: System (built-in for Azure resources), Custom (your events), Domain (multi-topic hierarchical), Partner (SAP, Salesforce, etc.).
  • Subscriptions route events to webhooks, Functions, Service Bus, Event Hubs, Storage Queues — with filters (subject prefix/suffix + advanced filters on payload).
  • CloudEvents v1.0 schema is the cross-cloud standard; Event Grid speaks both CloudEvents and its native schema.
  • Reliability: at-least-once delivery, exponential retry, dead-letter to Storage. Premium SKU adds namespaces, MQTT broker, bigger limits.

Concepts (deep dive)

What Event Grid is

A managed pub/sub system optimized for discrete event reactions — "this resource changed, react now." Not a queue; not a log. Push-based: Event Grid pushes events to subscribers.

The decision tree for the three Azure event/messaging services is:

  • Event Grid"Tell me when something happens". Reactive notifications, small JSON payloads, sub-second latency, fan-out by filter. Built-in for Azure resource events (blob created, image pushed, secret rotated).
  • Service Bus"Process this work item". Commands, business workflows, ordering, transactions, DLQ. The thing has to be done; the broker tracks that.
  • Event Hubs"Append this to my stream". Telemetry, IoT, click streams, event sourcing. Append-only log, billions of events, replay by offset.

It's normal to use all three in one system: Event Grid notifies a Function when a blob arrives, the Function drops a job onto Service Bus, the worker processes the job and emits telemetry to Event Hubs.

[ Publisher ]                     [ Event Grid Topic ]                 [ Subscribers ]
Storage account     ──┐                   ├── Subscription 1 ─► Function (filter: blob created)
Resource Group ─event─┤                   │
Custom App publish ──┤                   ├── Subscription 2 ─► Webhook  (filter: subject /orders/*)
SaaS partner    ─────┘                    │
                                          └── Subscription 3 ─► Service Bus queue (filter: type=cancelled)

Topic types

Type What Example
System Built-in topics for Azure resources Storage BlobCreated, ACR ImagePushed, Resources ResourceWriteSuccess
Custom Your topic for your app's events OrderPlaced, UserSignedUp
Domain Multi-tenant container of many topics, one endpoint One domain per app, topic-per-tenant
Partner Third-party SaaS publishes events to Azure SAP S/4HANA events, Salesforce events
// Custom topic
resource topic 'Microsoft.EventGrid/topics@2024-06-01-preview' = {
  name: 'orders-topic'
  location: location
  properties: { inputSchema: 'CloudEventSchemaV1_0' }
}

Subscriptions

A subscription = (topic, filter, destination, retry policy, DLQ).

resource sub 'Microsoft.EventGrid/topics/eventSubscriptions@2024-06-01-preview' = {
  parent: topic
  name: 'order-cancelled-to-fn'
  properties: {
    destination: {
      endpointType: 'AzureFunction'
      properties: { resourceId: fn.id, maxEventsPerBatch: 10 }
    }
    filter: {
      includedEventTypes: ['Order.Cancelled']
      subjectBeginsWith: '/orders/'
      advancedFilters: [{
        operatorType: 'NumberGreaterThan'
        key: 'data.amount'
        value: 1000
      }]
    }
    retryPolicy: {
      maxDeliveryAttempts: 30
      eventTimeToLiveInMinutes: 1440  // 24h
    }
    deadLetterDestination: {
      endpointType: 'StorageBlob'
      properties: { resourceId: storage.id, blobContainerName: 'eg-dlq' }
    }
  }
}

Filters

Filter What
includedEventTypes List of event-type strings (e.g. Microsoft.Storage.BlobCreated)
subjectBeginsWith / subjectEndsWith String prefix / suffix on the event subject
Advanced filters AND/OR over event payload fields: NumberIn, StringContains, BoolEquals, etc.

Advanced filter example:

{
  "operatorType": "StringIn",
  "key": "data.region",
  "values": ["eastus", "westus2"]
}

Up to 25 advanced filters per subscription on Standard; more on Premium.

Destinations

Endpoint Use
Webhook (HTTPS) Any HTTPS endpoint with the validation handshake
Azure Function Direct binding (EventGridTrigger)
Logic App Visual orchestration
Service Bus queue / topic Buffer + transactional re-processing
Event Hubs Pipe events into streaming
Storage Queue Cheap durable buffer
Hybrid Connection On-prem destinations

CloudEvents v1.0

Industry-standard schema for events (CNCF). Fields: id, source, type, specversion, subject, time, data, datacontenttype.

{
  "specversion": "1.0",
  "type": "com.example.order.placed",
  "source": "/orders/api",
  "id": "evt-001",
  "time": "2026-04-30T10:00:00Z",
  "subject": "orders/12345",
  "datacontenttype": "application/json",
  "data": { "orderId": 12345, "amount": 199.99 }
}

Event Grid native schema is similar but with different field names. Default new topics to CloudEvents — portable, friendly to non-Azure consumers.

Reliability

  • At-least-once delivery — subscribers may see duplicates (ensure idempotency).
  • Exponential backoff retry — starts seconds, grows up to hours, gives up after maxDeliveryAttempts or eventTimeToLive.
  • Dead-letter — undeliverable events (4xx other than 408/429, exhausted retries) park in a Blob container.
  • Ordering not guaranteed — events may arrive out-of-order. If you need order, include a sequence number in payload or use Service Bus sessions instead.
  • Throughput — Standard SKU: ~5K events/s/topic ingress. Premium SKU (Namespaces): much higher.
Send fails →
  retry after 10s →
  retry after 30s →
  retry after 1m → ... →
  give up → DLQ to Blob

Validation handshake (webhooks)

When you create a webhook subscription, Event Grid sends a subscription validation event. Your endpoint must echo back the validation code (or use URL-validation handshake) to prove ownership.

1. Create subscription → Event Grid POSTs validation event to your URL
2. Your endpoint:
   - Detects validationCode in payload
   - Either: returns { "validationResponse": "<code>" } in 200
   - Or: GET the validationUrl provided
3. Event Grid marks subscription active

This stops random people from registering your URL as a target for their events.

Premium SKU + Namespaces

Newer "Event Grid namespaces" (Premium) bring:

  • Higher limits — millions of events/s.
  • Push + pull delivery — pull mode lets subscribers fetch with HTTP long-poll (no public webhook required).
  • MQTT broker — IoT-style pub/sub at the edge with QoS 0/1.
  • Topic spaces for hierarchical access control.

Use namespaces for IoT, high-throughput systems, or when you want pull-style consumption.

MQTT broker mode

[ IoT device ] ──MQTT 3.1.1/5──► [ Event Grid namespace ] ──► [ Service Bus / Functions / etc. ]

Replaces self-managed MQTT brokers (e.g. EMQX, HiveMQ). Authenticate via X.509 certs, JWT, or AAD. Bridges MQTT messages into the rest of Azure.

Event Grid vs Service Bus vs Event Hubs

Event Grid Service Bus Event Hubs
Pattern Discrete events; reactive Work queue / messaging Telemetry log / streaming
Throughput High (M/s on Premium) ~100K msg/s Millions/s
Delivery Push (and pull on Premium) Pull Pull (consumer groups)
Ordering No Sessions Per-partition
Replay No No Yes (retention)
DLQ Built-in (Blob) Built-in Manual
Filtering Subject + advanced Subscription-level (SQL filter) None at broker
Use "Something happened, fan out" "Process this work item" "Stream of telemetry"

Mental model:

  • Event Grid: "the doorbell rang."
  • Service Bus: "here's a job to do — atomically process it once."
  • Event Hubs: "10K sensors sending readings every second; build a graph."

These often work together: Event Grid alerts on a Storage BlobCreated, drops a Service Bus message that a worker consumes; the worker emits processed metrics to Event Hubs.

.NET clients

Publishing

using Azure.Messaging.EventGrid;
using Azure.Identity;

var client = new EventGridPublisherClient(
    new Uri("https://orders-topic.eastus-1.eventgrid.azure.net/api/events"),
    new DefaultAzureCredential());

// CloudEvents
var evt = new CloudEvent("/orders/api", "com.acme.order.placed", new
{
    orderId = 12345,
    amount = 199.99
})
{
    Subject = "orders/12345"
};

await client.SendEventAsync(evt);

Webhook handler in ASP.NET Core (with validation)

app.MapPost("/eg/orders", async (HttpRequest req) =>
{
    using var sr = new StreamReader(req.Body);
    var body = await sr.ReadToEndAsync();

    // CloudEvents schema → array of events
    var events = EventGridEvent.ParseMany(BinaryData.FromString(body));

    foreach (var e in events)
    {
        // 1) Subscription validation (Event Grid native schema only)
        if (e.TryGetSystemEventData(out object data) &&
            data is SubscriptionValidationEventData v)
        {
            return Results.Ok(new { validationResponse = v.ValidationCode });
        }

        // 2) Real event — process idempotently
        if (e.EventType == "com.acme.order.placed")
        {
            var payload = e.Data.ToObjectFromJson<OrderPlaced>();
            await _orders.ProcessAsync(payload, e.Id /* idempotency key */);
        }
    }

    return Results.Ok();
});

For CloudEvents schema, validation uses an OPTIONS pre-flight on the URL with WebHook-Request-Origin — respond with WebHook-Allowed-Origin header. The SDK helpers handle both schemas.

Functions trigger

[Function(nameof(OrderPlacedFn))]
public async Task Run([EventGridTrigger] CloudEvent e)
{
    var order = e.Data.ToObjectFromJson<OrderPlaced>();
    await _orders.ProcessAsync(order, e.Id);
}

Functions handles validation + retries + DLQ for you.

Authentication

Path Auth
Publisher → topic AAD (managed identity, preferred) or topic access key
Topic → webhook Webhook URL with optional client secret in query string, or AAD
Topic → Function/Service Bus/etc. Managed identity assigned to topic with role on destination
# Grant topic identity rights to push to a Function
az role assignment create \
  --assignee <topic-mi-principal-id> \
  --role 'EventGrid Data Sender' \
  --scope <function-resource-id>

Idempotency

At-least-once = duplicates can happen. Always include a stable id (or correlation key) in the event and dedupe on the consumer side.

// Pseudo
if (await _seenIds.AddAsync(eventId))   // returns false if already seen
{
    await ProcessAsync(payload);
}

Common stores for the dedup set: Redis with TTL, Cosmos DB, SQL with unique index.

Schema registry (lightweight)

For custom events, document the schema (JSON Schema or AsyncAPI). Optionally use Azure Schema Registry (lives in Event Hubs Premium) for centralized Avro/JSON schemas, but most teams just version type strings (com.acme.order.placed.v1v2) and document per-topic in the repo.

Cost shape

  • Per million operations (ingress + delivery attempt).
  • DLQ storage in your Blob account.
  • Premium namespaces: per-throughput-unit baseline + ops above the unit.

A typical app generating < 1M events/month costs cents.


Code: correct vs wrong

❌ Wrong: no idempotency in handler

app.MapPost("/eg/orders", async (CloudEvent e) =>
{
    await _orders.ChargeCard(e.Data.ToObjectFromJson<Order>());  // ☠ duplicate charges
});

✅ Correct: idempotent processing keyed by event id

if (await _processed.TryClaim(e.Id, ttl: TimeSpan.FromDays(7)))
{
    await _orders.ChargeCard(e.Data.ToObjectFromJson<Order>());
}

❌ Wrong: ignoring subscription validation

Webhook returns 200 to validation event but doesn't echo the code. Subscription stuck in AwaitingManualAction.

✅ Correct: handle the handshake

Use SDK helpers (SubscriptionValidationEventData) or echo validationCode for native schema; respond to OPTIONS with WebHook-Allowed-Origin for CloudEvents.

❌ Wrong: relying on order

// "this OrderUpdated must come after OrderPlaced"

Event Grid doesn't guarantee order.

✅ Correct: use sequence numbers or Service Bus sessions

Include sequenceNumber in payload; reject out-of-order updates. Or use Service Bus sessions when ordering is required.


Design patterns for this topic

Pattern 1 — "System events as glue"

  • Intent: react to Azure resource changes (BlobCreated → Function processes file).

Pattern 2 — "Custom topic as fan-out hub"

  • Intent: many subscribers to one event with filtering.

Pattern 3 — "Event Grid → Service Bus for durable work"

  • Intent: combine fan-out + durable processing.

Pattern 4 — "DLQ + alert + replay"

  • Intent: never lose events; observe and recover.

Pattern 5 — "CloudEvents schema by default"

  • Intent: portable, cross-cloud-friendly.

Pros & cons / trade-offs

Aspect Pros Cons
Event Grid Push delivery; cheap; built-in retry + DLQ No replay; no ordering
System topics Free Azure-resource events Schema dictated by source
Custom topics Your domain events You own schema versioning
MQTT (Premium) IoT broker built-in Premium cost
CloudEvents Industry standard Slightly more verbose

When to use / when to avoid

  • Use for reactive integration to Azure resource changes.
  • Use for fan-out to multiple subscribers with per-subscriber filters.
  • Use as front-end before Service Bus when you want filtering + durability.
  • Avoid for high-throughput telemetry streaming — Event Hubs.
  • Avoid for FIFO ordered work — Service Bus sessions.
  • Avoid when you need replay — Event Hubs (or persist your own log).

Interview Q&A

Q1. Event Grid vs Service Bus vs Event Hubs? Event Grid: discrete events, push, fan-out. Service Bus: transactional work queue, FIFO via sessions. Event Hubs: high-throughput streaming with replay.

Q2. Topic types? System (Azure resources), Custom (your events), Domain (multi-topic), Partner (SaaS).

Q3. Subscription filters? Event types, subject begins/ends with, advanced filters (StringIn, NumberGreaterThan, BoolEquals) over payload.

Q4. CloudEvents vs native schema? CloudEvents v1.0 = CNCF standard with type, source, data, etc. Native = older Microsoft schema. Prefer CloudEvents.

Q5. Delivery semantics? At-least-once. Duplicates possible. Order not guaranteed.

Q6. Retry + DLQ? Exponential backoff up to maxDeliveryAttempts or TTL. Then dead-letter to Storage Blob.

Q7. Subscription validation? Event Grid sends validation event; your endpoint echoes the code (native schema) or responds to OPTIONS with allowed-origin header (CloudEvents).

Q8. Idempotency? Use event id as dedup key; store in Redis/Cosmos with TTL.

Q9. MQTT broker mode? Premium namespaces support MQTT 3.1.⅕ — IoT-class pub/sub.

Q10. Premium namespace benefits? Higher throughput, MQTT, pull-style delivery, topic spaces.

Q11. Auth from publisher? Managed identity preferred over access keys.

Q12. When pair with Service Bus? Event Grid for fan-out + filtering at edge; Service Bus for durable, transactional consumer-side processing.


Gotchas / common mistakes

  • ⚠️ No idempotency → duplicate side-effects on retry.
  • ⚠️ Subscription validation ignored → subscription never activates.
  • ⚠️ Assuming order — events may arrive out-of-order.
  • ⚠️ No DLQ configured — events vanish after retries.
  • ⚠️ Tight coupling on schema — version your event type strings.
  • ⚠️ Webhook open to internet without secret — anyone can spam it.
  • ⚠️ Using Event Grid for streaming telemetry — wrong tool; use Event Hubs.

Further reading