Skip to content
Get Started for Free

Service Bus Data Plane

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.

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:

Terminal window
lstk az start-interception

This 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:

Terminal window
lstk az stop-interception

This reconfigures the az CLI to send commands to the official Azure management REST API.

Create a resource group for your Service Bus resources:

Terminal window
az group create \
--name rg-servicebus-dp-demo \
--location westeurope
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-servicebus-dp-demo",
"location": "westeurope",
...
"name": "rg-servicebus-dp-demo",
"properties": {
"provisioningState": "Succeeded"
},
...
}

Create a namespace for the queues and topics:

Terminal window
az servicebus namespace create \
--resource-group rg-servicebus-dp-demo \
--name sbnsdoc84 \
--location westeurope \
--sku Standard
Output
{
...
"name": "sbnsdoc84",
...
"provisioningState": "Succeeded",
...
"serviceBusEndpoint": "https://sbnsdoc84.servicebus.azure.localhost.localstack.cloud:4511/",
...
}

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:

Terminal window
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:

Terminal window
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:

Terminal window
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:

Terminal window
SB_TOKEN=$(get_sas_token "$SB_ENDPOINT" RootManageSharedAccessKey "$SB_KEY")

Create a queue via the data-plane Entity Put API:

Terminal window
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>'
Output
<?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:

Terminal window
curl -s "$SB_ENDPOINT/dpqueue?api-version=2021-05" \
-H "Authorization: $SB_TOKEN"
Output
<?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 a topic entity:

Terminal window
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:

Terminal window
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>'
Output
<?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:

Terminal window
curl -s "$SB_ENDPOINT/dptopic/subscriptions/dpsub?api-version=2021-05" \
-H "Authorization: $SB_TOKEN"
Output
<?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 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:

Terminal window
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}'
Output
HTTP/1.1 201 CREATED
Content-Type: application/xml; charset=utf-8
Content-Length: 0

Receive 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:

Terminal window
curl -s -i -X POST "$SB_ENDPOINT/dpqueue/messages/head?timeout=10" \
-H "Authorization: $SB_TOKEN" \
-H "Content-Length: 0" \
| tee peek-lock-response.txt
Output
HTTP/1.1 201 CREATED
Content-Type: application/json
Content-Length: 17
Priority: "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:

Terminal window
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"
Output
HTTP/1.1 200 OK
Content-Type: application/xml; charset=utf-8
Content-Length: 0

Send a second message, then receive it in receive-and-delete mode, which removes it from the queue as it is returned:

Terminal window
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"
Output
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 17
BrokerProperties: {"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:

Terminal window
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:

Terminal window
pip install azure-servicebus

Save the following code as send_receive.py. It sends a message to the queue, then receives and completes every message waiting in it:

send_receive.py
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:

Terminal window
python send_receive.py
Output
Received: Order 1003 created (subject: order-created)

Delete the subscription via Subscription Delete:

Terminal window
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:

Terminal window
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"

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 $Default rule.
  • 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 forwardTo and forwardDeadLetteredMessagesTo name.
  • Duplicate detection: On an entity with duplicate detection enabled, a message whose MessageId was 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.
  • 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_address in the Python SDK, is set to the namespace endpoint the emulator returns in serviceBusEndpoint. In turn, the Python SDK uses AMQP over WebSockets by default whenever a custom endpoint address is set, and that transport needs the websocket-client package, or aiohttp for the asynchronous client. azure-servicebus installs neither: install it next to azure-servicebus, or the client fails with ImportError: 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_RBAC to 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.

The following samples demonstrate how to use Service Bus with LocalStack for Azure:

13 of 13 operations implemented

Operation ▲Implemented ▼
Page 1 of 0
Was this page helpful?