Home / Data Platform / Streaming & CDC

Data PlatformHow-toBeginner

Azure Event Hubs basics: create a namespace, send events and read them with Python

Learn Event Hubs by using it. Create a namespace and an event hub, grant access with Entra ID, send and receive with Python, save your position with checkpoints and read the metrics that show if it's healthy.

Deepak Bhardwaj6 min read

Applies toAzure Event Hubs Standard tier, Azure CLI, azure-eventhub Python SDK 5.x

The goal

You've been asked to look at a pipeline that uses Event Hubs, or to feed events into one. Event Hubs is a managed service for high-volume event streams. It's a lot like Kafka, and it even has a Kafka-compatible endpoint. Here, you'll build a small one, send events and read them back.

The ideas you need

Term Meaning Kafka equivalent
Namespace Container and endpoint Cluster
Event hub A named stream Topic
Partition An ordered slice of the stream Partition
Consumer group One reader's independent view Consumer group
Checkpoint A saved position Committed offset

Consumers read events without removing them. Events stay until the retention period ends, so a new consumer group can read older data too.

Step 1: create the resources

RG=rg-eventhub-lab
NS=ehns-lab-$RANDOM
EH=orders

az group create -n "$RG" -l centralindia

az eventhubs namespace create -g "$RG" -n "$NS" -l centralindia --sku Standard

az eventhubs eventhub create -g "$RG" --namespace-name "$NS" -n "$EH" \
  --partition-count 2 \
  --cleanup-policy Delete \
  --retention-time-in-hours 24

If your CLI rejects the retention flags, run az eventhubs eventhub create --help, as the names have changed across versions.

Choose the partition count with care, because on the Standard tier you can't change it later. For sizing, see tiers and auto-inflate.

Step 2: grant access with Entra ID

Avoid connection strings where you can. Use a data role:

ME=$(az ad signed-in-user show --query id -o tsv)
SCOPE=$(az eventhubs namespace show -g "$RG" -n "$NS" --query id -o tsv)

az role assignment create --assignee-object-id "$ME" --assignee-principal-type User \
  --role "Azure Event Hubs Data Owner" --scope "$SCOPE"
Role Allows
Azure Event Hubs Data Sender Send
Azure Event Hubs Data Receiver Read
Azure Event Hubs Data Owner Full data access

Use Sender or Receiver for real applications. Role assignments can take a few minutes to apply.

Step 3: send events

pip install azure-eventhub azure-identity

Save this as send.py. Replace the namespace name:

import json
from azure.eventhub import EventHubProducerClient, EventData
from azure.identity import DefaultAzureCredential

NAMESPACE = "ehns-lab-12345.servicebus.windows.net"
EVENT_HUB = "orders"

producer = EventHubProducerClient(
    fully_qualified_namespace=NAMESPACE,
    eventhub_name=EVENT_HUB,
    credential=DefaultAzureCredential(),
)

with producer:
    batch = producer.create_batch(partition_key="customer-100")
    for i in range(10):
        body = json.dumps({"order_id": i, "customer": "customer-100", "amount": 10 + i})
        batch.add(EventData(body))
    producer.send_batch(batch)

print("Sent 10 events")

DefaultAzureCredential picks up your az login session here, and a managed identity when running in Azure.

Events with the same partition_key go to the same partition, so they stay in order relative to each other. Ordering isn't guaranteed across partitions.

If you add more events than fit, batch.add raises ValueError. Real code sends the batch and starts a new one when that happens.

Step 4: read events

Save this as receive.py:

from azure.eventhub import EventHubConsumerClient
from azure.identity import DefaultAzureCredential

NAMESPACE = "ehns-lab-12345.servicebus.windows.net"
EVENT_HUB = "orders"

def on_event(partition_context, event):
    print(partition_context.partition_id, event.sequence_number, event.body_as_str())

client = EventHubConsumerClient(
    fully_qualified_namespace=NAMESPACE,
    eventhub_name=EVENT_HUB,
    consumer_group="$Default",
    credential=DefaultAzureCredential(),
)

with client:
    # "-1" means start from the beginning of the stream
    client.receive(on_event=on_event, starting_position="-1")

Run it in one terminal, and then run send.py in another. Press Ctrl+C to stop.

Run it twice and you'll see the same events again. It has no memory of where it stopped, which brings us to checkpoints.

Step 5: save your position

A checkpoint stores how far you've read, in a Blob Storage container:

pip install azure-eventhub-checkpointstoreblob

az storage account create -g "$RG" -n stehlab$RANDOM --sku Standard_LRS

Create a container, and grant yourself Storage Blob Data Contributor on the account. Then:

from azure.eventhub import EventHubConsumerClient
from azure.eventhub.extensions.checkpointstoreblob import BlobCheckpointStore
from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()

store = BlobCheckpointStore(
    blob_account_url="https://stehlab12345.blob.core.windows.net",
    container_name="checkpoints",
    credential=credential,
)

def on_event(partition_context, event):
    print(event.body_as_str())
    partition_context.update_checkpoint(event)

client = EventHubConsumerClient(
    fully_qualified_namespace="ehns-lab-12345.servicebus.windows.net",
    eventhub_name="orders",
    consumer_group="$Default",
    credential=credential,
    checkpoint_store=store,
)

with client:
    client.receive(on_event=on_event, starting_position="-1")

Now a restart continues from where it left off. Calling update_checkpoint for each event is slow at high volume, so real consumers do it every few seconds or every N events. That also means a crash can replay events, so make your processing able to handle duplicates.

The store also lets several copies of the consumer share partitions between them. If they keep stealing partitions from each other, see epoch and ownership errors.

Step 6: check its health

ID=$(az eventhubs namespace show -g "$RG" -n "$NS" --query id -o tsv)

az monitor metrics list --resource "$ID" \
  --metric IncomingMessages OutgoingMessages ThrottledRequests \
  --interval PT5M --aggregation Total -o table
Metric What to watch for
IncomingMessages A sudden drop means producers have stopped
OutgoingMessages Falling behind incoming, while consumers should be keeping up
ThrottledRequests Above zero means you need more capacity

Common errors

Error Likely cause Fix
Unauthorized or CBS token errors Missing data role Check the role and its scope, then wait a few minutes
ServerBusy Capacity limit reached See throttling
Connection timeouts Firewall blocks AMQP on port 5671 Open it, or use WebSockets (port 443)
EventDataSendError or size error Event or batch is too large Split the batch, and check the tier limit
MessagingEntityNotFound Wrong event hub name Check the name, and the namespace
Consumer gets nothing Starting at the latest position Use starting_position="-1" while testing

Clean up

az group delete -n "$RG" --yes --no-wait

Caution

This deletes the namespace, its events and your checkpoint storage account. Confirm that the group holds only lab resources before you run it.

Prevent it next time

  • Use one consumer group per application, so readers don't affect each other.
  • Choose partition keys that spread load evenly. A single busy key can overload a partition.
  • Alert on throttling, and on consumers falling behind.
  • If your apps already speak Kafka, see the Kafka endpoint errors post, and choosing a streaming platform.