Domain 3 of 4 · Chapter 2 of 4

Implement event-driven workflows with Azure Event Grid

How Event Grid routes an event

A blob lands in a storage container at three in the morning and a thumbnail exists a second later, with no scheduled job and nothing polling. The piece that made that happen is Azure Event Grid, and the whole service reduces to one sentence: a publisher POSTs an event to a topic, and every event subscription attached to that topic whose filter matches gets its own copy of the event pushed to its own handler.

The previous page in this domain covered brokered messaging, where a receiver connects, pulls a message, holds a lock and then tells the broker how processing ended. This page covers the opposite arrangement: nobody connects to Event Grid to draw work from it, and the service makes the outbound call. Concretely, a delivery arrives at your code as an inbound HTTPS POST carrying a JSON array, and your handler answers it with an HTTP status code, which is the same shape as any web request handler you have already written. Getting that inversion straight first makes every later mechanic follow, because filtering, retries and dead-lettering all belong to the routing rule rather than to the consumer. By the end of this page you can pick the right topic kind, write a filter that delivers exactly the events one handler wants, stand up an endpoint that passes the ownership handshake, and set a retry and dead-letter policy that turns a handler outage into a recoverable backlog rather than silent loss.

The four parts, named the way Microsoft names them

Microsoft's push-delivery concepts article[1] defines the pieces, and it is worth using its words exactly because three of them collide with words used elsewhere in Azure.

A publisher is "the application that sends events to Event Grid". A topic "holds events that you publish to Event Grid", and comes in three kinds: custom topics for your own applications, which "as a self-standing resource, they expose their own endpoint to which you publish events"; system topics, which are "built-in topics provided by Azure services such as Azure Storage, Azure Event Hubs, and Azure Service Bus"; and partner topics, for events published by a non-Microsoft SaaS system. An event subscription "tells Event Grid which events on a topic you're interested in receiving", and when you create one "you provide an endpoint for handling the event". An event handler "is the place where the event is sent".

Those four parts line up left to right in the figure below, which traces one event through them: a publisher POSTs, the topic routes to each matching event subscription, and each event subscription delivers to its own handler, whose status code comes back the other way.

Three words that mean something else one page over

The word subscription carries three unrelated meanings in this exam, and a stem that uses it bare is usually testing whether you noticed. An Event Grid event subscription is a routing rule: a filter, a destination endpoint and a set of delivery settings. It holds nothing and stores nothing. A Service Bus subscription is a named child entity of a topic that holds its own copy of every matching message and is what consumers actually read. An Azure subscription is the billing and resource container that both of them live inside. This page writes "event subscription" in full wherever the sense could be misread, and you should too when reading a question.

The word topic is overloaded across the same two services. A Service Bus topic is the publish side of an entity whose subscriptions hold the messages; an Event Grid topic is an addressable HTTPS endpoint that accepts a POST and routes onward. Neither is something a consumer reads directly, which is the one property they share.

The word acknowledge belongs to Event Grid and not to Service Bus. Event Grid "uses HTTP response codes to acknowledge receipt of events", per the delivery and retry article[2]. Service Bus has no acknowledgement verb: it has settlement, with the four outcomes complete, abandon, dead-letter and defer. Using "ack" for a Service Bus receive is the sort of vocabulary slip that makes a distractor look right.

Events are not messages, and the distinction has consequences

Microsoft states the difference plainly on the messaging services comparison[3]. An event is "a lightweight notification of a condition or state change" where "the publisher has no expectation about how the event is handled". A message is "raw data produced by a service to be consumed or stored elsewhere" for which "a contract exists between publisher and consumer".

That is not vocabulary pedantry, it is a design constraint. Because the publisher expects nothing, an Event Grid publisher gets no back pressure, no reply and no confirmation that anyone acted. If your design needs the producer to know that the work completed, you are describing a message and you want the queue on the sibling page, not a topic here.

What Event Grid guarantees, and what it does not

Event Grid "provides durable delivery. It tries to deliver each message at least once for each matching subscription", and the same article carries the blunt companion note: "Event Grid doesn't guarantee order for event delivery, so subscribers might receive events out of order." The comparison table records the same two facts in one row each: delivery guarantee "At least once", ordering "No guarantee", duplicate detection "No".

At-least-once with no duplicate detection means your handler will eventually run twice on one event. There is no setting that changes this, and the retry mechanics in the last section make it concrete: if your handler takes longer than 30 seconds to respond, Event Grid has already queued a retry, and although it "attempts to remove the event from the retry queue on a best effort basis" when a late response arrives within three minutes, "duplicates might still be received". Being safe to run twice on the same event (idempotency) is therefore your job, not the service's. Key it off the event's id field, which the publisher must supply and which the next section covers.

It is worth noting what Service Bus offers here by contrast, because the comparison table's Service Bus cell reads "At least once (optional ordered, exactly once with sessions)" while Microsoft's own duplicate-detection and message-sessions articles never use the phrase "exactly once" at all. That inconsistency is Service Bus's problem, not Event Grid's, and it is discussed on the sibling page. On the Event Grid side there is nothing to weigh up: at-least-once is the only guarantee published, so idempotent handlers are not a precaution, they are the design.

One boundary before the mechanics

Everything on this page is push delivery, the model used by custom topics, system topics, partner topics and domains. Event Grid also offers pull delivery over namespace topics, where "your application connects to Event Grid to read CloudEvents using queue-like semantics" with lock tokens and explicit acknowledge, release and reject operations, as the pull delivery overview[4] describes. Pull delivery is a different resource model with a different concepts article, and none of the retry, dead-letter or handshake behaviour below applies to it unchanged. When a question mentions lock tokens or a consumer that reads at its own pace, it has left this page.

1 Publish2 Route3 DeliverPublisherHTTPS POSTTopicEvent subscription Afilter + endpointeach rule gets its own copyEvent subscription Bfilter + endpointHandler AHandler BDashed: the handler HTTP status code, the only acknowledgement Event Grid receives.
Publish, route, deliver: one POST becomes one copy per matching event subscription, each pushed to its own handler.

