Introduction
Figure 1: Event-driven order processing architecture with AWS EventBridge custom event bus and content-filtered routing
Modern cloud applications are increasingly shifting from monolithic, request-response architectures to event-driven models. AWS EventBridge is a powerful serverless event bus service that enables you to build decoupled, scalable, and extensible applications by connecting your AWS services, custom applications, and SaaS partners through events.
In this tutorial, you will learn how to design, implement, and deploy an event-driven architecture using AWS EventBridge. We’ll cover everything from setting up event buses and writing rules to building event producers and consumers with AWS Lambda. By the end of this guide, you will have a fully functional event-driven system that processes orders in real time.
📖 Prerequisites
An AWS account with CLI access configured, AWS SAM CLI or Terraform installed, Python 3.9+, and basic familiarity with AWS Lambda and IAM.
What Is EventBridge and Why Use It?
AWS EventBridge is a fully managed, serverless event bus that simplifies event-driven application development. It replaces the older Amazon CloudWatch Events and provides several key advantages:
- Schema Registry: Automatically discovers and stores event schemas, enabling code generation for your consumers.
- SaaS Integration: Native support for over 90 SaaS partners including Datadog, Shopify, Zendesk, and PagerDuty.
- Content-Based Filtering: Filter events by JSON content, not just by source and type.
- Cross-Account/Across-Region: Route events between AWS accounts and regions securely.
- Archive and Replay: Store events for up to 365 days and replay them for debugging or recovery.
EventBridge fits perfectly into a microservices architecture where services need to react to state changes without tight coupling. An order service publishes an “OrderPlaced” event; an inventory service, a notification service, and a shipping service all consume that event independently — no service knows about the others.
Core Concepts
Event Buses
An event bus is the central router that receives and delivers events. Every AWS account comes with a default event bus that receives events from AWS services (EC2 state changes, S3 object creates, etc.). You can create custom event buses for your own application events and partner event buses for SaaS integration.
Events
An event is a JSON payload that describes a state change. Every EventBridge event follows this structure:
{
"version": "0",
"id": "6a7e8f9b-c234-4d12-a345-6e7f8g9h0i12",
"detail-type": "OrderPlaced",
"source": "com.nova-tech.orders",
"account": "123456789012",
"time": "2026-05-19T14:00:00Z",
"region": "eu-west-1",
"resources": [],
"detail": {
"orderId": "ORD-98765",
"customerId": "CUST-4321",
"total": 299.99,
"items": [
{"sku": "CLD-001", "quantity": 2},
{"sku": "CLD-002", "quantity": 1}
]
}
}
Rules
Rules match incoming events and route them to one or more targets. Rules use event pattern matching — JSON-based filters that can check event source, detail-type, and even specific fields within the detail payload.
Targets
Targets are the services that process matched events. Supported targets include AWS Lambda, Step Functions, SQS, SNS, Kinesis, ECS tasks, API Gateway, and dozens more. You can also send events to another event bus (cross-bus or cross-account).
Architecture Overview
We’ll build an order processing system with the following components:
| Component | Service | Role |
| Order API | API Gateway + Lambda | Accepts HTTP requests and publishes OrderPlaced events |
| Custom Event Bus | EventBridge | Routes events to the correct consumers |
| Inventory Service | Lambda | Reserves stock when an order is placed |
| Notification Service | Lambda | Sends email confirmation to the customer |
| Dead-Letter Queue | SQS | Captures failed events for reprocessing |
Step 1: Creating a Custom Event Bus
Let’s start by creating a custom event bus named orders.bus. You can do this via the AWS Console, CLI, or Infrastructure as Code. We’ll use the AWS CLI:
aws events create-event-bus --name orders.bus
Verify it was created:
aws events list-event-buses --query "EventBuses[?Name=='orders.bus']"
If you’re using Terraform, the resource looks like this:
resource "aws_cloudwatch_event_bus" "orders" {
name = "orders.bus"
}
Step 2: Creating the Order Producer (Lambda + API Gateway)
The order producer is an HTTP-triggered Lambda function that validates an incoming order and publishes an event to our custom bus.
Lambda Function Code (Python)
import json
import os
import uuid
import boto3
from datetime import datetime
client = boto3.client("events")
EVENT_BUS_NAME = os.environ["EVENT_BUS_NAME"]
def lambda_handler(event, context):
# Parse and validate the incoming order
body = json.loads(event["body"])
required_fields = ["customerId", "items", "total"]
for field in required_fields:
if field not in body:
return {
"statusCode": 400,
"body": json.dumps({"error": f"Missing field: {field}"})
}
order_id = f"ORD-{uuid.uuid4().hex[:8].upper()}"
detail = {
"orderId": order_id,
"customerId": body["customerId"],
"total": body["total"],
"items": body["items"],
"timestamp": datetime.utcnow().isoformat()
}
# Publish event to EventBridge
response = client.put_events(
Entries=[
{
"Source": "com.nova-tech.orders",
"DetailType": "OrderPlaced",
"Detail": json.dumps(detail),
"EventBusName": EVENT_BUS_NAME
}
]
)
print(f"Published event: {response['Entries'][0]['EventId']}")
return {
"statusCode": 201,
"body": json.dumps({
"orderId": order_id,
"message": "Order placed successfully",
"eventId": response["Entries"][0]["EventId"]
})
}
IAM Role for the Producer
The Lambda function needs permission to publish events to our custom bus:
{
"Effect": "Allow",
"Action": "events:PutEvents",
"Resource": "arn:aws:events:eu-west-1:123456789012:event-bus/orders.bus"
}
Step 3: Creating Event Rules and Targets
Now we’ll create rules that match our OrderPlaced events and route them to various consumers.
Inventory Service Rule
This rule matches all OrderPlaced events and sends them to an inventory Lambda function.
aws events put-rule \
--name orders-inventory-rule \
--event-bus-name orders.bus \
--event-pattern '{
"source": ["com.nova-tech.orders"],
"detail-type": ["OrderPlaced"]
}'
Add the inventory Lambda as a target:
aws events put-targets \
--rule orders-inventory-rule \
--event-bus-name orders.bus \
--targets "Id"="InventoryTarget1","Arn"="arn:aws:lambda:eu-west-1:123456789012:function:inventory-service"
Notification Service Rule with Content Filtering
For content-based filtering, let’s say we only want to send notifications for orders above $100:
aws events put-rule \
--name orders-notification-rule \
--event-bus-name orders.bus \
--event-pattern '{
"source": ["com.nova-tech.orders"],
"detail-type": ["OrderPlaced"],
"detail": {
"total": [{"numeric": [">=", 100]}]
}
}'
💡 Pro Tip
EventBridge supports numeric comparisons (>, >=, <, <=), prefix matching, exists checks, and array matching — giving you powerful filtering without any consumer-side logic.
Adding a Dead-Letter Queue
Always configure a dead-letter queue (DLQ) to catch events that can’t be delivered:
aws events put-targets \
--rule orders-inventory-rule \
--event-bus-name orders.bus \
--targets "Id"="InventoryTarget1","Arn"="arn:aws:lambda:eu-west-1:123456789012:function:inventory-service","DeadLetterConfig"="{\"Arn\":\"arn:aws:sqs:eu-west-1:123456789012:orders-dlq\"}"
Step 4: Building Event Consumers
Here’s the inventory service Lambda that processes OrderPlaced events:
import json
import boto3
dynamodb = boto3.resource("dynamodb")
table = dynamodb.Table("inventory")
def lambda_handler(event, context):
for record in event["Records"]:
# EventBridge delivers wrapped events when using Lambda targets
detail = json.loads(record["body"])["detail"]
order_id = detail["orderId"]
items = detail["items"]
print(f"Processing order {order_id}: reserving inventory")
for item in items:
sku = item["sku"]
quantity = item["quantity"]
# Reserve stock
response = table.update_item(
Key={"sku": sku},
UpdateExpression="SET reserved = reserved + :qty",
ConditionExpression="available - reserved >= :qty",
ExpressionAttributeValues={
":qty": quantity,
":reserved": 0
}
)
return {"statusCode": 200}
Step 5: Using the Schema Registry
One of EventBridge’s most powerful features is the Schema Registry. When you enable it, EventBridge automatically discovers event schemas and generates code bindings in Python, Java, TypeScript, and Go.
aws events start-schema-discovery \
--enabled \
--event-bus orders.bus
Once enabled, you can download the generated SDK and use strongly-typed event objects:
pip install event-bus-schema-sdk
from order_schema import OrderPlacedEvent
def handler(event, context):
# The SDK gives you typed access to event fields
order = OrderPlacedEvent.from_dict(json.loads(event["body"]))
print(f"Customer: {order.detail.customerId}")
print(f"Total: {order.detail.total}")
💡 Tip
The Schema Registry also validates events against their registered schema, catching malformed events at the bus level before they reach consumers.
Step 6: Cross-Account Event Routing
In larger organizations, teams often operate in separate AWS accounts. EventBridge supports cross-account event routing so a billing event in the Finance account can trigger a resource update in the Production account.
In the Source Account (Producer)
aws events put-rule \
--name cross-account-billing \
--event-bus-name default \
--event-pattern '{"source": ["aws.billing"]}'
aws events put-targets \
--rule cross-account-billing \
--event-bus-name default \
--targets "Id"="CrossAccountBus","Arn"="arn:aws:events:eu-west-1:PRODUCER_ACCOUNT:event-bus/default"
In the Target Account (Consumer)
aws events put-permission \
--event-bus-name orders.bus \
--statement-id AllowBillingAccount \
--action events:PutEvents \
--principal PRODUCER_ACCOUNT
Step 7: Archive and Replay
Event archiving allows you to store all events for a specified retention period and replay them later — critical for debugging production issues.
aws events create-archive \
--archive-name orders-archive \
--event-source-arn arn:aws:events:eu-west-1:123456789012:event-bus/orders.bus \
--retention-days 30
To replay events from a specific time window:
aws events start-replay \
--replay-name replay-20260519 \
--event-source-arn arn:aws:events:eu-west-1:123456789012:event-bus/orders.bus \
--destination '{"Arn":"arn:aws:events:eu-west-1:123456789012:event-bus/orders.bus"}' \
--event-start-time 2026-05-19T00:00:00Z \
--event-end-time 2026-05-19T14:00:00Z
⚠️ Important
Archived events count toward your storage costs. Set retention based on your compliance and debugging needs — 7 to 30 days is typical for most workloads.
Testing the End-to-End Flow
Figure 2: End-to-end event processing flow from order placement through EventBridge routing to consumer services
With everything deployed, test the full flow by sending an HTTP request to the Order API:
curl -X POST https://api-gateway-url.execute-api.eu-west-1.amazonaws.com/prod/orders \
-H "Content-Type: application/json" \
-d '{
"customerId": "CUST-4321",
"total": 299.99,
"items": [
{"sku": "CLD-001", "quantity": 2},
{"sku": "CLD-002", "quantity": 1}
]
}'
Check CloudWatch Logs for each consumer Lambda to verify they received and processed the event:
aws logs filter-log-events \
--log-group-name /aws/lambda/inventory-service \
--filter-pattern "Processing order"
Best Practices for Production
- Use explicit event sourcing: Always set a meaningful
sourcefield using reverse domain notation (com.yourcompany.servicename). - Design for idempotency: EventBridge guarantees at-least-once delivery. Your consumers must handle duplicate events gracefully by checking unique event IDs.
- Set DLQs on every rule: Without a DLQ, failed events are silently dropped. Always configure one, even during development.
- Use content-based filtering: Filter early and filter often. Push filtering logic into EventBridge rules rather than consumer code — it reduces costs and complexity.
- Monitor rule invocations: Enable CloudWatch metrics on your event bus to track
Invocations,FailedInvocations, andTriggeredRules. - Version your event schemas: As your system evolves, you’ll need to modify event payloads. Use the Schema Registry to manage versioning and communicate changes to consumer teams.
Conclusion
AWS EventBridge provides a robust, fully managed foundation for event-driven architectures. In this tutorial, you built a complete order processing system with a custom event bus, content-filtered rules, two consumer services, a dead-letter queue, and cross-account routing capability.
The shift from request-response to event-driven thinking takes practice, but the benefits — loose coupling, scalability, extensibility, and resilience — are transformative for cloud-native applications. Start small with one business event and a single consumer, then expand your event mesh as your confidence grows.
EventBridge is not just a replacement for message queues; it’s a new paradigm for how services communicate. By adopting event-driven patterns early, you position your architecture for long-term flexibility and growth.