Event-Driven Architectures with AWS EventBridge

Introduction

Event-Driven Order Processing with AWS EventBridge Order API API Gateway + Lambda Publishes OrderPlaced EventBridge Custom Bus orders.bus Schema Registry Archive & Replay Event Rules & Content-Based Filtering Pattern: source=com.nova-tech.orders, detail-type=OrderPlaced, detail.total >= 100 Inventory Service Lambda: Reserves stock DynamoDB update_item Notification Service Lambda: Sends email Orders > $100 only (filtered) Dead-Letter Queue SQS: Failed events Reprocessing & debugging Cross-Account Event Routing Finance Account (Producer) → Production Account (Consumer) via resource-based policies Legend Active Consumer

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:

AWS EventBridge
AWS Lambda
Amazon SQS
API Gateway
Amazon DynamoDB
Terraform

  • 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

End-to-End Event Processing Flow 1. HTTP POST /orders Customer places order 2. Lambda Producer Validate & put_events() 3. EventBridge Bus Route via rules 4. Consumers Process event Rule: Inventory (All Orders) Source: com.nova-tech.orders Rule: Notification (Orders > $100) Content filter: detail.total >= 100 DLQ: Failed Events SQS for retry/debug Verification & Monitoring ✅ curl POST /orders HTTP 201 with orderId ✅ CloudWatch Logs “Processing order ORD-…” ✅ DynamoDB Inventory reserved count incremented

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 source field 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, and TriggeredRules.
  • 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.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top