Publishing: the endpoint, the credential, the envelope

Publishing to Event Grid is an HTTPS POST of a JSON array to a URL, with one header for the credential. There is no client connection to keep alive and no broker session, which is why a publisher can be a shell script as easily as an application.

This section covers the three decisions a publisher makes: where to send, how to authenticate, and which envelope format to use.

The topic endpoint

A custom topic exposes its own endpoint. For custom topics, domains and partner namespaces the URL takes the form https://<yourtopic>.<region>.eventgrid.azure.net/api/events, as documented on the access key and SAS article[5]. You never construct it by hand; read it off the topic's Overview page in the portal or with the CLI.

Events are always published in an array. Microsoft is explicit about this on the concepts page: "When you use a custom topic, you must always publish events in an array. It can be a batch of one for low-throughput scenarios." The array can total up to 1 MB and each event in it is limited to 1 MB; exceed either and, per the event schema article[6], "you receive the response 413 Payload Too Large".

Three ways to authenticate, and the one Microsoft recommends

There are three publish-side credentials, and the choice is a security decision rather than a functional one, because all three reach the same endpoint.

The access key is the simplest: pass the topic's key as the value of the aeg-sas-key HTTP header, or as a query parameter of the same name. The SAS token adds an expiry and a resource scope: a token is the URL-encoded string r={resource}&e={expiration_utc}&s={signature}, presented either in the aeg-sas-token header or as an Authorization: SharedAccessSignature header. A SAS token "is valid for all resources prefixed with the resource URI used in the signature string", so scoping it narrowly matters.

Microsoft Entra ID is the third and the recommended one. The access-key article opens with the instruction directly: "Authenticating and authorizing users or applications using Microsoft Entra identities provides superior security and ease of use over key-based and shared access signatures (SAS) authentication ... We strongly recommend using Microsoft Entra ID with your applications." The mechanism is a role assignment. Per the Entra ID publishing article[7], "the security principal used by a client application that sends events to Event Grid must have the RBAC role EventGrid Data Sender associated with it", and more precisely Event Grid "validates that the identity has the Microsoft.EventGrid/events/send/action permission in an RBAC role associated to the identity before allowing the event publishing request to complete". RBAC here expands to role-based access control. Microsoft's own prose writes the role name as both "EventGrid Data Sender" and "Event Grid Data Sender" on that one page; they are the same built-in role, and the permission string above is the unambiguous way to name it.

The identity can be a managed identity on the hosting service (a virtual machine, an App Service, a function app) or an application service principal you registered. Once you commit to Entra ID you can shut the other doors: the API parameter disableLocalAuth turns off key and SAS authentication on the topic entirely.

Publishing one event with the access key. The following is Microsoft's own CLI test from the filtering how-to[8], reduced to a single event. It shows the array wrapper, the aeg-sas-key header named in the paragraph above, and the five fields the next subsection explains.

topicEndpoint=$(az eventgrid topic show --name $topicName -g gridResourceGroup --query "endpoint" --output tsv)
key=$(az eventgrid topic key list --name $topicName -g gridResourceGroup --query "key1" --output tsv)

# The payload is an ARRAY even for one event, and dataVersion is the publisher's own
# version stamp for the shape of "data" (optional; see the field table below).
event='[ {"id": "'"$RANDOM"'", "eventType": "recordInserted", "subject": "myapp/vehicles/cars", "eventTime": "'`date +%Y-%m-%dT%H:%M:%S%z`'", "data":{ "model": "SUV", "color": "green"},"dataVersion": "1.0"} ]'

curl -X POST -H "aeg-sas-key: $key" -d "$event" $topicEndpoint

The Event Grid schema, field by field

The native envelope is small and fixed. The event schema article[6] publishes this table of top-level properties, and the Required column is the part worth memorising.

Property Required Who supplies it
subject Yes Publisher-defined path to the event subject
eventType Yes One of the registered event types for this event source
eventTime Yes The time the event is generated, in the provider's UTC time
id Yes Unique identifier for the event
data Yes Event data specific to the resource provider; for custom topics the publisher defines its structure
dataVersion "No, but will be stamped with an empty value" The publisher defines the schema version of the data object
topic No; if included it must match the topic's Azure Resource Manager ID exactly, otherwise Event Grid stamps it "This field isn't writeable. Event Grid provides this value."
metadataVersion No; if included it must match exactly, currently only 1, otherwise Event Grid stamps it Event Grid provides this value

Five fields are yours to fill and three are effectively the service's. The two you should set deliberately rather than mechanically are subject and eventType, because those are what subscribers filter on. Microsoft's guidance is to treat the subject as a path: "if you provide a three segment path like /A/B/C in the subject, subscribers can filter by the first segment /A to get a broad set of events". Storage does exactly that, publishing /blobServices/default/containers/<container-name>/blobs/<file>, which is why a .jpg suffix filter works at all.

CloudEvents, and the two schema knobs people conflate

Event Grid also speaks CloudEvents 1.0, which the concepts article describes as the "Cloud Native Computing Foundation (CNCF) open standard ... specification with the HTTP protocol binding and the JSON format". Microsoft is now steering people toward it: "The support for Event Grid event schema isn't going to be retired, but we won't be making any major improvements to it in the future. We recommend that you use CloudEvents schema." Note the split of authority here. The CloudEvents specification defines the format and its attribute names; what the Azure service accepts and emits is defined by Microsoft, and the CloudEvents integration article[9] is the citable source for the latter. Of the specification's three content modes, Event Grid's concepts article records structured JSON as supported and binary as not.

The part that trips people is that there are two separate knobs, not one. The input schema is fixed when you create the topic, with --input-schema on az eventgrid topic create. The output schema, also called the delivery schema, is set per event subscription with --event-delivery-schema. So a single topic can accept the Event Grid format and deliver CloudEvents to one subscriber, and a claim that "the schema is chosen once, at the topic" is only half true.

