AWS EventBridge: Event-Driven Cloud Architecture
AWS EventBridge event-driven architecture enables loosely coupled, scalable systems where services communicate through events rather than direct API calls. EventBridge is a serverless event bus that routes events from AWS services, custom applications, and SaaS providers to target services based on rules. Therefore, adding new consumers or modifying event processing requires no changes to event producers. A producer simply publishes an “OrderCreated” event and walks away; whether one consumer reacts or ten, and whether those consumers are Lambda functions, SQS queues, or external webhooks, is entirely a routing concern handled by the bus.
Event-driven architecture reduces coupling between services, improves scalability, and enables real-time processing. Moreover, EventBridge’s schema registry automatically discovers event structures, making integration easier. Consequently, EventBridge has become the backbone of modern serverless architectures on AWS. That said, decoupling is not free: you trade the straightforward request/response debugging of a synchronous call for the harder problem of tracing an event as it fans out across asynchronous consumers. The patterns below cover both the power and the sharp edges.
AWS EventBridge Event-Driven: Event Bus Design
Organize events using custom event buses for different domains or environments. Each bus has its own rules and targets, providing isolation and access control. Furthermore, use event patterns in rules to filter events precisely, ensuring each consumer receives only relevant events.
{
"Comment": "Order domain event",
"source": "com.myapp.orders",
"detail-type": "OrderCreated",
"detail": {
"orderId": "ORD-12345",
"customerId": "CUST-678",
"items": [
{"productId": "PROD-001", "quantity": 2, "price": 29.99}
],
"total": 59.98,
"currency": "USD",
"timestamp": "2026-04-07T10:30:00Z"
}
}
// EventBridge Rule — route to multiple targets
// Rule pattern (matches OrderCreated events over $100):
{
"source": ["com.myapp.orders"],
"detail-type": ["OrderCreated"],
"detail": {
"total": [{"numeric": [">", 100]}]
}
}
Designing Events That Age Well
The hardest part of an event-driven system is not the plumbing — it is the event contract. Once another team subscribes to your OrderCreated event, that JSON shape becomes a public API, and breaking it breaks them silently. Therefore, treat events as versioned contracts from day one. A practical convention is to embed a version in the detail-type (for example OrderCreated.v1) so that a future OrderCreated.v2 can run side by side while consumers migrate. Equally important is the choice between “thin” and “fat” events. A thin event carries only identifiers and forces consumers to call back for details; a fat event carries enough state to be processed standalone.
// A "fat" event carries the state consumers need — no callback required.
// Producers should publish via the AWS SDK v2 EventBridgeClient.
public record OrderCreated(
String eventVersion, // "1.0" — bump on breaking changes
String orderId,
String customerId,
List<LineItem> items,
BigDecimal total,
String currency,
Instant occurredAt // when it happened, not when delivered
) {}
PutEventsRequestEntry entry = PutEventsRequestEntry.builder()
.eventBusName("order-events")
.source("com.myapp.orders")
.detailType("OrderCreated.v1")
.detail(objectMapper.writeValueAsString(order))
.build();
PutEventsResponse resp = eventBridge.putEvents(
PutEventsRequest.builder().entries(entry).build());
// CRITICAL: putEvents can partially fail. A 200 HTTP status does
// NOT mean every event was accepted — you must inspect the result.
if (resp.failedEntryCount() > 0) {
resp.entries().stream()
.filter(e -> e.errorCode() != null)
.forEach(e -> log.error("event rejected: {} {}",
e.errorCode(), e.errorMessage()));
// retry the failed entries with backoff
}
The partial-failure check in that snippet is the single most-missed detail in EventBridge integrations. The PutEvents API returns HTTP 200 even when individual entries are rejected, so code that only checks the status code will drop events without ever knowing. Always read failedEntryCount and retry the rejected entries.
Integration Patterns
EventBridge supports fan-out (one event to many targets), content-based routing (different targets based on event content), and event transformation (modify events before delivery). Additionally, API Destinations enable EventBridge to call external HTTP APIs as targets, connecting your event-driven architecture to any webhook-compatible service.
# CloudFormation: EventBridge rules with multiple targets
OrderCreatedRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source: ["com.myapp.orders"]
detail-type: ["OrderCreated"]
Targets:
# Fan-out to multiple consumers
- Id: ProcessPayment
Arn: !GetAtt PaymentFunction.Arn
- Id: UpdateInventory
Arn: !GetAtt InventoryFunction.Arn
- Id: SendConfirmation
Arn: !Ref NotificationQueue
- Id: AnalyticsStream
Arn: !GetAtt AnalyticsStream.Arn
InputTransformer:
InputPathsMap:
orderId: "$.detail.orderId"
total: "$.detail.total"
InputTemplate: '{"event":"order_created","orderId":"<orderId>","amount":<total>}'
HighValueOrderRule:
Type: AWS::Events::Rule
Properties:
EventBusName: !Ref OrderEventBus
EventPattern:
source: ["com.myapp.orders"]
detail-type: ["OrderCreated"]
detail:
total: [{"numeric": [">", 1000]}]
Targets:
- Id: FraudCheck
Arn: !GetAtt FraudCheckFunction.Arn
- Id: VIPNotification
Arn: !Ref VIPNotificationTopic
Delivery Guarantees, Retries, and the Dead-Letter Queue
EventBridge delivers events at least once, which has two practical consequences every consumer must handle. First, duplicates happen — a consumer can receive the same event more than once, so processing must be idempotent. The standard remedy is to record a unique idempotency key (the orderId plus event type, for instance) and short-circuit if you have already handled it. Second, delivery can fail: a target Lambda might throttle, time out, or throw. EventBridge retries failed deliveries with exponential backoff for up to 24 hours by default, but if every retry fails, the event is dropped unless you attach a dead-letter queue. Always configure a DLQ on production targets so failed events land somewhere recoverable instead of vanishing.
# A target with a DLQ and bounded retries — never lose events silently
Targets:
- Id: ProcessPayment
Arn: !GetAtt PaymentFunction.Arn
RetryPolicy:
MaximumRetryAttempts: 4
MaximumEventAgeInSeconds: 3600 # stop retrying after 1 hour
DeadLetterConfig:
Arn: !GetAtt PaymentDLQ.Arn # rejected events captured here
Cross-Account Event Routing
EventBridge supports cross-account event routing, enabling event-driven architectures that span multiple AWS accounts. This is essential for organizations using a multi-account strategy where different teams own different accounts. The mechanics are straightforward but easy to misconfigure: the source account’s rule names the destination account’s bus as a target, while the destination bus carries a resource policy granting events:PutEvents to the source. Because IAM permissions live on both sides, a silent failure here usually means the destination bus policy is missing the source account principal — check it first when cross-account events disappear.
When NOT to Reach for EventBridge
EventBridge is not the right tool for every messaging job, and choosing it reflexively leads to pain. Because it is optimized for routing flexibility rather than raw throughput, very high-volume streaming workloads — clickstreams, IoT telemetry, log pipelines — are usually a better fit for Kinesis or Kafka, which preserve ordering within a shard and sustain far higher event rates. EventBridge also does not guarantee ordering, so any workflow that depends on strict sequence (such as a state machine where step two must follow step one) belongs in Step Functions or SQS FIFO instead. Finally, when you need a simple point-to-point queue with one producer and one consumer, plain SQS is cheaper and simpler than a bus with rules. Reserve EventBridge for what it does best: routing meaningful business events to a changing set of consumers.
Schema Registry and Event Discovery
The EventBridge Schema Registry automatically discovers and catalogs event schemas. It generates code bindings in Java, Python, and TypeScript, making it easy to produce and consume events with type safety. See the EventBridge documentation for advanced patterns. For a broader look at how these patterns fit into service design, see our Modular Monolith vs Microservices guide.
Key Takeaways
- Version your event contracts and prefer fat events that consumers can process standalone
- Always check
failedEntryCounton PutEvents and attach a DLQ to every production target - Make consumers idempotent — at-least-once delivery means duplicates are normal
- Reach for Kinesis, Step Functions, or SQS when you need throughput or strict ordering
- Document event schemas and architectural decisions for future team members
In conclusion, AWS EventBridge event-driven architecture provides a powerful, serverless foundation for building decoupled cloud applications. Use custom event buses for domain isolation, pattern-based rules for intelligent routing, versioned contracts for safe evolution, and DLQs plus idempotency for reliable delivery. Event-driven architecture scales naturally and makes your system more resilient to individual component failures — as long as you respect its at-least-once, unordered delivery model and pick the right tool for each workload.