Amazon EventBridge: The Advanced Architect's Guide
A deep, production-grade walk through how Amazon EventBridge really works under the hood — internal routing, replay and archive mechanics, cross-account event mesh design, security boundaries, and the failure modes that only show up at scale.
Amazon EventBridge quietly sits behind some of the largest event-driven systems on the planet. Most engineers meet it as “a slightly fancier SNS” — you write a rule, you pick a target, events flow. That surface-level understanding is enough to build a demo. It is not enough to run a payments platform, a multi-account SaaS product, or a fleet of microservices that must never silently drop an event. This guide skips the basics entirely. We assume you already know what an event bus is and what a rule pattern looks like. Instead, we go under the hood: how EventBridge actually partitions, matches, and delivers billions of events; how replay and archive work at the storage layer; how cross-account event meshes are secured; and where experienced teams still get burned in production.
1Advanced Core Concepts
The concepts that separate a working EventBridge setup from a resilient one: schema evolution, content filtering at scale, and the distinction between Rules, Pipes, and Scheduler.
Schema Registry as a Contract, Not a Convenience
EventBridge’s Schema Registry is often treated as an afterthought — a nice-to-have that auto-generates code bindings. At an advanced level, it should be treated the way an API team treats a versioned contract. Every event type published to a bus is, in effect, a public interface to every consumer subscribed to it. When a producing team changes a field’s type, renames a key, or removes an optional attribute, every downstream rule pattern and every target’s parsing logic is at risk. The registry supports schema versioning, and mature teams enforce a rule: a schema version is never mutated in place, it is only extended. This is the same additive-only discipline used in protobuf or Avro contracts, applied to JSON events.
Think of the schema registry like a shipping manifest for cargo containers. If a warehouse quietly starts putting different contents in a container labeled “Electronics — Fragile” without updating the manifest, every downstream handler that trusts that label breaks. The manifest is only useful if everyone treats it as a promise, not a suggestion.
Content Filtering Beyond Simple Equality
Basic rule patterns match on exact values. Advanced patterns use the full content-filtering language: numeric range matching ([{"numeric": [">", 100, "<=", 500]}]-style logic conceptually, without writing code here), prefix and suffix matching, anything-but exclusion, and — critically — matching against nested JSON structures several levels deep. A single rule can combine multiple such conditions with implicit AND logic across top-level keys, letting one rule replace what used to require five separate Lambda functions each doing manual filtering. The trade-off is discoverability: a rule with eight nested conditions is powerful but nearly unreadable six months later without strong naming conventions and inline documentation stored alongside the infrastructure code.
Rules vs. Pipes vs. Scheduler — Three Different Jobs
Advanced architects stop treating these as interchangeable. A Rule reacts to events already on a bus and fans them out to targets. A Pipe is a point-to-point connector: it pulls from a source (like a Kinesis stream or SQS queue), can enrich and filter the payload mid-flight, and pushes to exactly one target, without ever touching the bus’s broadcast semantics. Scheduler generates events on a time basis — cron or rate expressions — decoupled from any upstream trigger. Using a Rule where a Pipe is the right tool (for example, connecting one queue to one Lambda) adds an unnecessary broadcast hop and observability blind spot. Using a Pipe where a Rule is needed loses the fan-out and cross-account distribution EventBridge is built for.
Broadcast Routing
One event, many possible targets, driven by pattern matching against a shared bus.
Point-to-Point ETL
Source → filter → enrich → target, with no bus, no fan-out, and lower latency.
Time-Driven Events
Cron and rate-based invocation at massive scale, replacing per-tenant CloudWatch Events rules.
The Shared Backbone
Default, custom, and partner buses each have independent quotas, policies, and archives.
Input Transformers as a Mini Data Layer
Input transformers let a rule reshape the event before it reaches a target — extracting specific JSON paths and rebuilding a new payload structure. Advanced teams use this to decouple a target’s expected shape from the producer’s native event shape, avoiding a full Lambda invocation purely for reformatting. This is a genuine architectural lever: it moves transformation logic out of compute and into configuration, which means it scales with EventBridge’s own throughput rather than a Lambda concurrency limit.
2Internal Working
What actually happens between “PutEvents was called” and “the Lambda function received a payload” — the matching engine, partitioning, and delivery guarantees.
EventBridge is built as a distributed, multi-tenant, partitioned matching engine sitting in front of a fan-out delivery layer. When an event is submitted through PutEvents, it does not go directly to targets. It is first durably accepted onto the bus, then evaluated against every active rule attached to that bus. Because a single AWS account can have hundreds of rules on one bus, and a single organization can run millions of events per minute across accounts, this matching step is deliberately built to scale horizontally: rule evaluation is partitioned so that no single rule, however complex, can create a bottleneck for the rest of the bus’s traffic.
flowchart LR
P[Producer Service] -->|PutEvents API| API[EventBridge Ingestion API]
API --> ACC[Durable Event Acceptance]
ACC --> ME[Rule Matching Engine]
ME -->|Pattern Match 1| T1[Target: Lambda Function]
ME -->|Pattern Match 2| T2[Target: SQS Queue]
ME -->|Pattern Match 3| T3[Target: Step Functions]
ME -->|No Match| DROP[Event Discarded - No Rule Fired]
T1 -->|Failure| DLQ1[Dead-Letter Queue]
T2 -->|Failure| DLQ2[Dead-Letter Queue]
Every rule match is evaluated independently, which means one event can trigger zero, one, or many targets simultaneously. This is fundamentally different from a queue, where one message typically goes to one consumer group. Internally, delivery to each target is treated as its own retryable unit of work — a failure delivering to Target A has no effect on delivery to Target B for the same event. This isolation is what allows EventBridge to promise per-target retry policies and per-target dead-letter queues rather than one blunt setting for the whole rule.
At-Least-Once Delivery, Not Exactly-Once
EventBridge guarantees at-least-once delivery to each matched target. Under normal conditions duplicates are rare, but they are not impossible — network retries, internal service retries during a partial failure, and target-side timeout ambiguity can all produce a second delivery of the same event. Advanced consumers must be idempotent: every target’s processing logic should be safe to run twice on the same event ID without producing a duplicated side effect, typically by tracking processed event IDs in a fast key-value store with a short TTL.
Many teams assume “each event fires each matching rule exactly once” also means “each rule delivers exactly once to its target.” These are two separate guarantees. The first is generally true; the second is explicitly at-least-once. Idempotency belongs in the consumer, never assumed away.
No Ordering Guarantee Across Targets
Because each target delivery is an independent asynchronous operation, EventBridge makes no promise that two events emitted in order will arrive at a given target in that same order, nor that two different targets receive an event at the same moment. Systems that require strict ordering (for example, a state machine that must process “order created” before “order shipped”) cannot rely on EventBridge alone for sequencing — they need a sequencing key inside the payload and ordering logic in the consumer, or they route through a FIFO-capable component like an SQS FIFO queue as the actual target.
3Data Flow & Lifecycle
Following one event from birth to archival, including the paths most tutorials skip: replay, archive expiry, and dead-letter routing.
An event’s life on EventBridge has more stages than “sent, matched, delivered.” A full advanced lifecycle looks like this: a producer emits the event; the bus durably accepts it; if an archive is configured for that bus, the event is asynchronously copied into the archive with an event-pattern filter of its own (an archive can store all events or only a filtered subset); rule matching runs against the live event; each matched target attempt is made with the target’s configured retry policy; on exhausted retries, the event is written to that target’s dead-letter queue if one is configured, or silently dropped if not.
Ingestion & Durability
PutEvents accepts the event and durably persists it before any matching begins, decoupling producer latency from downstream target health.
Optional Archive Write
If archiving is enabled on the bus, a filtered or unfiltered copy is written to the archive independently of rule matching succeeding or failing.
Rule Evaluation
Every enabled rule on the bus is checked against the event pattern in parallel; zero, one, or many rules may match.
Per-Target Delivery Attempt
Each matched target is invoked independently, with its own retry policy (up to 185 retries or 24 hours, whichever is configured lower) and maximum event age.
Dead-Letter or Drop
On exhausted retries, an event goes to the target’s DLQ if one exists; otherwise it is lost with only a CloudWatch metric marking the failure.
Replay: Rewinding Time Without Re-Sending
Replay lets you take a time range from an archive and re-inject those events back onto the bus as if they were happening now, re-running them through current rules. This is invaluable after fixing a broken Lambda function or a misconfigured filter — instead of asking every producer to resend, you replay history from the archive. The subtlety advanced teams must plan for: replayed events re-trigger every currently active rule, not just the one you intended to fix. A rule that was working perfectly before your incident can be flooded with replayed traffic it was never sized for, so replay windows should be scoped narrowly and, where possible, replayed onto a dedicated bus rather than production.
Production Example — Expedia’s Event Rehydration
Large travel platforms with dozens of pricing and inventory microservices use archive-and-replay as a recovery mechanism: when a downstream pricing consumer has a bug deployed, the fix is followed by a scoped replay of the affected hours from the archive, rather than asking every hotel-partner integration to resend inventory updates.
Archive Retention Is Not “Forever by Default”
An archive can be created with unlimited retention or a fixed number of days. Advanced cost management means matching retention to actual replay need, not defaulting to “keep everything forever.” A payments-adjacent bus might need 90 days for audit and dispute windows; a high-volume telemetry bus replayed only for same-day debugging might need 3 days. Archive storage is billed, and an unfiltered archive on a high-throughput bus can silently become one of the largest line items in an event-driven system’s AWS bill.
4Advantages, Disadvantages & Trade-offs
Where EventBridge genuinely wins over alternatives like SNS, Kafka, or direct service calls — and where it structurally cannot.
Advantages
- Native schema-aware routing without running any brokers or clusters
- Cross-account and cross-region event mesh support built into the primitive itself
- Fine-grained content filtering reduces target-side compute otherwise spent discarding irrelevant events
- Native SaaS partner event sources (Datadog, Zendesk, Stripe-class integrations) with zero custom polling code
- Archive and replay eliminate the need for a custom event-sourcing log for many use cases
Disadvantages & Limits
- No native strict ordering guarantee — must be engineered around, not assumed
- At-least-once delivery forces idempotency onto every consumer, adding design overhead
- Event size is capped (256 KB per event), forcing a claim-check pattern for large payloads
- No built-in consumer group / offset model like Kafka — you cannot “rewind and replay from offset 500” per consumer, only bus-level archive replay
- Cost scales per-event and per-target-invocation, which can surprise teams migrating from a flat-rate Kafka cluster
EventBridge vs. Kafka — a Trade-off, Not a Replacement
Kafka gives you a durable, ordered, replayable log with per-partition offsets and long-term retention as a first-class citizen — ideal for event sourcing and stream processing pipelines that need to replay from an exact point per consumer. EventBridge gives you managed, serverless, pattern-based routing with none of that operational burden, but a shallower replay model and no per-consumer offset tracking. Advanced architectures often run both: Kafka (or Kinesis) as the durable backbone for high-volume, order-sensitive streams, with EventBridge as the routing and integration layer that fans significant business events out to dozens of loosely coupled consumers, SaaS tools, and cross-account subscribers.
5Performance & Scalability
How EventBridge scales horizontally, where its quotas actually bind, and how to design around them before they bind you.
EventBridge scales by partitioning both ingestion and rule matching across the service’s internal fleet — you never provision throughput, shards, or brokers. That said, “serverless” does not mean “unlimited,” and advanced teams treat published quotas as design inputs, not fine print. Two quotas matter most in practice: the PutEvents throughput per account per region (which can be raised via a service quota increase request) and the per-target invocation rate, which can be throttled independently of how fast the bus itself accepts events.
SINGLE EVENT
PER EVENT BUS
BEFORE DLQ/DROP
The Claim-Check Pattern for Oversized Payloads
Because a single event is capped, systems that need to move large objects — a full order document, a media file, a large ML feature vector — never put the object itself on the bus. Instead they write the object to S3, and the event carries only a reference (bucket, key, and a content hash for integrity checking). Consumers dereference the S3 object only when they actually need the full payload. This keeps EventBridge doing what it is fast at — routing small, structured signals — while storage-heavy work stays in a storage-optimized service.
The claim-check pattern is exactly like checking a coat at a theater: you don’t carry the coat around with you all evening, you carry a small numbered ticket. Anyone who needs the coat back uses the ticket to retrieve it from the one place it’s actually stored.
Throttling Is Per-Target, Not Just Per-Bus
A rule that fans out to five targets can succeed against four of them and be throttled against the fifth if that target (say, a Lambda function near its reserved concurrency limit) cannot keep up. EventBridge’s retry behavior handles this gracefully with exponential backoff up to the target’s configured retry policy, but if the underlying target’s scaling ceiling is the true bottleneck, no amount of EventBridge-side tuning fixes it — the fix is raising the target’s own concurrency or throughput ceiling, or inserting a buffering queue in front of it.
6High Availability & Reliability
Designing for a bus, a region, or a target failing — and the difference between EventBridge being available and your event actually being processed.
EventBridge itself is a regional, highly available managed service — AWS operates the matching and delivery infrastructure across multiple Availability Zones, so a single AZ failure does not take the bus down. High availability at the application layer, however, is a separate concern from service availability, and it is where most production incidents actually originate: a healthy bus faithfully delivering events to an unhealthy or overwhelmed target still results in data loss if no dead-letter queue is configured.
Every target that matters should have a dead-letter queue attached at the rule-target level, plus a CloudWatch alarm on that DLQ’s message count. A DLQ nobody watches is functionally the same as no DLQ at all — it just fails quietly instead of loudly.
Multi-Region Failover Patterns
EventBridge does not natively replicate events across regions. Multi-region resilience is built by the architect: a common pattern uses a global endpoint, backed by two event buses in different regions with cross-region replication of the underlying stream, and Route 53 health checks that redirect PutEvents traffic to the secondary region if the primary’s health check fails. This shifts the failover decision to health-check latency rather than instantaneous, which is an accepted trade-off for the simplicity gained versus running a custom multi-region event log.
flowchart TB
PROD[Producer] --> GEP[EventBridge Global Endpoint]
GEP -->|Healthy| PRIMARY[Primary Region Bus - us-east-1]
GEP -.->|Failover on Health Check| SECONDARY[Secondary Region Bus - us-west-2]
PRIMARY --> REPL[Managed Replication]
REPL --> SECONDARY
PRIMARY --> T1[Targets in us-east-1]
SECONDARY --> T2[Targets in us-west-2]
Retry Policy Tuning Is a Reliability Lever
Each rule target’s retry policy — maximum retry attempts and maximum event age — directly controls how long EventBridge will keep trying a failing target before giving up. Setting maximum event age too low on a target that has occasional 10-minute outages (say, during a downstream deployment) causes events to reach the DLQ unnecessarily; setting it too high on a latency-sensitive target lets stale events pile up and get processed long after they’re still relevant. Advanced tuning treats this policy as business-specific, not a value copied from documentation defaults.
7Security
Resource policies, cross-account trust boundaries, encryption, and the security model of a service that, by design, moves data between accounts.
Resource-Based Policies Are the Real Trust Boundary
Cross-account event delivery is governed by a resource-based policy on the receiving event bus, not by IAM policy on the sender alone. The receiving account explicitly grants specific sender accounts (or an AWS Organization) permission to put events onto its bus. Advanced security reviews focus heavily here: a resource policy scoped to "Principal": "*" with only a condition on organization ID is very different in blast radius from one scoped to three explicit account IDs, and audits should treat overly broad bus policies as a first-class finding, the same way an overly permissive S3 bucket policy would be treated.
Context
A platform team wants any account in the company’s AWS Organization to publish events to a shared “domain events” bus with minimal setup friction.
Anti-Pattern
Granting events:PutEvents to Principal: "*" with no organization or account condition at all, intending to add conditions “later” once things are working.
Why It Fails
This makes the bus a public write target if the policy is ever accidentally exposed or misapplied to a bus that is not meant to be shared, and it removes any audit trail distinguishing legitimate internal accounts from anything else that discovers the ARN.
Encryption: In Transit by Default, At Rest by Configuration
All EventBridge API traffic is encrypted in transit via TLS. At-rest encryption of event data — including archived events — uses AWS-owned keys by default, but advanced, compliance-driven deployments configure customer-managed KMS keys so the organization controls key rotation and can revoke access independently of AWS. This matters most for archives holding sensitive business events with long retention windows, where the archive effectively becomes a long-lived, queryable-by-replay data store, not just transient message traffic.
VPC-Originated Events and PrivateLink
Services running inside a VPC that need to call PutEvents without traversing the public internet use an interface VPC endpoint (powered by AWS PrivateLink) for EventBridge. This keeps event traffic on the AWS private network backbone, which matters for workloads under network-isolation compliance requirements (PCI-DSS scoped VPCs, for example) where any path to a public endpoint, even one secured by IAM, is itself a finding during an audit.
| Security Control | Protects Against | Where It’s Configured |
|---|---|---|
| Resource-based bus policy | Unauthorized cross-account PutEvents | Event bus level |
| IAM policy on producer role | Unauthorized calls from within your own account | IAM |
| KMS customer-managed key | Unrevocable/unaudited data-at-rest access | Archive / bus encryption config |
| VPC interface endpoint | Public internet exposure of event traffic | VPC networking |
8Monitoring, Logging & Metrics
The signals that actually tell you an event-driven system is healthy — most of which are not visible from the bus itself.
The most dangerous property of an event-driven architecture is that a silently dropped event produces no error anywhere obvious — the producer got a success response from PutEvents, and the consumer simply never ran. Advanced monitoring compensates for this structurally, not just by watching dashboards.
Per-Rule Target Metric
Tracks failed delivery attempts to a specific target; a sustained rise here means a downstream target is unhealthy, not the bus.
Bus-Level Metric
Signals the bus itself is hitting an account-level throughput ceiling — the trigger to request a quota increase.
DLQ Delivery Metric
Confirms events are actually landing in the DLQ rather than being silently discarded when no DLQ exists.
Distributed Tracing
Carrying an AWS X-Ray or OpenTelemetry trace ID inside the event payload so a single business transaction can be followed across every hop.
Reconciliation as a Detection Strategy
Because there’s no built-in “did every expected consumer process this?” signal, mature teams add a reconciliation job: producers log an expected count of events per time window, consumers log a processed count, and a scheduled job compares the two. A gap between them — even a small one — is the earliest possible signal that something is silently failing, well before a customer notices missing data.
9Deployment & Cloud
Managing buses, rules, and archives as versioned infrastructure across dozens of accounts without configuration drift.
At scale, EventBridge resources are never hand-clicked into the console — buses, rules, targets, archives, and schema registries are defined as infrastructure-as-code (CloudFormation, CDK, or Terraform) and deployed through the same pipeline as application code. The advanced concern is not “how do I create a rule” but “how do I prevent two teams from independently creating overlapping, conflicting rules on a shared bus.”
The Central Bus, Federated Rules Pattern
Large organizations typically run a small number of centrally-owned event buses (often one per domain, like “orders,” “payments,” “inventory”) while allowing individual product teams to own the rules and targets that subscribe to those buses within their own accounts. The bus itself, and its resource policy, is managed by a platform team; the consuming rules are managed by whoever needs the data. This mirrors how a well-run API gateway separates “who owns the endpoint” from “who owns the consumers.”
Production Example — Zendesk-Style Event Mesh
SaaS platforms exposing partner event sources publish domain events to a partner bus per customer; each customer account subscribes their own rules and targets against that dedicated bus, giving per-tenant isolation without the platform team needing to provision per-tenant compute.
Blue/Green Rule Changes
Changing a live rule’s event pattern is risky if done in place — a typo can instantly stop matching production traffic with no deployment rollback to fall back on beyond redeploying the old pattern. Advanced teams deploy a new rule alongside the old one, dual-run both against production traffic, verify the new rule’s matched volume looks correct via CloudWatch metrics, and only then disable the old rule — the same blue/green discipline used for compute deployments, applied to routing logic instead of code.
10Design Patterns & Anti-patterns
Patterns proven in production, and the anti-patterns that look reasonable until they fail under real load.
Pattern: The Event-Carried State Transfer
Instead of an event saying only “order 123 changed” and forcing every consumer to call back to the orders service to find out what changed, the event carries the full relevant state itself. This removes a synchronous dependency between consumer and producer at read time, at the cost of larger event payloads and the need for careful schema versioning discussed in Chapter 1.
Pattern: Fan-Out with Independent Failure Domains
A single business event (say, “payment captured”) fans out via one rule to five completely independent targets — billing, analytics, fraud detection, customer notification, and a partner webhook — each with its own DLQ and retry policy. A failure in the notification target has zero effect on billing processing succeeding. This is EventBridge’s single strongest architectural advantage over a synchronous orchestration call chain.
Context
A team wants a single Lambda function to handle five different downstream responsibilities for one event type, to “keep it simple.”
Anti-Pattern
One monolithic target Lambda that internally branches into billing logic, analytics logic, and notification logic based on event content, all in one function with one shared retry policy.
Why It Fails
A bug or throttle in the notification branch now retries the entire function, including already-succeeded billing logic, risking duplicate billing side effects unless every branch is independently idempotent — and one slow branch (like a third-party webhook) drags down the retry timing for all the others.
Pattern: The Saga Coordinator via Step Functions
When a business process genuinely needs ordering and compensation logic across services (an order-to-fulfillment saga, for example), EventBridge routes the triggering event to a Step Functions state machine, which owns the sequencing, retries, and rollback logic explicitly — rather than trying to force ordering guarantees onto the bus itself, which was never designed to provide them.
Anti-Pattern: Using EventBridge as a Request-Response Channel
EventBridge is fire-and-forget by design; there is no built-in mechanism for a producer to receive a response to an event it emitted. Teams that try to simulate request-response by emitting an event and then polling a database for a result field are fighting the tool’s fundamental model. If a true synchronous response is needed, a direct API call (or a callback pattern using Step Functions’ waitForTaskToken) is the correct tool, not the event bus.
11Best Practices & Common Mistakes
The habits that separate teams who run EventBridge for years without incident from teams who get paged at 3 a.m.
Version Your Event Schema in the Detail-Type
Encode a version like OrderCreated.v2 in the event’s detail-type so old and new consumers can coexist during migration without guessing which shape they received.
Always Attach a DLQ, Even If You Think You Don’t Need One
A target that has “never failed” is one deployment away from its first failure — DLQs are near-zero cost insurance against silent event loss.
Treating Rule Patterns as Untested Code
A malformed or overly broad pattern either matches nothing (silent data loss) or matches everything (unexpected fan-out); patterns deserve unit tests just like application logic.
Ignoring Maximum Event Age Defaults
Leaving the default 24-hour maximum event age on a latency-sensitive target means a long outage silently produces a burst of very stale, low-value retries once the target recovers.
Naming Conventions Prevent Cross-Team Chaos
On a shared bus with dozens of producing teams, a consistent naming convention for the source field (like com.company.domain.service) and detail-type field is what makes rule patterns writable and auditable at all. Without it, every consuming team ends up writing overly broad patterns just to be safe, defeating the entire purpose of content-based filtering.
12Real-World & Industry Examples
How well-known companies apply these advanced patterns in production, not in a tutorial.
Netflix — Cross-Account Operational Events
Large streaming platforms with hundreds of independently deployed microservice accounts use event buses as the backbone for operational signals — deployment events, health-check state changes, capacity alerts — routed via cross-account rules so a central observability account can react without each individual service account needing direct access into every other account.
Capital One — Event-Driven Fraud Signal Fan-Out
Financial services platforms fan a single “transaction authorized” event out simultaneously to fraud scoring, rewards calculation, and customer notification systems, each with independently tuned retry and DLQ policies, so a slow fraud model never delays a customer’s purchase confirmation.
Yelp — SaaS Partner Event Integration
Companies integrating with SaaS observability and support tools receive partner events (from providers with native EventBridge partner event sources) directly onto a dedicated partner bus, avoiding custom webhook receivers and their associated authentication and retry-handling code entirely.
The Common Thread
Across every advanced production use case, the pattern is the same: EventBridge is chosen specifically for its decoupling and fan-out properties, not for raw throughput records or strict ordering — those needs get routed to Kinesis, Kafka, or SQS FIFO instead, often working alongside EventBridge rather than replacing it.
13Frequently Asked Questions
14Summary & Key Takeaways
What to Carry Forward
- At-least-once, not exactly-once: every consumer must be idempotent — EventBridge will not do this for you.
- Rules, Pipes, and Scheduler are different tools: pick based on broadcast vs. point-to-point vs. time-driven needs, not habit.
- Resource-based bus policies are the real security boundary for cross-account event flow — audit them like you would an S3 bucket policy.
- Replay re-runs current rules, not just the broken one — scope replay windows deliberately and consider an isolated bus first.
- Every meaningful target needs a monitored dead-letter queue; a DLQ nobody watches is the same as no DLQ.
- Ordering must be engineered, not assumed — reach for SQS FIFO or Step Functions when sequencing genuinely matters.
- Unfiltered, unretained archives are the most common hidden cost in production EventBridge deployments — set retention deliberately.