Not every combination is legal, and the reason is worth knowing:

Input schema Output schema
CloudEvents format CloudEvents format
Event Grid format CloudEvents format
Event Grid format Event Grid format

The missing fourth row is CloudEvents in, Event Grid out. Microsoft explains that it "can't be used ... because CloudEvents supports extension attributes that aren't supported by the Event Grid schema": the older envelope has nowhere to put them, so the conversion would lose data. That is a one-way constraint you cannot configure around, and it is the reason the input schema is the more consequential of the two knobs: a topic whose input schema is CloudEvents cannot deliver the Event Grid format to any event subscription on it.

Filtering: one topic, handlers that want different things

Two teams subscribe to the same storage account. One only wants new JPEGs in one container; the other wants every event for audit. Neither team can change what the publisher sends, and neither should have to filter in code and throw most invocations away. Filtering on the event subscription is how both get exactly their slice from one topic, and the figure below shows that shape with three rules on one topic.

Microsoft's event filtering article[10] enumerates the options up front: "When creating an event subscription, you have three options for filtering: Event types, Subject begins with or ends with, Advanced fields and operators." Take them in that order, because that is also increasing cost of understanding and decreasing chance of a surprise.

Event type: the filter you get wrong by omission

"By default, all event types for the event source are sent to the endpoint." You narrow that with includedEventTypes, an array of type names, and you can also pass the literal All to be explicit about wanting everything. The Azure Resource Manager reference for event subscriptions[11] states the same rule from the other direction: "If it is desired to subscribe to all default event types, set the IncludedEventTypes to null."

The defect this produces is silent. An event subscription created without the list works perfectly in a demo where only one kind of event exists, then starts invoking the handler for deletions and metadata changes the day someone else uses the storage account. Set the list even when there is only one type today.

Subject: prefix and suffix, and nothing else

Subject filtering is a plain string comparison against the event's subject, bounded at either end. subjectBeginsWith is "an optional string to filter events for an event subscription based on a resource path prefix" and subjectEndsWith does the same for a suffix. Both carry the same caveat in the reference: "Wildcard characters are not supported in this path." There is no contains, no regular expression and no glob. If you find yourself wanting one, you have reached the advanced filters below.

Case handling is a separate flag, isSubjectCaseSensitive, described as specifying "if the SubjectBeginsWith and SubjectEndsWith properties of the filter should be compared in a case sensitive manner", and surfaced in the portal as "Case-sensitive subject matching". Microsoft's own Resource Manager examples set it to false explicitly, which is a reasonable habit to copy: state it rather than inherit it.

Because storage subjects are paths, prefix filters are how you scope to a container or a folder. Microsoft's worked forms are worth reading as a set, since each one narrows the previous:

Goal Filter
All events for the storage account Leave the subject filters empty
Blobs in containers sharing a prefix subjectBeginsWith of /blobServices/default/containers/containerprefix
Blobs in one specific container subjectBeginsWith of /blobServices/default/containers/containername/
Blobs in a subfolder of a container subjectBeginsWith of /blobServices/default/containers/{containername}/blobs/{subfolder}/
Blobs sharing a file extension subjectEndsWith of .log or .jpg

The trailing slash in the third row is doing real work. Without it, a filter for containers/report also matches containers/reports-archive.

Advanced filters: content-based routing

Advanced filters test a named field with a comparison operator, and unlike the two above they can reach inside the payload. You specify three things: the operator type, the key ("the field in the event data that you're using for filtering"), and the value or values. Keys reach into data with dot notation, so data.siteName and data.appEventTypeDetail.action are both addressable, and for events in the Event Grid schema you may also key on ID, Topic, Subject, EventType or DataVersion. For CloudEvents the corresponding keys are id, source, type and dataschema, plus event data.

The operator set is closed and worth knowing by family rather than by memorising every member. Numbers get NumberIn, NumberNotIn, NumberLessThan, NumberGreaterThan, NumberLessThanOrEquals, NumberGreaterThanOrEquals, NumberInRange and NumberNotInRange. Booleans get BoolEquals and nothing else. Strings get StringIn, StringNotIn, StringContains, StringNotContains, StringBeginsWith, StringNotBeginsWith, StringEndsWith and StringNotEndsWith. Two operators stand outside the type families: IsNullOrUndefined and IsNotNull, which test presence rather than value.

Three behaviours decide whether a filter you wrote does what you meant:

Combination is asymmetric, and Microsoft states it directly. "If you specify a single filter with multiple values, an OR operation is performed, so the value of the key field must be one of these values." And: "If you specify multiple different filters, an AND operation is done, so each filter condition must be met." So one StringIn with three values is a three-way OR, while two separate StringContains filters must both hold. The same key may appear in more than one filter, which is how you build a range or an intersection.

Case is not honoured. "All string comparisons aren't case-sensitive." There is no flag for advanced filters, unlike subject filtering.

A missing key does not fail uniformly. If the event JSON has no such key, the filter "is evaluated as not matched" for the positive operators (NumberGreaterThan, NumberGreaterThanOrEquals, NumberLessThan, NumberLessThanOrEquals, NumberIn, BoolEquals, StringContains, StringNotContains, StringBeginsWith, StringNotBeginsWith, StringEndsWith, StringNotEndsWith, StringIn) and as matched for NumberNotIn and StringNotIn. A negative filter therefore lets absent-key events through, which is either exactly what you wanted or a leak, depending on whether you thought about it.

Arrays need opting in. A key holding an array is only evaluated when the event subscription sets enableAdvancedFilteringOnArrays to true, and even then "Event Grid doesn't support filtering on an array of objects. It only allows String, Boolean, Numbers, and Array of the same types".

The published limits

Advanced filtering is bounded, and the bounds are per event subscription: "25 advanced filters and 25 filter values across all the filters per Event Grid subscription" and "512 characters per string value". One more restriction catches people building filters over identity claims or email addresses: keys containing a dot character are not supported, because there is currently no escape syntax, so a key such as john.doe@contoso.com cannot be filtered on.

