Skip to content
Get Started for Free

Event Hubs

Azure Event Hubs is a real-time data streaming platform that ingests and processes millions of events per second. Producers append events to partitioned logs, and consumers read those logs independently at their own pace, which makes Event Hubs a common backbone for telemetry pipelines, event sourcing, and stream analytics. It exposes its data plane over AMQP 1.0, HTTPS, and a Kafka-compatible endpoint. For more information, see What is Azure Event Hubs?

LocalStack for Azure provides a local environment for building and testing applications that make use of Azure Event Hubs. The supported APIs are available on our API Coverage section, which provides information on the extent of Event Hubs’ integration with LocalStack.

This guide is designed for users new to Event Hubs 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 to contain your Event Hubs resources:

Terminal window
az group create \
--name rg-eventhubs-demo \
--location westeurope
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo",
"location": "westeurope",
"managedBy": null,
"name": "rg-eventhubs-demo",
"properties": {
"provisioningState": "Succeeded"
},
"tags": null,
"type": "Microsoft.Resources/resourceGroups"
}

Create an Event Hubs namespace in the resource group:

Terminal window
az eventhubs namespace create \
--resource-group rg-eventhubs-demo \
--name ehnsdoc42 \
--location westeurope \
--sku Standard
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.EventHub/namespaces/ehnsdoc42",
"name": "ehnsdoc42",
"location": "westeurope",
"kafkaEnabled": true,
"provisioningState": "Succeeded",
"serviceBusEndpoint": "https://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4511",
"sku": {
"capacity": 1,
"name": "Standard",
"tier": "Standard"
},
"status": "Active",
...
}

Create an event hub in the namespace:

Terminal window
az eventhubs eventhub create \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--name orders-hub \
--partition-count 4 \
--cleanup-policy Delete \
--retention-time 168
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.EventHub/namespaces/ehnsdoc42/eventhubs/orders-hub",
"name": "orders-hub",
"location": "westeurope",
"messageRetentionInDays": 7,
"partitionCount": 4,
"partitionIds": [
"0",
"1",
"2",
"3"
],
"retentionDescription": {
"cleanupPolicy": "Delete",
"retentionTimeInHours": 168
},
"status": "Active",
"type": "Microsoft.EventHub/namespaces/eventhubs",
...
}

Create a consumer group for the event hub:

Terminal window
az eventhubs eventhub consumer-group create \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--eventhub-name orders-hub \
--name analytics
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.EventHub/namespaces/ehnsdoc42/eventhubs/orders-hub/consumergroups/analytics",
"name": "analytics",
"type": "Microsoft.EventHub/namespaces/eventhubs/consumergroups",
...
}

List the consumer groups of the event hub:

Terminal window
az eventhubs eventhub consumer-group list \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--eventhub-name orders-hub \
--query "[].name" -o json
Output
[
"$Default",
"analytics"
]

Manage authorization rules and connection strings

Section titled “Manage authorization rules and connection strings”

Create a send-only authorization rule on the event hub:

Terminal window
az eventhubs eventhub authorization-rule create \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--eventhub-name orders-hub \
--name send-policy \
--rights Send
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.EventHub/namespaces/ehnsdoc42/eventhubs/orders-hub/authorizationRules/send-policy",
"name": "send-policy",
"rights": [
"Send"
],
"type": "Microsoft.EventHub/namespaces/eventhubs/authorizationrules"
}

List the connection strings for the rule:

Terminal window
az eventhubs eventhub authorization-rule keys list \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--eventhub-name orders-hub \
--name send-policy
Output
{
"keyName": "send-policy",
"primaryConnectionString": "Endpoint=sb://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4511/;SharedAccessKeyName=send-policy;SharedAccessKey=...;UseDevelopmentEmulator=true;EntityPath=orders-hub",
"primaryKey": "...",
"secondaryConnectionString": "Endpoint=sb://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4511/;SharedAccessKeyName=send-policy;SharedAccessKey=...;UseDevelopmentEmulator=true;EntityPath=orders-hub",
"secondaryKey": "..."
}

The namespace-level RootManageSharedAccessKey rule is created automatically with the namespace:

Terminal window
az eventhubs namespace authorization-rule keys list \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--name RootManageSharedAccessKey
Output
{
"keyName": "RootManageSharedAccessKey",
"primaryConnectionString": "Endpoint=sb://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4511/;SharedAccessKeyName=RootManageSharedAccessKey;SharedAccessKey=...;UseDevelopmentEmulator=true",
...
}

The Event Hubs runtime REST API accepts events on the namespace endpoint. Send a single event to the event hub:

Terminal window
curl -sk -i -X POST \
"https://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4566/orders-hub/messages" \
-H "Authorization: SharedAccessSignature sr=...&sig=...&se=...&skn=RootManageSharedAccessKey" \
-H "Content-Type: application/json" \
-d '{"orderId": 1001, "status": "created"}'
Output
HTTP/2 201
server: TwistedWeb/26.4.0
content-type: application/xml; charset=utf-8
content-length: 0

Route an event to a specific partition by partition key using the BrokerProperties header:

Terminal window
curl -sk -i -X POST \
"https://ehnsdoc42.servicebus.azure.localhost.localstack.cloud:4566/orders-hub/messages" \
-H "Authorization: SharedAccessSignature sr=...&sig=...&se=...&skn=RootManageSharedAccessKey" \
-H 'BrokerProperties: {"PartitionKey": "customer-42"}' \
-H "Content-Type: application/json" \
-d '{"orderId": 1002, "status": "created"}'
Output
HTTP/2 201
server: TwistedWeb/26.4.0
content-type: application/xml; charset=utf-8
content-length: 0

The namespace endpoint also serves the Kafka protocol. Configure a Kafka client exactly as for Azure: SASL_SSL with the PLAIN mechanism, the literal username $ConnectionString, and the namespace connection string as the password. For more information, see Azure Event Hubs for Apache Kafka. The bootstrap server is the host and port of the connection string’s endpoint; an Azure connection string carries no port, so the script below falls back to 9093, the Kafka port in Azure.

Read the namespace connection string into an environment variable:

Terminal window
export EVENTHUB_CONNECTION_STRING=$(az eventhubs namespace authorization-rule keys list \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--name RootManageSharedAccessKey \
--query primaryConnectionString \
--output tsv)

Install the confluent-kafka client library for Python:

Terminal window
pip install confluent-kafka

Save the following code as kafka_orders.py. It produces an event to the event hub, then reads the event hub from the beginning:

kafka_orders.py
import os
from urllib.parse import urlparse
from confluent_kafka import Consumer, Producer
connection_string = os.environ["EVENTHUB_CONNECTION_STRING"]
endpoint = urlparse(connection_string.split(";")[0].removeprefix("Endpoint="))
config = {
"bootstrap.servers": f"{endpoint.hostname}:{endpoint.port or 9093}",
"security.protocol": "SASL_SSL",
"sasl.mechanism": "PLAIN",
"sasl.username": "$ConnectionString",
"sasl.password": connection_string,
}
def on_delivery(err, msg):
if err is not None:
raise SystemExit(f"the event was not delivered: {err}")
producer = Producer(config)
producer.produce(
"orders-hub",
value=b'{"orderId": 1003, "status": "created"}',
on_delivery=on_delivery,
)
if producer.flush(30):
raise SystemExit("the event was not delivered")
consumer = Consumer({
**config,
"group.id": "kafka-demo",
"auto.offset.reset": "earliest",
"enable.auto.commit": False,
})
consumer.subscribe(["orders-hub"])
while (message := consumer.poll(timeout=15)) is not None:
if message.error():
raise SystemExit(message.error())
print(message.value().decode())
consumer.close()

Run it:

Terminal window
python kafka_orders.py
Output
{"orderId": 1001, "status": "created"}
{"orderId": 1002, "status": "created"}
{"orderId": 1003, "status": "created"}

The consumer receives the two events sent over HTTP in the previous step as well as the one produced over Kafka, because all protocols share one log. Events from different partitions can arrive in a different order.

Event Hubs Capture archives the event stream into a storage account as Avro files. Create a storage account for the archives:

Terminal window
az storage account create \
--resource-group rg-eventhubs-demo \
--name stehcapturels \
--location westeurope \
--sku Standard_LRS
Output
{
"id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.Storage/storageAccounts/stehcapturels",
"name": "stehcapturels",
"kind": "StorageV2",
"location": "westeurope",
"primaryEndpoints": {
"blob": "https://stehcapturels.blob.core.azure.localhost.localstack.cloud:4566",
...
},
"provisioningState": "Succeeded",
...
}

Enable Capture on the event hub:

Terminal window
STORAGE_ID=$(az storage account show \
--resource-group rg-eventhubs-demo \
--name stehcapturels \
--query id -o tsv)
az eventhubs eventhub update \
--resource-group rg-eventhubs-demo \
--namespace-name ehnsdoc42 \
--name orders-hub \
--enable-capture true \
--capture-interval 60 \
--capture-size-limit 10485760 \
--destination-name EventHubArchive.AzureBlockBlob \
--storage-account "$STORAGE_ID" \
--blob-container eventhub-capture \
--skip-empty-archives true
Output
{
"captureDescription": {
"destination": {
"blobContainer": "eventhub-capture",
"name": "EventHubArchive.AzureBlockBlob",
"storageAccountResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-eventhubs-demo/providers/Microsoft.Storage/storageAccounts/stehcapturels"
},
"enabled": true,
"encoding": "Avro",
"intervalInSeconds": 60,
"sizeLimitInBytes": 10485760,
"skipEmptyArchives": true
},
"name": "orders-hub",
...
}

