Service Bus Data Plane
Introduction
Section titled “Introduction”The Azure Service Bus data plane is how applications send and receive messages. The Azure SDKs connect to a namespace over AMQP 1.0, and the namespace endpoint also serves an HTTP API for sending and receiving messages and an Atom-based API for managing queues, topics, and subscriptions. For more information, see AMQP 1.0 in Azure Service Bus and Azure Service Bus REST API.
LocalStack for Azure runs a Service Bus message broker inside the emulator. The Azure SDKs connect to it with the connection strings the emulator returns, and the HTTP and Atom APIs are served on the namespace endpoint. The supported APIs are available on our API Coverage section, which provides information on the extent of Service Bus Data Plane integration with LocalStack.
Getting started
Section titled “Getting started”This guide is designed for users new to Service Bus Data Plane APIs and assumes basic knowledge of the Azure CLI and our lstk az proxy.
Launch LocalStack using your preferred method. For more information, see Introduction to LocalStack for Azure. Once the container is running, enable Azure CLI interception by running:
lstk az start-interceptionThis command points the az CLI away from the public Azure management REST API and toward the LocalStack for Azure emulator API.
To revert this configuration, run:
lstk az stop-interceptionThis reconfigures the az CLI to send commands to the official Azure management REST API.
Create a resource group
Section titled “Create a resource group”Create a resource group for your Service Bus resources:
az group create \ --name rg-servicebus-dp-demo \ --location westeurope{ "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-servicebus-dp-demo", "location": "westeurope", ... "name": "rg-servicebus-dp-demo", "properties": { "provisioningState": "Succeeded" }, ...}Create a Service Bus namespace
Section titled “Create a Service Bus namespace”Create a namespace for the queues and topics:
az servicebus namespace create \ --resource-group rg-servicebus-dp-demo \ --name sbnsdoc84 \ --location westeurope \ --sku Standard{ ... "name": "sbnsdoc84", ... "provisioningState": "Succeeded", ... "serviceBusEndpoint": "https://sbnsdoc84.servicebus.azure.localhost.localstack.cloud:4511/", ...}Authorize data-plane requests
Section titled “Authorize data-plane requests”Each request in this guide carries a shared access signature (SAS) token in its Authorization header.
Capture the namespace endpoint, without the trailing slash Azure returns, so that the paths below can be appended to it:
SB_ENDPOINT=$(az servicebus namespace show \ --resource-group rg-servicebus-dp-demo \ --name sbnsdoc84 \ --query serviceBusEndpoint \ --output tsv)SB_ENDPOINT=${SB_ENDPOINT%/}Capture the primary key of the namespace’s RootManageSharedAccessKey rule, which grants the Manage, Send, and Listen rights:
SB_KEY=$(az servicebus namespace authorization-rule keys list \ --resource-group rg-servicebus-dp-demo \ --namespace-name sbnsdoc84 \ --name RootManageSharedAccessKey \ --query primaryKey \ --output tsv)Define a function that signs a token with the key, adapted from the Bash example in Generate SAS token. It requires OpenSSL, jq, and iconv:
get_sas_token() { local RESOURCE_URI=$1 local SHARED_ACCESS_KEY_NAME=$2 local SHARED_ACCESS_KEY=$3 local EXPIRY=${EXPIRY:=$((60 * 60))} # Default token expiry is 1 hour
local ENCODED_URI=$(echo -n $RESOURCE_URI | jq -s -R -r @uri) local TTL=$(($(date +%s) + $EXPIRY)) local UTF8_SIGNATURE=$(printf "%s\n%s" $ENCODED_URI $TTL | iconv -t utf8)
local HASH=$(echo -n "$UTF8_SIGNATURE" | openssl sha256 -hmac $SHARED_ACCESS_KEY -binary | base64) local ENCODED_HASH=$(echo -n $HASH | jq -s -R -r @uri)
echo -n "SharedAccessSignature sr=$ENCODED_URI&sig=$ENCODED_HASH&se=$TTL&skn=$SHARED_ACCESS_KEY_NAME"}Sign a token for the whole namespace:
SB_TOKEN=$(get_sas_token "$SB_ENDPOINT" RootManageSharedAccessKey "$SB_KEY")Create and inspect a queue entity
Section titled “Create and inspect a queue entity”Create a queue via the data-plane Entity Put API:
curl -s -X PUT "$SB_ENDPOINT/dpqueue?api-version=2021-05" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Type: application/atom+xml;type=entry;charset=utf-8" \ -d '<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"><title type="text">dpqueue</title><content type="application/xml"><QueueDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" /></content></entry>'<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"> ... <title type="text">dpqueue</title> ... <content type="application/xml"> <QueueDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <LockDuration>PT1M</LockDuration> <MaxSizeInMegabytes>1024</MaxSizeInMegabytes> ... <MaxDeliveryCount>10</MaxDeliveryCount> ... <Status>Active</Status> ... </QueueDescription> </content></entry>Get the queue entity via Entity Get:
curl -s "$SB_ENDPOINT/dpqueue?api-version=2021-05" \ -H "Authorization: $SB_TOKEN"<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"> ... <title type="text">dpqueue</title> ... <content type="application/xml"> <QueueDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> ... <MessageCount>0</MessageCount> ... <Status>Active</Status> ... <CountDetails xmlns:d2p1="http://schemas.microsoft.com/netservices/2011/06/servicebus"> <d2p1:ActiveMessageCount>0</d2p1:ActiveMessageCount> <d2p1:DeadLetterMessageCount>0</d2p1:DeadLetterMessageCount> <d2p1:ScheduledMessageCount>0</d2p1:ScheduledMessageCount> <d2p1:TransferMessageCount>0</d2p1:TransferMessageCount> <d2p1:TransferDeadLetterMessageCount>0</d2p1:TransferDeadLetterMessageCount> </CountDetails> ... </QueueDescription> </content></entry>Create and inspect a topic subscription
Section titled “Create and inspect a topic subscription”Create a topic entity:
curl -s -X PUT "$SB_ENDPOINT/dptopic?api-version=2021-05" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Type: application/atom+xml;type=entry;charset=utf-8" \ -d '<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"><title type="text">dptopic</title><content type="application/xml"><TopicDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" /></content></entry>'Create a subscription via Subscription Put:
curl -s -X PUT "$SB_ENDPOINT/dptopic/subscriptions/dpsub?api-version=2021-05" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Type: application/atom+xml;type=entry;charset=utf-8" \ -d '<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"><title type="text">dpsub</title><content type="application/xml"><SubscriptionDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" /></content></entry>'<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"> ... <title type="text">dpsub</title> ... <content type="application/xml"> <SubscriptionDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> <LockDuration>PT1M</LockDuration> ... <MaxDeliveryCount>10</MaxDeliveryCount> ... <Status>Active</Status> ... </SubscriptionDescription> </content></entry>Get the subscription via Subscription Get:
curl -s "$SB_ENDPOINT/dptopic/subscriptions/dpsub?api-version=2021-05" \ -H "Authorization: $SB_TOKEN"<?xml version="1.0" encoding="utf-8"?><entry xmlns="http://www.w3.org/2005/Atom"> ... <title type="text">dpsub</title> ... <content type="application/xml"> <SubscriptionDescription xmlns="http://schemas.microsoft.com/netservices/2010/10/servicebus/connect" xmlns:i="http://www.w3.org/2001/XMLSchema-instance"> ... <MessageCount>0</MessageCount> ... <Status>Active</Status> ... <CountDetails xmlns:d3p1="http://schemas.microsoft.com/netservices/2011/06/servicebus"> <d3p1:ActiveMessageCount>0</d3p1:ActiveMessageCount> <d3p1:DeadLetterMessageCount>0</d3p1:DeadLetterMessageCount> <d3p1:ScheduledMessageCount>0</d3p1:ScheduledMessageCount> <d3p1:TransferMessageCount>0</d3p1:TransferMessageCount> <d3p1:TransferDeadLetterMessageCount>0</d3p1:TransferDeadLetterMessageCount> </CountDetails> ... </SubscriptionDescription> </content></entry>Send and receive messages over HTTP
Section titled “Send and receive messages over HTTP”Send a message to the queue.
The BrokerProperties header sets the message’s system properties, and any other header that is not a standard HTTP header becomes a custom property:
curl -s -i -X POST "$SB_ENDPOINT/dpqueue/messages" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Type: application/json" \ -H 'BrokerProperties: {"MessageId": "order-1001", "Label": "order-created"}' \ -H "Priority: High" \ -d '{"orderId": 1001}'HTTP/1.1 201 CREATEDContent-Type: application/xml; charset=utf-8Content-Length: 0Receive the message in peek-lock mode.
The broker locks the message and returns its body, its system properties in the BrokerProperties header, its custom properties as headers, and the URI of the locked message in the Location header:
curl -s -i -X POST "$SB_ENDPOINT/dpqueue/messages/head?timeout=10" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Length: 0" \ | tee peek-lock-response.txtHTTP/1.1 201 CREATEDContent-Type: application/jsonContent-Length: 17Priority: "High"BrokerProperties: {"DeliveryCount":1,"EnqueuedTimeUtc":"Tue, 13 Oct 2026 12:07:34 GMT","Label":"order-created","LockToken":"98f625bb-f6ed-47ec-8329-4dce0466557a","LockedUntilUtc":"Tue, 13 Oct 2026 12:08:34 GMT","MessageId":"order-1001","SequenceNumber":1,"State":"Active","TimeToLive":922337203685.4775}Location: https://sbnsdoc84.servicebus.azure.localhost.localstack.cloud:4511/dpqueue/messages/1/98f625bb-f6ed-47ec-8329-4dce0466557a
{"orderId": 1001}Complete the message by deleting it at the URI of the lock, which removes it from the queue:
MESSAGE_URI=$(grep -i '^location:' peek-lock-response.txt | cut -d ' ' -f 2 | tr -d '\r')
curl -s -i -X DELETE "$MESSAGE_URI" \ -H "Authorization: $SB_TOKEN"HTTP/1.1 200 OKContent-Type: application/xml; charset=utf-8Content-Length: 0Send a second message, then receive it in receive-and-delete mode, which removes it from the queue as it is returned:
curl -s -X POST "$SB_ENDPOINT/dpqueue/messages" \ -H "Authorization: $SB_TOKEN" \ -H "Content-Type: application/json" \ -H 'BrokerProperties: {"MessageId": "order-1002", "Label": "order-created"}' \ -d '{"orderId": 1002}'
curl -s -i -X DELETE "$SB_ENDPOINT/dpqueue/messages/head?timeout=10" \ -H "Authorization: $SB_TOKEN"HTTP/1.1 200 OKContent-Type: application/jsonContent-Length: 17BrokerProperties: {"DeliveryCount":1,"EnqueuedTimeUtc":"Tue, 13 Oct 2026 12:07:34 GMT","Label":"order-created","MessageId":"order-1002","SequenceNumber":2,"State":"Active","TimeToLive":922337203685.4775}
{"orderId": 1002}Send and receive messages with the Azure SDK
Section titled “Send and receive messages with the Azure SDK”The Azure SDKs reach the broker over AMQP with the namespace’s connection string.
Capture the connection string of the RootManageSharedAccessKey rule in an environment variable:
export SERVICEBUS_CONNECTION_STRING=$(az servicebus namespace authorization-rule keys list \ --resource-group rg-servicebus-dp-demo \ --namespace-name sbnsdoc84 \ --name RootManageSharedAccessKey \ --query primaryConnectionString \ --output tsv)Install the Azure Service Bus client library for Python:
pip install azure-servicebusSave the following code as send_receive.py.
It sends a message to the queue, then receives and completes every message waiting in it:
import os
from azure.servicebus import ServiceBusClient, ServiceBusMessage
connection_string = os.environ["SERVICEBUS_CONNECTION_STRING"]
with ServiceBusClient.from_connection_string(connection_string) as client: with client.get_queue_sender(queue_name="dpqueue") as sender: sender.send_messages(ServiceBusMessage("Order 1003 created", subject="order-created"))
with client.get_queue_receiver(queue_name="dpqueue", max_wait_time=5) as receiver: for message in receiver: print(f"Received: {message} (subject: {message.subject})") receiver.complete_message(message)Run it:
python send_receive.pyReceived: Order 1003 created (subject: order-created)Delete subscription and entities
Section titled “Delete subscription and entities”Delete the subscription via Subscription Delete:
curl -s -X DELETE "$SB_ENDPOINT/dptopic/subscriptions/dpsub?api-version=2021-05" \ -H "Authorization: $SB_TOKEN"Delete the topic and the queue via Entity Delete:
curl -s -X DELETE "$SB_ENDPOINT/dptopic?api-version=2021-05" \ -H "Authorization: $SB_TOKEN"
curl -s -X DELETE "$SB_ENDPOINT/dpqueue?api-version=2021-05" \ -H "Authorization: $SB_TOKEN"Features
Section titled “Features”The Service Bus emulator supports the following data-plane capabilities:
- AMQP 1.0 broker: The Azure SDKs, such as those for Python, .NET, and Java, connect with the connection strings the emulator returns, or with a Microsoft Entra credential over TLS.
- Receive modes and settlement: Messages are received in peek-lock or receive-and-delete mode, and a locked message can be completed, abandoned, deferred, or dead-lettered, and its lock renewed.
- Peek, scheduling, and deferral: Messages can be peeked without being locked and scheduled for a later enqueue time, a scheduled message can be canceled, and a deferred message can be received by its sequence number.
- Sessions: Session receivers accept a named session or the next available one, read and set the session state, and renew the session lock.
- Topic filters and actions: Each message sent to a topic is copied to the subscriptions whose rules match it, evaluating SQL filters, correlation filters, SQL rule actions, and the
$Defaultrule. - Dead-lettering, expiration, and forwarding: A message is dead-lettered when it exceeds the maximum delivery count or expires on an entity that dead-letters expired messages, and is forwarded to the entities that
forwardToandforwardDeadLetteredMessagesToname. - Duplicate detection: On an entity with duplicate detection enabled, a message whose
MessageIdwas already sent within the detection window is dropped. - HTTP API: Messages can be sent, alone or in batches, received in peek-lock or receive-and-delete mode, and unlocked, completed, or have their lock renewed over HTTP.
- Atom management API: Queues, topics, subscriptions, and subscription rules can be created, read, updated, listed, and deleted, and the namespace’s properties read, through the Atom API that the administration clients, such as
ServiceBusAdministrationClient, use. - Microsoft Entra ID: Clients can authenticate with a Microsoft Entra token instead of a shared access signature. Managed identities of App Service, Azure Functions, and AKS workloads reach the emulator through
<namespace>.servicebus.windows.net, so identity-based connections work unchanged. - Azure RBAC: When RBAC enforcement is enabled, a Microsoft Entra caller needs the Azure Service Bus Data Owner, Data Sender, or Data Receiver role on the entity, the namespace, the resource group, or the subscription, as in Azure.
Limitations
Section titled “Limitations”- Transactions are not supported: The broker refuses the AMQP transaction coordinator with
amqp:not-implemented, so messages cannot be sent or settled inside a transaction. - AMQP over WebSockets needs a custom endpoint: A client configured for AMQP over WebSockets connects to port 443 instead of the emulator’s AMQP port, so it fails unless its custom endpoint address, such as
custom_endpoint_addressin the Python SDK, is set to the namespace endpoint the emulator returns inserviceBusEndpoint. In turn, the Python SDK uses AMQP over WebSockets by default whenever a custom endpoint address is set, and that transport needs thewebsocket-clientpackage, oraiohttpfor the asynchronous client.azure-servicebusinstalls neither: install it next toazure-servicebus, or the client fails withImportError: Please install websocket-client library to use sync websocket transport. - Message size is not enforced: A message larger than the maximum message size of its entity, 256 KB on a Standard namespace, is accepted instead of being rejected.
- Shared access signatures are not verified: The emulator checks that a SAS token names the namespace or entity it is used for, but it does not verify the signature or the rights of the authorization rule that signed it.
- Connections without a token are accepted: Without RBAC enforcement, on a namespace that allows local authentication, a link whose connection did not present a token for its entity is accepted, whereas Azure refuses the link and closes a connection that does not present a valid token within 20 seconds.
- Short lock durations: A lock duration shorter than five seconds is reported as configured, but messages stay locked for five seconds.
- Container Apps managed identities: A Container Apps workload cannot authenticate to Service Bus with a managed identity yet, so it needs a connection string.
- RBAC enforcement is opt-in: By default, data-plane operations succeed regardless of role assignments. Set
LS_AZURE_ENFORCE_RBACto require a Microsoft Entra caller to hold an Azure Service Bus Data role. Role assignments take effect immediately, whereas Azure can take several minutes to apply them.
Samples
Section titled “Samples”The following samples demonstrate how to use Service Bus with LocalStack for Azure:
- Function App with Service Bus messaging (.NET)
- Service Bus with Spring Boot (Java)
- URL shortener with Web App, Functions, Storage, Key Vault, Service Bus, and PostgreSQL (Python)
- KEDA scaling an AKS workload on a Service Bus queue
API Coverage
Section titled “API Coverage”13 of 13 operations implemented
| Operation ▲ | Implemented ▼ |
|---|