What Microsoft does not say about combining the three kinds

All three filter kinds live together in one filter object on the event subscription, and Microsoft's own Resource Manager templates set subjectBeginsWith, subjectEndsWith, isSubjectCaseSensitive and includedEventTypes side by side in a single event subscription. Each is documented as limiting what is delivered. What the filtering articles state explicitly, though, is only the AND rule among multiple advanced filters; they do not publish a combination rule across the three kinds, and they publish no evaluation order. Treat the practical reading (set several, and each one narrows) as sound engineering practice while knowing that the AND wording you can quote applies to advanced filters. If a question turns on the composition of an event-type filter with a subject filter, it is resting on an inference rather than on a published sentence.

Topicone publishThumbnail subscriptionincludedEventTypes: BlobCreatedsubjectEndsWith: .jpgAudit subscriptionincludedEventTypes omittedevery type is deliveredPriority subscriptionadvanced filter on data.colorStringIn: red, amberThumbnailerAudit sinkPriority queueOne publish; each event subscription filter decides whether that rule gets a copy.
Filtering lives on the rule, not on the topic: three event subscriptions on one topic, each with its own criteria and handler.

Where an event can be delivered

You have written the thumbnailer from the previous section and it is running somewhere. Where you put it decides how much wiring you still owe: drop it in a function with the Event Grid trigger and the event subscription starts delivering the moment you create it, or expose it as an HTTPS endpoint of your own and Event Grid will not send it a single event until the endpoint has proved it wants them. This section covers the first half of that fork, the destinations Microsoft supports; the next section covers the obligation the second half carries.

The handlers Microsoft publishes

The event handlers article[12] defines a handler as "the destination for an event. The handler takes some action to process the event", and lists the supported set in full. It is a closed list, so it is worth reproducing rather than summarising: webhooks (with the note that "Azure Automation runbooks and Logic Apps are supported via webhooks"), Azure functions, Event Hubs, Service Bus queues and topics, Relay hybrid connections, Storage queues, Azure Monitor alerts from an Azure Key Vault source only, and Event Grid namespace topics. The Resource Manager reference lists the same set as endpointType values on the event subscription's destination: AzureFunction, EventHub, HybridConnection, MonitorAlert, NamespaceTopic, PartnerDestination, ServiceBusQueue, ServiceBusTopic, StorageQueue and WebHook.

Two things follow that are easy to get backwards. Logic Apps is on the list as a webhook rather than as a first-class destination type, so a stem that offers "Logic Apps" and "webhook" as separate answers is describing one mechanism twice. And a Storage queue is a legitimate handler, which is not the same as being a legitimate dead-letter target; the failure section below returns to that asymmetry, because it is a favourite distractor.

One blanket constraint applies to every webhook: "Event Grid only supports HTTPS webhook endpoints." There is no plaintext option, in any tier or region.

Two classes, and why the split matters

Read the list again and it sorts into two classes. Most entries are Azure services that Event Grid already knows how to reach and authenticate to, using either its own service-principal access to the resource or a managed identity you enable on the topic. The remaining entry, the generic webhook, is a URL Event Grid has never seen and cannot assume anything about.

That difference is the whole reason the next section exists. Delivering to a Service Bus queue is a question of permissions, which you solve with a role assignment. Delivering to https://api.contoso.com/events is a question of consent, which no role assignment can answer, because the point is that the owner of that URL may never have asked for the traffic in the first place.

The practical takeaway when you are choosing: picking an Azure service from the list costs you a role assignment and nothing else, while picking your own endpoint costs you a handshake implementation you must get right before a single event flows.

Proving a webhook wants the traffic

Microsoft states the reasoning without hedging: "Event Grid requires you to prove ownership of your webhook endpoint before it starts delivering events to that endpoint. This requirement prevents a malicious user from flooding your endpoint with events." Without a handshake, anyone who learns your URL could create a topic and an event subscription pointing at it and use Event Grid as an amplifier. This section covers who is excused, the two handshake flavours, and which one you get.

Three services are excused, and the third one has a catch

Per the Event Grid schema validation article[13]: "When you use any of the following three Azure services, the Azure infrastructure automatically handles this validation: Azure Logic Apps with Event Grid Connector, Azure Automation via webhook, Azure Functions with Event Grid Trigger." That list is exactly three long.

Note the precision on the third one: it is a function using the Event Grid trigger. "If you're using any other type of endpoint, such as an HTTP trigger based Azure function, your endpoint code needs to participate in a validation handshake." A function is not automatically exempt; the binding is what exempts it. The trigger and binding mechanics themselves belong to the Functions page; what matters here is that the choice of binding decides whether you owe a handshake.

The Event Grid schema handshake

When the event subscription's output schema is the Event Grid schema, validation happens through an event. On create or update, Event Grid POSTs a subscription validation event to the endpoint. It arrives with the header aeg-event-type: SubscriptionValidation, its eventType property is Microsoft.EventGrid.SubscriptionValidationEvent, and its data object carries a validationCode (a randomly generated string) and a validationUrl. "The array contains only the validation event. Other events are sent in a separate request after you echo back the validation code."

There are two ways to complete it, and the article names them synchronous and asynchronous, the latter also described as "a manual validation handshake".

The synchronous path is the one you write code for. Your endpoint returns the code it received in a validationResponse property:

The synchronous validation response body. This is the entire response your endpoint sends back to the POST described in the paragraph above; the value of validationResponse is the validationCode verbatim from the request's data object.

{
  "validationResponse": "512d38b6-c7b8-40c8-89fe-f46f9e9622b6"
}

The status code you return alongside it is not a free choice, and this is the single most surprising rule on the page: "You must return an HTTP 200 OK response status code. HTTP 202 Accepted isn't recognized as a valid Event Grid subscription validation response." A 202 is a perfectly good answer to a delivered event, as the failure section below shows, and it is not a valid answer here. The request must also complete within 30 seconds; if it does not, "the operation is canceled and reattempted after 5 seconds", and if every attempt fails the handshake errors out.