Send a few more events, wait for the capture window to elapse (60 seconds here), and list the archives:

Terminal window
STORAGE_CONNECTION=$(az storage account show-connection-string \
--resource-group rg-eventhubs-demo \
--name stehcapturels \
--query connectionString -o tsv)
az storage blob list \
--container-name eventhub-capture \
--connection-string "$STORAGE_CONNECTION" \
--query "[].{name:name, bytes:properties.contentLength}" -o json
Output
[
{
"bytes": 605,
"name": "ehnsdoc42/orders-hub/0/2026/08/09/19/23/32.avro"
},
{
"bytes": 674,
"name": "ehnsdoc42/orders-hub/1/2026/08/09/19/23/32.avro"
},
{
"bytes": 605,
"name": "ehnsdoc42/orders-hub/2/2026/08/09/19/23/32.avro"
},
{
"bytes": 605,
"name": "ehnsdoc42/orders-hub/3/2026/08/09/19/23/32.avro"
}
]

The archives are Avro object-container files in Azure’s EventData record schema, one per partition and capture window. After each archive is written, the emulator raises the Microsoft.EventHub.CaptureFileCreated event through Event Grid system topics, so downstream automation such as an Azure Function can react to new archives.

The emulator includes the following core capabilities:

  • Multi-protocol data plane on a single event log: AMQP 1.0 (what the official azure-eventhub SDKs speak, including AMQP-over-WebSockets), a Kafka-compatible endpoint, the HTTP runtime API, and the legacy Atom-XML management API all read and write the same per-partition log, so an event sent over one protocol is readable over the others.
  • ARM control plane: CRUD for namespaces, event hubs, consumer groups, authorization rules and keys, network rule sets, schema groups, application groups, geo-disaster-recovery configurations, and dedicated clusters — with tier quotas enforced as in Azure (event hub counts, retention caps, partition limits, and the Basic-tier consumer-group restriction).
  • Event Hubs Capture: per-partition Avro archives written to emulated Blob Storage on time- or size-based windows, including skipEmptyArchives, and the Microsoft.EventHub.CaptureFileCreated system event raised through Event Grid.
  • Schema Registry: the data-plane API served on the namespace endpoint, with schema group CRUD, schema registration and versioning, lookup by content, and the tier quota on schema groups.
  • Checkpointing: the SDK’s event processor pattern works with the blob-backed checkpoint store against emulated Blob Storage.
  • Kafka-compatible endpoint: topics map to event hubs, Kafka offsets equal Event Hubs sequence numbers, and consumer-group offsets are stored broker-side; verified with kafka-python and confluent-kafka. Clients use the settings Azure requires (security.protocol=SASL_SSL, sasl.mechanism=PLAIN, sasl.username=$ConnectionString, and the connection string as sasl.password), with the bootstrap server read from the emulator’s connection string; SASL_SSL is verified with confluent-kafka.
  • Emulator-ready connection strings: keys list returns connection strings that the Azure SDKs, Kafka clients, and IaC tools can use verbatim against the emulator.

The current version of the emulator does not support the following:

  • Credential verification: SAS tokens, JWTs, and Kafka SASL credentials are checked for the existence of the target namespace or entity, but signatures and keys are not verified. The Kafka endpoint also accepts plaintext, SSL without SASL, and SASL_PLAINTEXT clients, which Azure rejects.
  • Microsoft Entra ID on the Kafka endpoint: only SASL PLAIN with a connection string is supported, so Kafka clients that authenticate with OAUTHBEARER are rejected with UNSUPPORTED_SASL_MECHANISM.
  • Azure’s partition-key hash: the partition-key-to-partition mapping is a stable hash, so events with the same key land on the same partition, but the concrete partition ids differ from real Azure.
  • Kafka namespace isolation: Kafka topics resolve across all Kafka-enabled namespaces, and Kafka consumer-group offsets and producer ids are held in memory only.
  • Enforcement of throughput and network controls: throughput-unit throttling, application-group policies, network rule sets, private endpoints, and geo-disaster-recovery are stored and echoed as metadata, but not enforced — a geo-DR failover changes state without moving events.
  • Dedicated cluster hardware: clusters are metadata-level, and Azure’s 4-hour cluster deletion moratorium is intentionally skipped so local clean-up is immediate.
  • Receiving over HTTP: the HTTP runtime API is send-only, as in Azure; receiving requires AMQP or Kafka.
  • Persistence: entities and event data are held in memory and are lost when the emulator restarts. Recreate resources on startup via the CLI or IaC.

Explore the following samples to get started with Event Hubs on LocalStack:

68 of 74 operations implemented

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