AI-200 Service Bus and Event Grid: Messaging and Event-Driven AI Solutions
Event- and message-based AI solutions connect the parts of an AI application through Azure Service Bus, a message broker that stores jobs until workers settle them, and Azure Event Grid, an event router that pushes notifications about changes to subscribed handlers. This lesson covers both AI-200 skills in this area: queuing and processing back-end operations with Service Bus (queues, topics, subscriptions, settlement, sessions, duplicate detection, scheduling and dead-letter handling) and building event-driven workflows with Event Grid (custom events, schemas, filters, retries, dead-lettering and pull delivery), with the Python SDK calls you need for each.
On this page10 sections
- Service Bus or Event Grid: which one fits the job?
- Queues, topics and subscriptions
- Receiving messages safely: peek-lock, settlement, scheduling and deferral
- Dead-letter queue handling in Service Bus
- Sessions, duplicate detection and subscription filters
- Keyless access with Microsoft Entra ID and data-plane roles
- Publishing custom events to Event Grid
- Event subscription filters
- Event Grid delivery, retries and dead-lettering
- Event Grid namespaces and pull delivery
- Choose between Service Bus queues, Service Bus topics, Event Grid push, Event Grid pull and Event Hubs from a scenario's ordering, retention and fan-out needs
- Receive Service Bus messages safely with peek-lock, settle them with complete, abandon, defer or dead-letter, and renew locks for long-running AI calls
- Use scheduled and deferred messages, sessions with session state, duplicate detection and subscription rules, knowing which settings are fixed at creation
- Read, diagnose and resubmit dead-lettered Service Bus messages, including expired ones
- Connect without keys using DefaultAzureCredential and the least-privilege Service Bus and Event Grid data roles
- Publish custom events with the right input and delivery schemas, and filter subscriptions by event type, subject and advanced filters
- Configure Event Grid retries, event time-to-live and Blob dead-lettering, write idempotent handlers, and settle pulled events from namespace topics
Service Bus or Event Grid: which one fits the job?
Use Azure Service Bus when the message is a piece of work that must not be lost and must be processed by a consumer that pulls it when ready; use Azure Event Grid when something has happened and one or more handlers should be told about it within seconds. Service Bus is a message broker: it stores commands and jobs until a receiver settles them, with ordering, de-duplication, scheduling and dead-lettering. Event Grid is an event router: it pushes small notifications ("a blob was created", "a model finished training") to subscribers and retries for a limited time.
| Need | Azure Service Bus | Azure Event Grid (basic topics) | Azure Event Hubs |
|---|---|---|---|
| What travels | Commands, jobs, business transactions | Discrete notifications that something changed | High-volume telemetry and log streams |
| Delivery model | Consumers pull and settle each message | Event Grid pushes to each handler | Readers track their own offset in a stream |
| How long data waits | Until the message's time-to-live expires (consumers can be offline for days) | Retries stop at the event time-to-live, at most 24 hours | Retention period of the hub |
| Ordering and de-duplication | Sessions (FIFO per key) and duplicate detection | None: at least once, unordered | Ordered within a partition |
| Typical AI use | Hand OCR, embedding or summarisation jobs to a worker pool | React to uploads and lifecycle events with several independent handlers | Ingest token-usage or sensor telemetry for analytics |
The two services also combine. A Service Bus queue or topic is a built-in Event Grid event handler, so an event subscription can deliver events straight into a queue that existing workers already read. Once delivered, the event waits in the queue for as long as the message time-to-live allows, so Event Grid's 24-hour retry ceiling no longer limits how long consumers can be away, and no relay code is needed.
When a scenario lists several needs at once, check each against the table: ordered delivery per key and de-duplication of resends exist only in Service Bus (sessions and duplicate detection), while Event Grid, push or pull, offers neither. Triggering Azure Functions from either service is covered in the Azure Functions lesson of this domain.
Queues, topics and subscriptions
A Service Bus queue delivers each message to exactly one receiver, while a topic copies every message to each of its subscriptions, and each subscription behaves like its own queue. Choose by asking whether consumers share the work or each need all of it.
- Competing consumers. Scaling one service to several identical instances means they all receive from the same queue (or the same subscription). Peek-lock gives each message to one instance and completing it removes it, so load spreads with no duplicates.
- Fan-out. Different services that each need every message (a translation service and a search indexer, say) each get their own subscription on a topic. Each keeps its own backlog while offline, and adding a consumer later is just a new subscription, so the publisher never changes. Sending each message to several queues from the publisher works only until the next consumer arrives.
- Copies are per subscription, by design. The unit that shares work is one subscription (or one queue), not one instance. If several instances of the same service each have their own subscription, each message is processed once per subscription, because every subscription legitimately received its own copy. Features that act on a single entity's messages (duplicate detection, sessions, delivery counts) never merge copies held by different subscriptions.
Auto-forwarding chains a queue or subscription to another queue or topic in the same namespace; it moves messages along, it does not copy them to extra consumers.
Tiers matter. The Basic tier supports queues only. Standard adds topics and subscriptions, sessions, transactions and duplicate detection; Premium runs on dedicated capacity at a higher cost and adds features Standard lacks, such as large messages, geo-disaster recovery and geo-replication, and private endpoints. Fan-out (topics) is therefore unavailable on Basic.
Receiving messages safely: peek-lock, settlement, scheduling and deferral
Receive in peek-lock mode and settle each message only after its work is done, so that a crash never loses a message. In the Python azure-servicebus SDK, peek-lock (ServiceBusReceiveMode.PEEK_LOCK) is the default: the broker locks the message for one receiver and keeps it until that receiver settles it. RECEIVE_AND_DELETE removes the message as soon as it is handed over, so anything in flight during a crash is gone. peek_messages() is a browse that takes no lock and cannot be settled, so it cannot divide work.
| Receiver call | What happens to the message | Use it when |
|---|---|---|
complete_message(msg) | Removed from the queue | The result has been stored (call it after, never on receipt) |
abandon_message(msg) | Lock released at once; available to any receiver; delivery count goes up | A transient failure (such as a dependency timing out) and you want a retry now |
defer_message(msg) | Stays in the queue but hidden from normal receives | The message must wait for something else; fetch it later by sequence number |
dead_letter_message(msg, reason=...) | Moved to the dead-letter subqueue | The message can never succeed (poison message) |
If a receiver never calls complete_message, processing may succeed but the broker never hears about it: the lock expires, the message becomes visible again, its delivery count rises, and after the maximum delivery count (10 by default) it lands in the dead-letter subqueue with the reason MaxDeliveryCountExceeded.
Long-running work. The lock lasts for the entity's lock duration, which cannot exceed 5 minutes. If processing takes longer (a 25-minute batch transcription, for example), the lock expires mid-work: another receiver picks the message up (duplicates) and the first receiver's complete_message fails with MessageLockLostError. Keep peek-lock and extend the lock instead, with AutoLockRenewer or renew_message_lock:
from azure.servicebus import AutoLockRenewer
renewer = AutoLockRenewer()
with client.get_queue_receiver("transcribe-batches") as receiver:
for msg in receiver.receive_messages(max_wait_time=5):
renewer.register(receiver, msg, max_lock_renewal_duration=1800)
transcribe(msg) # may take up to 25 minutes
receiver.complete_message(msg)
renewer.close()
Raising the maximum delivery count does not affect lock expiry, and switching to receive-and-delete trades duplicates for lost messages.
Scheduled and deferred messages
Scheduling delays a message you are sending until a known future time; deferral parks a message you have already received until an unpredictable later moment. They look similar but sit on opposite sides of the queue.
| Scheduled message | Deferred message | |
|---|---|---|
| Who acts | The sender | The receiver |
| How | sender.schedule_messages(msg, enqueue_time_utc), or set scheduled_enqueue_time_utc before send_messages | receiver.defer_message(msg) |
| When it comes back | The broker makes it available at the chosen UTC time | Only when the app calls receiver.receive_deferred_messages(sequence_numbers=[...]) |
| What you must keep | The returned sequence number, to cancel it | The message's sequence number, to fetch it |
| Undo | sender.cancel_scheduled_messages(sequence_numbers) | Receive it and settle it |
from datetime import datetime, timedelta, timezone
when = datetime.now(timezone.utc) + timedelta(days=3)
with client.get_queue_sender("trial-expiry") as sender:
seq = sender.schedule_messages(ServiceBusMessage("expire"), when)
# later, the trial is converted:
sender.cancel_scheduled_messages(seq)
Until its enqueue time a scheduled message is invisible to receivers, so no receiver call (settle, defer retrieval, dead-letter) can reach it; cancellation is a sender method only. Neither feature is a substitute for the others: time-to-live controls when a message expires, lock duration applies only after a receive, and the dead-letter queue is for failed messages, not for parking work.
Dead-letter queue handling in Service Bus
Every queue and subscription has a dead-letter subqueue (DLQ) that holds messages the broker or your code has taken out of normal processing, and messages stay there until something reads and completes them. Service Bus never cleans the DLQ automatically and does not apply time-to-live to messages in it.
- System reasons.
MaxDeliveryCountExceededafter too many failed deliveries, andTTLExpiredExceptionfor expired messages, but only when dead-lettering on message expiration is enabled on the queue or subscription. It is off by default, and without it an expired message is simply removed without trace. - Application dead-lettering. Your code calls
dead_letter_message(msg, reason=..., error_description=...)for a poison message. - Transfer dead-letter queue. A separate subqueue (
ServiceBusSubQueue.TRANSFER_DEAD_LETTER) holds messages that failed during auto-forwarding or transfer.
To read the DLQ in Python, open a receiver on the same queue or subscription and pass sub_queue:
from azure.servicebus import ServiceBusSubQueue
with client.get_queue_receiver(
"translation-jobs",
sub_queue=ServiceBusSubQueue.DEAD_LETTER) as receiver:
for msg in receiver.receive_messages(max_wait_time=5):
print(msg.dead_letter_reason, msg.dead_letter_error_description)
receiver.complete_message(msg)
Resubmission is your job: a recovery process (or Service Bus Explorer in the portal) reads each dead-lettered message, sends a copy back to the main queue once the cause is fixed, and then completes the dead-lettered copy.
Sessions, duplicate detection and subscription filters
Three Service Bus features change which messages a receiver sees: sessions give ordered, exclusive processing per key, duplicate detection drops resends, and subscription rules decide which topic messages a subscription keeps. Sessions and duplicate detection need the Standard or Premium tier and must be enabled when the queue or subscription is created; they cannot be switched on later, so an existing entity has to be replaced by a new one.
Sessions
The sender sets session_id (for example the customer or conversation ID) on each ServiceBusMessage. A session receiver takes an exclusive lock on one session and receives its messages in order, while other workers process other sessions in parallel. Pass a specific session_id string to pin a receiver to one session, or session_id=NEXT_AVAILABLE_SESSION (imported from azure.servicebus) to take whichever session is free; there is no wildcard string. Each session can also hold a broker-stored state blob, read and written with receiver.session.get_state() and receiver.session.set_state(), so a worker that takes over after a crash can resume from the recorded step without a separate database.
Duplicate detection
With duplicate detection on, the entity logs the message_id of every message for a history window (10 minutes by default, from 20 seconds up to 7 days). A later message with a logged ID is accepted (the send reports success) and silently discarded, whatever its body. Nothing else is compared: not the body, not correlation_id, not the broker-assigned sequence number. It therefore works only when senders set a deterministic, business-derived ID such as a document or transaction ID; a fresh GUID on every resend defeats it. The enable flag is fixed at creation, but the window is an updatable property of the existing entity (for example az servicebus queue update --duplicate-detection-history-time-window). Keep it as small as the resend pattern allows, because every logged ID is checked against new messages and a larger window costs throughput.
Subscription rules
Each subscription has one or more rules; rules are evaluated independently and combine as OR, so a message is copied in if any rule matches. A new subscription gets a rule named $Default with a true filter (1=1) that matches everything, so adding a narrower rule beside it changes nothing until $Default is deleted (or the subscription is created with your filter as its default rule).
| Filter type | Matches on | When to use it |
|---|---|---|
| SQL filter | A SQL-like expression over system properties (prefixed sys., such as sys.Label) and application properties, with LIKE, AND, OR | Ranges, patterns and complex logic |
| Correlation filter | Exact equality on system properties (Subject, CorrelationId, MessageId, To, ReplyTo, ContentType...) and application properties | Equality checks; cheaper for the broker to evaluate, so preferred at high volume |
| True / false filter | Every message / no message | $Default is a true filter |
A rule can also carry a SQL action that changes properties of matched messages; an action never selects messages.
Keyless access with Microsoft Entra ID and data-plane roles
The recommended way to reach Service Bus and Event Grid from code is a Microsoft Entra token from a managed identity, with a data-plane role at the narrowest scope; connection strings and access keys are shared secrets and are often disabled by policy. In Python, pass the fully qualified namespace (the full host name, not the bare namespace name) and a TokenCredential such as DefaultAzureCredential:
from azure.identity import DefaultAzureCredential
from azure.servicebus import ServiceBusClient
client = ServiceBusClient(
"fabrikam-ml.servicebus.windows.net", DefaultAzureCredential())
ServiceBusClient.from_connection_string relies on a shared access key. ServiceBusAdministrationClient also accepts a token credential, but it manages queues, topics, subscriptions and rules; it does not send or receive.
| Role | Grants |
|---|---|
| Azure Service Bus Data Sender | Send messages |
| Azure Service Bus Data Receiver | Receive (and settle) messages |
| Azure Service Bus Data Owner | Full data access, including send and receive |
| EventGrid Data Sender | Publish events to a topic or domain |
| EventGrid Data Receiver | Receive events from namespace pull subscriptions |
| Contributor, Owner, EventGrid Contributor, EventGrid EventSubscription Contributor | Management plane only: create and configure resources, but no message or event data access |
Service Bus data roles can be assigned at the namespace, queue, topic or subscription scope. Least privilege means the narrowest role at the narrowest scope: an app that sends to one queue and receives from another gets Data Sender on the first and Data Receiver on the second, not Data Owner and not a namespace-wide assignment. Management roles never imply data roles: an identity that can create and configure a queue is still refused when it sends to or receives from it.
Publishing custom events to Event Grid
Applications publish their own events to an Event Grid custom topic (or a domain, or a namespace topic) with EventGridPublisherClient; Azure services publish theirs to system topics, such as a storage account's blob events. If the source is an Azure service, subscribe to its system topic: it raises Microsoft.Storage.BlobCreated however the blob was written (an API, Storage Explorer, a copy job), whereas a custom topic only sees what your code publishes.
Schemas. The input schema of a custom topic (Event Grid schema, CloudEvents 1.0, or a custom schema with input mapping) is chosen when the topic is created, and Event Grid rejects payloads that do not match it, typically with 400 Bad Request. The delivery (output) schema is set per event subscription. Supported combinations include CloudEvents in and out, Event Grid in and out, and Event Grid in with CloudEvents out, so one new CloudEvents-only subscriber can be added to an Event Grid-schema topic without touching the publisher or the other subscribers.
from azure.core.messaging import CloudEvent
from azure.eventgrid import EventGridPublisherClient
from azure.identity import DefaultAzureCredential
client = EventGridPublisherClient(
"https://docs-topic.westeurope-1.eventgrid.azure.net/api/events",
DefaultAzureCredential())
client.send(CloudEvent(type="Fabrikam.TicketClassified",
source="/triage", data={"ticket": "T-881"}))
Use EventGridEvent objects (subject, event_type, data_version) for an Event Grid-schema topic and CloudEvent objects (type, source) for a CloudEvents topic. With a token credential the identity needs EventGrid Data Sender on the topic.
Webhook validation. Before delivering, Event Grid makes a webhook prove it wants the events. With Event Grid schema delivery it posts a SubscriptionValidationEvent, and the endpoint answers with the validationCode in a validationResponse property (or visits the validationUrl manually). With CloudEvents 1.0 delivery it uses the CloudEvents abuse-protection handshake instead: an HTTP OPTIONS request carrying WebHook-Request-Origin, which the endpoint answers with a WebHook-Allowed-Origin header (the origin or *). Implementing the wrong handshake for the chosen schema makes subscription creation fail.
Event subscription filters
An event subscription can filter on three things: the event type (includedEventTypes), the subject (subjectBeginsWith, subjectEndsWith) and advanced filters on fields in the event, such as data.region. Only matching events are delivered to that subscription's handler.
Subject filters are the cheap way to scope by path. Blob events use the subject /blobServices/default/containers/<container>/blobs/<blob>, so subjectBeginsWith = /blobServices/default/containers/scans/ (with the trailing slash, so a "scans-archive" container does not match) plus subjectEndsWith = .png limits delivery to PNG images in one container. Event types are names like Microsoft.Storage.BlobCreated, not file extensions.
Advanced filters use an operator, a key and one or more values. Operators include NumberGreaterThan, NumberGreaterThanOrEquals, NumberLessThan, NumberIn, NumberNotIn, NumberInRange, NumberNotInRange, BoolEquals, StringIn, StringNotIn, StringContains, StringNotContains, StringBeginsWith, StringNotBeginsWith, StringEndsWith, StringNotEndsWith, IsNullOrUndefined and IsNotNull. The combination rules are what scenarios test:
- Several values in one filter are ORed:
StringInwith two region names matches either. - Several filters are ANDed: every filter must match. Splitting two allowed names into two
StringInfilters therefore matches nothing. - String comparisons are case-insensitive;
StringInis an exact match,StringContainsa substring test;NumberGreaterThanis strict. - The negated operators (
StringNotIn,NumberNotIn,StringNotContains,StringNotBeginsWith,StringNotEndsWith,NumberNotInRange) treat a missing key as a match. To exclude events without the field, add a second filter,IsNotNullon that key (true only when it exists and is not null);IsNullOrUndefinedselects the opposite set. enableAdvancedFilteringOnArrayslets filters evaluate array values; it does not change missing-key behaviour.
"advancedFilters": [
{"operatorType": "StringIn", "key": "data.region",
"values": ["eastus", "westus"]},
{"operatorType": "NumberLessThan",
"key": "data.confidence", "value": 0.5}
]
This example delivers events from either region (one filter, two values ORed) only when the confidence is also below 0.5 (a second filter, ANDed).
Event Grid delivery, retries and dead-lettering
Event Grid delivers each event at least once, with no ordering guarantee, and retries failed deliveries on a backoff schedule until a retry limit is reached; after that the event is dropped unless the subscription has a dead-letter destination. Handlers must therefore be idempotent: record each processed event id and skip repeats, and compare a version or timestamp with the stored one so an older change that arrives late never overwrites a newer one.
- Success is only HTTP 200, 201, 202, 203 or 204. Returning 202 after queueing the work internally is a complete, successful delivery.
- Timeout. Event Grid waits 30 seconds for a response; after that it queues the event for retry even if the handler later succeeds. So a handler that always takes longer than 30 seconds receives the same event repeatedly, although every attempt succeeds. Acknowledge quickly and do the slow work afterwards (for example via a queue).
- Not retried. Some errors mean the event can never be accepted, so Event Grid does not retry them, and the list depends on the endpoint type. For Azure-resource endpoints (such as a Service Bus queue or a storage queue): 400 Bad Request, 403 Forbidden and 413 Request Entity Too Large. For webhooks: 400 Bad Request, 401 Unauthorized and 413 Request Entity Too Large; a webhook's 403 is retried. Such an event goes to dead-letter straight away if a destination is configured. Errors such as 503 or a timeout are retried.
- Backoff schedule (best effort, with some randomisation): 10 seconds, 30 seconds, 1 minute, 5 minutes, 10 minutes, 30 minutes, 1 hour, 3 hours, 6 hours, then every 12 hours up to 24 hours.
| Retry policy setting | Range | Default | Controls |
|---|---|---|---|
| Maximum delivery attempts | 1 to 30 | 30 | How many tries Event Grid makes |
| Event time-to-live | 1 to 1,440 minutes | 1,440 (24 hours) | How long after publishing Event Grid keeps trying |
Retries stop at whichever limit comes first. Because the gaps between tries grow, an attempt count does not translate into a fixed time, so a requirement stated as a time limit ("useless after 45 minutes", say) maps to event time-to-live = 45 minutes, not to an attempt count. Neither setting can keep an event beyond 24 hours, and lowering attempts to 1 trades duplicates for data loss.
Dead-lettering is off by default. When enabled on the event subscription, undeliverable events are written as blobs to an Azure Blob Storage container (the only supported dead-letter destination), from which they can be inspected and replayed. Output batching (maximum events per batch) is a throughput setting and does not affect ordering or retries.
Event Grid namespaces and pull delivery
An Event Grid namespace adds namespace topics whose event subscriptions can be push or queue (pull), so a consumer can fetch events at its own pace over outbound HTTPS and settle each one explicitly, using Event Grid alone. Basic custom and system topics only push, which needs an inbound endpoint. Namespace topics accept only CloudEvents 1.0: when EventGridPublisherClient is created with namespace_topic set, it must send CloudEvent objects (or their dict form), and EventGridEvent payloads are rejected.
The consumer uses EventGridConsumerClient (identity role: EventGrid Data Receiver). receive() returns events with a lock token, and the consumer settles each token:
| Call | Effect | Use it for |
|---|---|---|
acknowledge(lock_tokens=[...]) | Deletes the event | Processed successfully |
release(lock_tokens=[...], release_delay=600) | Returns it for redelivery, optionally after a delay of 0, 10, 60, 600 or 3600 seconds | Transient failure (a dependency briefly down) |
reject(lock_tokens=[...]) | Never offered again; dead-lettered if the subscription has a Blob Storage dead-letter destination, otherwise dropped | Poison events that can never succeed |
renew_locks(lock_tokens=[...]) | Extends the current lock | Processing that needs more time |
from azure.eventgrid import EventGridConsumerClient
consumer = EventGridConsumerClient(endpoint, DefaultAzureCredential(),
namespace_topic="claims", subscription="ocr-workers")
for detail in consumer.receive(max_events=10):
token = detail.broker_properties.lock_token
try:
handle(detail.event)
consumer.acknowledge(lock_tokens=[token])
except QuotaExhausted:
consumer.release(lock_tokens=[token], release_delay=600)
A rejected event is kept only if the subscription has a dead-letter destination; reject on its own just drops it. Pull delivery is still at least once and unordered; if a scenario needs per-key order or de-duplication, that is Service Bus.
Tip. Questions on this topic are short Azure scenarios with one or two deciding requirements, such as no message loss, processing by exactly one worker, per-customer order, no keys, least privilege, least development effort or a time limit on usefulness. Several show a Python azure-servicebus or azure-eventgrid call with a missing argument, or an advancedFilters JSON block, and ask which line completes it. Expect near-twin options: queue versus topic, abandon versus defer, scheduling versus deferral, data roles versus management roles, input versus delivery schema, values in one filter versus separate filters, attempts versus time-to-live, and release versus reject. Some items are troubleshooting: a symptom such as duplicates, MessageLockLostError, messages in the dead-letter queue or a failed webhook validation, and you pick the cause or fix. Multiple-response items ask for two actions that together meet the requirement.
- Service Bus stores work until a consumer settles it (days if needed); Event Grid pushes notifications and stops retrying after at most 24 hours. A Service Bus queue can be an Event Grid handler to get both.
- A queue gives each message to one receiver; a topic gives every subscription its own copy. Instances of one service share a queue or subscription; different services get their own subscriptions.
- Basic tier has queues only; topics, sessions and duplicate detection need Standard or Premium, and sessions and duplicate detection must be enabled when the entity is created.
- Use peek-lock and complete after the result is stored; abandon retries now, defer parks a received message, and AutoLockRenewer covers work longer than the lock duration (5 minutes at most).
- Messages reach the dead-letter subqueue after the maximum delivery count (default 10) or on expiry only when dead-lettering on expiration is on; read them with sub_queue=ServiceBusSubQueue.DEAD_LETTER and resubmit them yourself.
- Duplicate detection compares only message_id within the history window; subscription rules are ORed, and the $Default true filter must go before a narrower filter has any effect.
- Keyless access is ServiceBusClient(fully qualified namespace, DefaultAzureCredential()) plus a data role (Data Sender, Data Receiver) at the narrowest scope; Contributor gives no data access.
- A custom topic's input schema is fixed at creation and the delivery schema is per subscription; CloudEvents delivery to a webhook uses the OPTIONS / WebHook-Allowed-Origin handshake.
- Event Grid filters: values within one filter OR, separate filters AND, negated operators such as StringNotIn match a missing key (add IsNotNull); success is 200-204 within 30 seconds; delivery is at least once and unordered.
- Retries end at max attempts (1-30) or event TTL (1-1,440 minutes), whichever comes first; dead-letter only to a Blob container, off by default. Namespace topics pull CloudEvents and settle with acknowledge, release, reject or renew_locks.
Frequently asked questions
When should I use Azure Service Bus instead of Azure Event Grid?
Use Service Bus when a message is work that must not be lost and must be processed by a consumer pulling at its own pace, especially when you need ordering (sessions), discarding of resends (duplicate detection) or storage while consumers are offline for days. Use Event Grid to notify one or more handlers within seconds that something happened, such as a blob upload; it pushes, delivers at least once without ordering, and stops retrying after at most 24 hours.
Can I turn on sessions or duplicate detection on an existing Service Bus queue?
No. Both must be enabled when the queue or subscription is created, and both need the Standard or Premium tier. To add them you create a new entity with the feature on and move senders and receivers to it. The duplicate detection history window, by contrast, can be changed on an existing queue.
Why do Service Bus messages end up in the dead-letter queue even though processing succeeded?
Usually because the receiver never calls complete_message in peek-lock mode. The lock expires, the message is delivered again and its delivery count rises; after the maximum delivery count, 10 by default, Service Bus moves it to the dead-letter subqueue with the reason MaxDeliveryCountExceeded. Completing each message after its result is stored fixes it.
How do I read and reprocess messages from a Service Bus dead-letter queue in Python?
Create a receiver for the same queue or subscription with sub_queue=ServiceBusSubQueue.DEAD_LETTER, read each message's dead_letter_reason, and after fixing the cause send a copy back to the main queue and complete the dead-lettered message. Service Bus never removes dead-lettered messages automatically, and time-to-live does not apply to them.
Why does my Service Bus topic subscription receive messages that do not match its filter?
Every new subscription has a $Default rule with a true filter that matches all messages, and rules on a subscription combine as OR. Adding a SQL or correlation filter beside $Default therefore changes nothing; delete $Default, or create the subscription with your filter as its default rule.
How do Event Grid advanced filters combine?
Multiple values inside one advanced filter are ORed, and multiple advanced filters on a subscription are ANDed, together with any event type and subject filters. String comparisons are case-insensitive. The negated operators, such as StringNotIn, NumberNotIn, StringNotContains and NumberNotInRange, treat a missing key as a match, so add an IsNotNull filter on the key if events without the field must be excluded.
What happens to an Event Grid event that cannot be delivered?
Event Grid retries on a backoff schedule until the subscription's maximum delivery attempts (1 to 30, default 30) or event time-to-live (1 to 1,440 minutes, default 1,440) is reached, whichever comes first. Some errors are never retried: 400, 403 and 413 for Azure-resource endpoints, and 400, 401 and 413 for webhooks. The event is then dropped unless the subscription has dead-lettering enabled, which writes it to an Azure Blob Storage container.
Can an application pull events from Azure Event Grid instead of receiving pushes?
Yes, with an Event Grid namespace. A namespace topic with a queue (pull) event subscription lets a client call EventGridConsumerClient.receive over outbound HTTPS and settle each event by lock token with acknowledge, release (optionally delayed), reject or renew_locks. Namespace topics accept only CloudEvents 1.0, and the consumer's identity needs the EventGrid Data Receiver role.
Source
This lesson covers the "Connect to and consume Azure services" domain of the official AI-200 exam guide. Vendors revise their guides — check the source for the current version.
- Microsoft AI-200 study guide — Microsoft Learn
Sign up free to mark lessons complete, bookmark topics and track your exam readiness.