The asynchronous path exists for endpoints that cannot reply programmatically, such as a third-party automation service. Event Grid puts a validationUrl in the event data and you complete the handshake by issuing a GET to it from a browser or a REST client. Three details decide whether this works. The URL "is valid for 10 minutes", during which "the provisioning state of the event subscription is AwaitingManualAction"; miss the window and "the provisioning state is set to Failed" and you must recreate the event subscription. The endpoint still has to return 200 to the original POST, because "if the endpoint returns 200 but doesn't return back a validation response synchronously, the mode is transitioned to the manual validation mode". And the validation URL "uses port 553", so a firewall that only permits 443 outbound will silently prevent the manual handshake.

Two more constraints belong with this: "Using self-signed certificates for validation isn't supported. Use a signed certificate from a commercial certificate authority (CA) instead." And the handshake proves ownership, not identity, so a determined attacker who has seen a real request could replay it; Microsoft's own answer is to put Microsoft Entra authentication in front of the webhook as well.

The CloudEvents handshake

Change the output schema and you change the handshake. "When you use the CloudEvents schema for output, Event Grid uses the CloudEvents v1.0 abuse protection in place of the Event Grid validation event mechanism", per the CloudEvents endpoint validation article[14]. No validation event is sent and there is no code to echo.

The exchange uses HTTP OPTIONS instead. "The validation request uses the HTTP OPTIONS method. The request goes to the exact resource target URI that you're registering." It carries a WebHook-Request-Origin header, which "MUST be included in the validation request and requests permission to send notifications from this sender, and contains a Domain Name System (DNS) expression that identifies the sending system". The endpoint consents by replying with WebHook-Allowed-Origin, whose "value MUST either be the origin name supplied in the WebHook-Request-Origin header, or a singular asterisk character ('*'), indicating that the delivery target supports notifications from all origins". After consent, "the sender MUST use the Origin request header for each delivery request".

The figure below puts the two exchanges side by side. What the diagram cannot show is the honest caveat Microsoft attaches: "It's important to understand that the handshake doesn't aim to establish an authentication or authorization context. It only serves to protect the sender from being told to a push to a destination that isn't expecting the traffic." Consent is not authentication, in either flavour.

One consequence for reading questions: the handshake you face is decided by the event subscription's output schema, not by what your endpoint prefers. Flip an event subscription to CloudEvents delivery and an endpoint that was correctly echoing validation codes will now be asked an OPTIONS question it has never handled.

Event Grid schema outputEvent GridPOST SubscriptionValidationEventvalidationResponse, HTTP 200Your HTTPS endpointDelivery beginsCloudEvents schema outputEvent GridOPTIONS + WebHook-Request-OriginWebHook-Allowed-OriginYour HTTPS endpointDelivery beginsThree Azure services skip both handshakes; the output schema picks which one applies.
The event subscription's output schema, not the endpoint, decides which ownership handshake Event Grid runs.

When delivery fails: codes, retries, dead-letters

Your handler is down for twenty minutes. Whether the events that arrived in that window are waiting for you, sitting in a blob container, or gone forever is decided by three settings and one number your code returns. This section takes them in the order Event Grid evaluates them, which is the order the figure below traces.

Only five status codes mean success

"Event Grid uses HTTP response codes to acknowledge receipt of events", and the delivery and retry article[2] is unusually absolute about which ones count: "Event Grid considers only the following HTTP response codes as successful deliveries. All other status codes are failed deliveries." The set is 200 OK, 201 Created, 202 Accepted, 203 Non-Authoritative Information and 204 No Content. Anything else is a failure: "Event Grid considers all status codes outside the range of 200-204 as failures."

That makes 206 Partial Content a failed delivery, and it is worth pausing there because Azure will also tell you the opposite in a different context. Application Insights, monitoring the same handler, will record a 206 response as a success: its telemetry data model[15] "defines a request as successful when the response code is less than 400 or equal to 401", so a 206 lands in the requests table with Success set to true. Both statements are correct, and they are not about the same thing. Event Grid is answering "was my delivery acknowledged?" and its answer for 206 is no. Application Insights is answering "should this request count against my failure rate?" and its default answer for 206 is no failure, with an explicit caveat on the same page that "partially accepted content 206 might indicate a failure of an overall request" and that "an increasing rate of 206 indicates a problem that needs to be investigated". A handler that returns 206 will therefore look healthy in your telemetry while Event Grid retries and eventually dead-letters every event it touched. When a question asks whether 206 is a success, the answer depends entirely on which product is asking, so read the stem for the subject of the sentence.

One timing rule sits alongside the codes: "Event Grid waits 30 seconds for a response after delivering a message. After 30 seconds, if the endpoint doesn't respond, Event Grid queues the message for retry." Long-running work belongs behind the acknowledgement, not in front of it.

Which failures are worth retrying

Not every failure gets a second attempt, because some cannot succeed later. "If the subscribed endpoint returns a configuration-related error that can't be fixed with retries (for example, if the endpoint is deleted), Event Grid either dead-letters the event or drops the event if dead-lettering isn't configured." The non-retriable set depends on the endpoint type:

Endpoint type Codes that are not retried
Azure Resources 400 (Bad request), 413 (Request entity is too large), 403 (Forbidden)
Webhook 400 (Bad request), 413 (Request entity is too large), 401 (Unauthorized), 403 (Forbidden)

The 401 row is the interesting one, and the per-code table on the same page confirms the asymmetry rather than contradicting it:

Status code Retry behavior
400 Bad Request Not retried
401 Unauthorized Retry after 5 minutes or more for Azure Resources Endpoints
403 Forbidden Not retried
404 Not Found Retry after 5 minutes or more for Azure Resources Endpoints
408 Request Timeout Retry after 2 minutes or more
413 Request Entity Too Large Not retried
503 Service Unavailable Retry after 30 seconds or more
All others Retry after 10 seconds or more

Read the two tables together: 401 is retried for an Azure resource endpoint and abandoned for a webhook, because a webhook that rejects the credential will keep rejecting it. So "is 401 retriable?" has no answer without knowing the endpoint type. Microsoft adds one honesty note about all of this: "Because of the highly parallelized nature of Event Grid's architecture, the retry behavior is nondeterministic."

The retry schedule you cannot change

Everything that is retried follows one published schedule, described as "an exponential backoff retry policy" applied "on a best effort basis": 10 seconds, 30 seconds, 1 minute, 5 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, and then every 12 hours up to 24 hours. Event Grid "adds a small randomization to all retry steps and might opportunistically skip certain retries if an endpoint is consistently unhealthy, down for a long period, or appears to be overwhelmed". The how-to article states the limit on your control in one line: "You can't configure the retry schedule."

Related, and often mistaken for a bug: delayed delivery. "As an endpoint experiences delivery failures, Event Grid begins to delay the delivery and retry of events to that endpoint. For example, if the first 10 events published to an endpoint fail, Event Grid assumes that the endpoint is experiencing problems and delays all subsequent retries and new deliveries for some time - in some cases, up to several hours." An event subscription in that state is recorded with the last delivery outcome Probation, and while in probation "events might get dead-lettered or dropped without even trying delivery". A handler that comes back up is therefore not immediately busy again.

The two bounds you do control

"You can customize the retry policy when creating an event subscription by using the following two configurations": maximum number of attempts, "an integer between 1 and 30. The default value is 30", and event time-to-live, "an integer between 1 and 1440. The default value is 1440 minutes". The how-to summarises the pair as "By default, Event Grid tries for 24 hours (1,440 minutes), or 30 times."

These are the same two axes the sibling page teaches for Service Bus, where a delivery count bounds attempts and a time to live bounds the clock, and they behave the same way here: one counts tries, the other counts wall-clock time from publication, and they fail an event for different reasons. What is specific to Event Grid is the arbitration: "If you set both Event time to live (TTL) and Maximum number of attempts, Event Grid uses the first to expire to determine when to stop event delivery." Microsoft's own worked example is the one to remember, because it shows the setting that does nothing: with a 30-minute TTL and 10 maximum attempts, the fixed schedule only fits six attempts into 30 minutes, so "setting max number of attempts to 10 has no impact in this case and Event Grid dead-letters events after 30 minutes". Raising the attempt count cannot buy time the TTL will not grant.

One more mechanism affects when the clock is read: "The time-to-live expiration is checked only at the next scheduled delivery attempt." An event whose TTL passed during a six-hour gap in the schedule is not dead-lettered at the moment of expiry; it is dead-lettered when the next attempt comes due.

A note on a number you may meet elsewhere. The Key Vault secret rotation tutorial[16] states, in a note about the lag between rotating a secret and updating the database, that "If any step fails, Event Grid retries for two hours." The Event Grid delivery articles give the defaults quoted above, 1440 minutes and 30 attempts, and nothing on either page reconciles the two. Neither figure is a safe thing to key an answer on: the tutorial's number is scenario prose in a Key Vault article, and the 1440-minute figure is a default that any event subscription can override to anything from 1 to 1440 minutes. The reliable statement is the mechanism, not the number: the retry window is whatever that event subscription's own retry policy says, and you read it off the event subscription.

Where undelivered events land

Dead-lettering is off until you turn it on. "By default, Event Grid doesn't turn on dead-lettering. To enable it, specify a storage account to hold undelivered events when creating the event subscription." Until you do, the failure path ends in deletion: events that exhaust the policy, and events that hit a non-retriable code, are simply dropped.

The destination is a blob container and only a blob container. You point at it with --deadletter-endpoint in the form $storageid/blobServices/default/containers/$containername, and the Resource Manager reference offers exactly one dead-letter type, StorageBlob, carrying a blobContainerName and the storage account's resourceId. A Storage queue is a valid event handler, as the destinations section above listed, and is not a valid dead-letter destination; that asymmetry is a favourite distractor.

Four operational details, from the dead-letter and retry how-to[17], decide whether dead-lettering works when you need it:

The container must exist first. "You need to create a storage account and a blob container in the storage before running commands in this article." Event Grid does not create it, and "dead-lettered events are dropped when the dead-letter destination isn't found".

The blob names are not what you would guess. "The names of blobs contain the name of the Event Grid subscription with all the letters in upper case. For example, if the name of the subscription is My-Blob-Subscription, names of the dead letter blobs contain MY-BLOB-SUBSCRIPTION", laid out as myblobcontainer/MY-BLOB-SUBSCRIPTION/YYYY/MM/DD/HH/<guid>.json using the non-zero-padded UTC date and hour. Microsoft explains the shouting: "This behavior is to protect against differences in case handling between Azure services." Each blob "contains one or more events in an array", so a reader that assumes one event per blob will drop data.

The write is not instant, and it is not guaranteed forever. "There's a five-minute delay between the last attempt to deliver an event and when it's delivered to the dead-letter location", and "if the dead-letter location is unavailable for four hours, the event is dropped."

The managed identity is optional. "You can optionally enable a system-assigned or user-assigned managed identity for dead-lettering", and only when you do does the role assignment matter: "The managed identity must be a member of a role-based access control (RBAC) role that allows writing events to the storage." Without an identity, Event Grid uses its own service principal's key-based access to the storage account. Assuming an identity is mandatory is a common wrong turn; so is enabling one and forgetting the role assignment, which produces silent dead-letter loss.

Handler returns a status code200, 201, 202, 203 or 204?yesDeliveredEvent Grid stops herenoNon-retriable for this endpoint?400, 403, 413; 401 on a webhookyesRetrying stops immediatelynoAttempts or time-to-livereached, whichever first?noRetry on the fixedback-off scheduleyesDead-letter destination configured?yesWritten to thedead-letter containernoEvent is dropped
The delivery outcome path: five success codes, a non-retriable shortcut, the two bounds, then dead-letter or drop.

What the exam does with this

The questions on this topic cluster around a small number of confusions, and almost all of them are cases where two true statements share a word. Here is what each one looks like from the inside.

A stem that says "subscription" without qualifying it. Read the rest of the sentence for which of the three it means. If the thing holds messages and consumers read it, that is Service Bus. If the thing routes and holds nothing, that is an Event Grid event subscription. If the thing contains resource groups, that is an Azure subscription. Answers that describe an Event Grid event subscription as storing events until a consumer collects them are describing the wrong service; nothing reads a topic and nothing reads an event subscription.

A handler that returns 202 Accepted. Correct for a delivered event, because 202 is one of the five success codes. Wrong for the validation handshake, where Microsoft states that "HTTP 202 Accepted isn't recognized as a valid Event Grid subscription validation response" and only 200 works. The same code, two operations, two verdicts.

A handler that returns 206. A failed delivery for Event Grid, and a success in Application Insights telemetry. Both are true because they answer different questions, which the failure section works through. Look at which product the stem is asking about.

"How long does Event Grid retry?" There is no single published answer to memorise, and the documentation is genuinely inconsistent: a Key Vault rotation tutorial says two hours while the Event Grid delivery articles give defaults of 1440 minutes and 30 attempts. The defensible answer is the mechanism: retries stop at the maximum number of attempts (1 to 30, default 30) or at the event time-to-live (1 to 1440 minutes, default 1440), whichever expires first, on a fixed back-off schedule you cannot configure.

Raising the maximum delivery attempts to get a longer retry window. It does not work if the time-to-live expires first, which is Microsoft's own worked example: with a 30-minute TTL, only six attempts fit, so setting the attempt limit to 10 changes nothing. Whenever a question offers both knobs, work out which one binds.

A dead-letter destination that is a Storage queue. Storage queues are supported handlers and are not supported dead-letter destinations. The only dead-letter type is a blob container in a storage account, and it must already exist before the event subscription is created.

"Enable a managed identity so dead-lettering works." The identity is optional. Dead-lettering works without one, using Event Grid's own access to the storage account. The role assignment becomes mandatory only once you have enabled an identity, and forgetting it at that point is what actually loses the dead-lettered events.

A webhook that never receives anything. Work down the handshake list before suspecting the filter. Is the endpoint HTTPS? Is its certificate from a commercial certificate authority rather than self-signed? Did it echo the validationCode in a validationResponse and return 200 within 30 seconds? If it is on the manual path, was the validationUrl fetched within 10 minutes, over port 553? And if the event subscription delivers CloudEvents, none of that applies and the endpoint needed to answer an HTTP OPTIONS request with WebHook-Allowed-Origin instead.

An Azure function that still needs the handshake. Only three services are exempt, and the exemption for Functions is specifically for the Event Grid trigger. An HTTP-triggered function subscribed as a generic webhook is on the hook for the handshake like any other endpoint, which Microsoft calls out by name.

An event subscription that receives more than expected. Look for a missing includedEventTypes, because the default is every event type for the source. If a filter is present but too loose, check whether a subjectBeginsWith is missing its trailing slash, and whether a StringNotIn advanced filter is letting through events that lack the key entirely, since absent keys evaluate as matched for the two negative operators.

Ordering, duplicates, and "exactly once". Event Grid publishes at-least-once delivery, no ordering guarantee, and no duplicate detection. Any answer that promises single delivery or preserved order for Event Grid is wrong, and the fix in every scenario is either an idempotent handler or a different service: Service Bus sessions for ordered processing of related items, Event Hubs for per-partition ordering of a stream.

Where this page stops. The mechanics of consuming an event inside a function, meaning the trigger, the binding and the parameter types, belong to the Functions page, and hosting and deployment of that function app belong to its neighbour. Which Key Vault events exist and what drives rotation belong to the Key Vault page. This page owns Event Grid's half of those contracts: what it delivers, in which schema, and with what retry and dead-letter behaviour when the other side does not answer.

The three filter kinds on an event subscription

CriterionEvent type (includedEventTypes)Subject prefix/suffixAdvanced filters
What it inspectsThe event's type field, against a list of accepted valuesThe event's subject string, at its start or its endAny named key in the event, including fields inside data
How you express itAn array of event types, or the value AllsubjectBeginsWith and subjectEndsWith; wildcard characters are not supportedAn operator type, a key, and one value or a list of values
Operators availableMembership of the list onlyPrefix and suffix matching onlyNumber, boolean and string operators plus IsNullOrUndefined and IsNotNull
Case handlingNot documented as configurableAn isSubjectCaseSensitive flag, shown as Case-sensitive subject matching in the portalString comparisons are not case-sensitive
Combining rule Microsoft statesAny one listed type matchesEach bound is a single string; no operator combines themSeveral values in one filter are OR; several different filters are AND
Published limitsNone published for the list itselfNone published for prefix or suffix length25 filters and 25 values across all filters per subscription; 512 characters per string value
If you leave it outAll event types for the source are deliveredThe subject is not narrowedNo content-based narrowing is applied

Decision tree

What produces the event?An Azure serviceSubscribe its system topicYour own applicationPublish to a custom topicA partner SaaS systemSubscribe its partner topicWhere does the handler live?Azure-native handlerFunctions with the Event Grid trigger,Logic Apps, Automation: no handshakeYour own HTTPS webhookEvent Grid schemaEcho the validationCodewith HTTP 200CloudEvents schemaAnswer OPTIONS withWebHook-Allowed-Origin

Sharp facts the exam loves — give these one last read before exam day.

Cheat sheet

Sharp facts the exam loves — scan these before test day.

Applications publish custom events to a custom topic's endpoint URI, authenticated by access key or identity

A custom (application) topic exposes an HTTPS endpoint to which your app POSTs events; the publisher authenticates with the topic's access key (or SAS token, or a managed identity with the EventGrid Data Sender role). This is how first-party app events enter Event Grid.

14 questions test this
An Event Grid schema event requires id, subject, eventType, eventTime, and data; dataVersion, topic, and metadataVersion are optional

A custom event in the native Event Grid schema must supply id, subject, eventType, eventTime, and data (the payload). dataVersion is optional and is stamped with an empty value when omitted, while topic and metadataVersion are stamped by Event Grid. The subject and eventType are the fields most subscriptions filter on, so publishers set them deliberately to enable routing.

Trap Treating topic as a field the publisher has to populate on a custom event.

8 questions test this
Event Grid supports the CNCF CloudEvents 1.0 JSON schema in addition to its native schema

A topic can be configured to use the CloudEvents 1.0 schema (fields include specversion, type, source, id, time, subject, and data) for interoperability with other CloudEvents systems. The schema is set with two separate knobs, not one: the input schema is fixed when the topic is created (--input-schema), while the output (delivery) schema is chosen per event subscription (--event-delivery-schema). Event Grid input can be delivered as CloudEvents, but CloudEvents input cannot be delivered in the Event Grid schema, because CloudEvents extension attributes have no place in it.

Trap Assuming a single schema setting on the topic governs both input and delivery.

6 questions test this
Subject filtering routes events with subjectBeginsWith and subjectEndsWith prefix/suffix matches

An event subscription can filter on the event's subject using subjectBeginsWith (for example a folder path prefix) and subjectEndsWith (for example a file extension), plus a case-sensitivity flag. This is the lightweight first-line filter for narrowing which events a handler receives.

11 questions test this
Advanced filters test individual event fields with operators such as StringContains and NumberGreaterThan

Advanced filters evaluate a specific key inside the event (including data payload fields) with operators like StringIn, StringContains, NumberGreaterThan, and BoolEquals, allowing precise content-based routing beyond subject prefixes. Multiple advanced filters combine with AND semantics.

Trap Expecting multiple advanced filters to match when only one of them is satisfied.

19 questions test this
includedEventTypes limits a subscription to specific event types

A subscription can restrict delivery to a named list of event types via includedEventTypes; omitting it delivers all event types published to the topic. This is the coarsest, most common filter for custom-event workflows.

Event Grid retries failed deliveries with exponential back-off, bounded by max attempts and event TTL

If a handler does not return success, Event Grid retries with an exponential back-off schedule until either the configured maximum delivery attempts or the event time-to-live is reached. Tuning these two retry-policy values controls how long a transient handler outage is tolerated.

Trap Raising max delivery attempts when the event time-to-live is what expires first.

14 questions test this
Undelivered events dead-letter to an Azure Storage blob container, which must exist first

When retries are exhausted, Event Grid writes the event to a dead-letter destination that is an Azure Storage blob container; the storage account and container must already exist before the subscription is created, and Event Grid names each blob after the subscription in upper case. Enabling a system- or user-assigned managed identity for dead-lettering is OPTIONAL, and only when one is enabled must that identity hold an RBAC role permitting writes to the storage. Storage queues are not a valid Event Grid dead-letter target.

Trap Expecting Event Grid to create the dead-letter storage container for you.

15 questions test this
Some handler responses are non-retriable and dead-letter immediately

Certain HTTP responses from a webhook handler (for example 400 Bad Request or 413 Payload Too Large) are treated as non-retriable, so Event Grid stops retrying and, if dead-lettering is configured on the subscription, dead-letters the event right away; dead-lettering is off by default, and with no dead-letter destination configured the event is dropped instead. 5xx responses and timeouts are retried under the back-off policy.

A custom webhook endpoint must complete the subscription validation handshake before it receives events

When you create a subscription to a webhook that Azure does not validate automatically (which includes an HTTP-triggered Azure Function, not only endpoints outside Azure), Event Grid sends a SubscriptionValidationEvent containing a validationCode; the endpoint must echo that code back in a validationResponse (synchronous) or use the manual validationUrl handshake, proving ownership before delivery begins.

Trap Assuming an HTTP-triggered Azure Function is validated automatically because it lives in Azure.

14 questions test this
Event Grid delivers to first-party handlers (Functions, Logic Apps, Service Bus, Storage Queues) and generic webhooks

A subscription's endpoint can be an Azure Function, Logic App, Service Bus queue/topic, Storage Queue, Event Hub, or an arbitrary HTTPS webhook. Azure-native handlers (like Functions with the Event Grid trigger) auto-complete the validation handshake, unlike a raw webhook.

13 questions test this
CloudEvents-schema webhooks validate via the HTTP OPTIONS abuse-protection handshake instead of the validation event

When a subscription uses the CloudEvents 1.0 schema, endpoint validation follows the CloudEvents abuse-protection flow — an HTTP OPTIONS request carrying WebHook-Request-Origin that the endpoint answers with WebHook-Allowed-Origin — rather than echoing a validationCode.

References

  1. Concepts (push delivery) in Event Grid basic
  2. Azure Event Grid delivery and retry explained
  3. Compare Azure messaging services: Event Grid, Event Hubs, Service Bus
  4. Azure Event Grid pull delivery overview
  5. Authenticate Azure Event Grid clients using access keys or shared access signatures
  6. Azure Event Grid event schema
  7. Authenticate Event Grid publishing clients using Microsoft Entra ID
  8. How to filter events for Azure Event Grid
  9. CloudEvents v1.0 schema integration with Azure Event Grid
  10. Understand event filtering for Event Grid subscriptions
  11. Microsoft.EventGrid eventSubscriptions resource reference
  12. Azure Event Grid event handlers overview
  13. Validate webhook endpoints with the Event Grid event schema
  14. Endpoint validation using the CloudEvents v1.0 schema
  15. Application Insights telemetry data model
  16. Key Vault rotation tutorial for resources with one set of credentials
  17. Set dead letter location and retry policy for Azure Event Grid