EventBridge is quietly eating your AWS bill, and it’s not because of the events themselves, but the sheer volume of events being delivered to targets that don’t actually need them.
Let’s watch some events flow. Imagine we have a UserSignedUp event published by our application.
{
"version": "0",
"id": "a1b2c3d4-e5f6-7890-1234-567890abcdef",
"detail-type": "UserSignup",
"source": "com.mycompany.auth",
"account": "123456789012",
"time": "2023-10-27T10:00:00Z",
"region": "us-east-1",
"resources": [],
"detail": {
"userId": "user-123",
"email": "user@example.com",
"signupTimestamp": "2023-10-27T09:59:59Z"
}
}
Now, let’s say we have three targets interested in this event:
WelcomeEmailSender: Needs to send a welcome email.AnalyticsIngestor: Needs to log user signup for marketing analysis.UserDatabaseUpdater: Needs to populate a user profile table.
Without filtering, EventBridge delivers this single UserSignup event to all three targets. This means three invocations, three potential charges. If UserSignedUp events are frequent (e.g., thousands per hour), those charges add up fast, even if the content of the event is only relevant to one or two targets.
The problem EventBridge filtering solves is precisely this: unnecessary target invocations driven by event volume, not event relevance. By defining rules that match specific event patterns before delivery, you ensure that targets only receive events they are designed to process. This directly translates to fewer API calls, fewer Lambda invocations, fewer SQS messages, and ultimately, a lower AWS bill.
Here’s how we’d set up a rule for the WelcomeEmailSender.
First, we need to create an EventBridge rule. Let’s call it UserSignupWelcomeEmailRule.
aws events put-rule \
--name UserSignupWelcomeEmailRule \
--event-bus-name default \
--description "Rule to send welcome emails for new user signups" \
--state ENABLED \
--event-pattern '{"source": ["com.mycompany.auth"], "detail-type": ["UserSignup"]}'
This rule, as is, would still send all UserSignup events from com.mycompany.auth to its targets. To make it cost-effective, we add an event pattern that specifies which signups we care about for the welcome email. Let’s say we only want to send welcome emails to users in the US region, and only if they signed up after a certain time (this part is illustrative; usually, you’d filter on detail fields).
aws events put-rule \
--name UserSignupWelcomeEmailRule \
--event-bus-name default \
--description "Rule to send welcome emails for new user signups in US" \
--state ENABLED \
--event-pattern '{"source": ["com.mycompany.auth"], "detail-type": ["UserSignup"], "region": ["us-east-1", "us-west-1", "us-west-2"]}'
Now, let’s say our AnalyticsIngestor only cares about signups that happen during business hours (9 AM to 5 PM UTC). We create another rule, UserSignupAnalyticsRule.
aws events put-rule \
--name UserSignupAnalyticsRule \
--event-bus-name default \
--description "Rule to log user signups for analytics during business hours" \
--state ENABLED \
--event-pattern '{"source": ["com.mycompany.auth"], "detail-type": ["UserSignup"]}'
And then, we’d refine this rule with a filter pattern that inspects the detail field of the event. Suppose the detail object contains a signupTimestamp field. We can filter based on its value. This requires a slightly different approach when defining the rule, as the filter pattern is associated with the target of the rule, not the rule itself when using the put-rule API directly. It’s more common to use the console or put-targets with a FilterPattern argument.
Here’s how you’d define the filter pattern for the AnalyticsIngestor target using the AWS CLI when adding the target:
aws events put-targets \
--rule UserSignupAnalyticsRule \
--event-bus-name default \
--targets \
'[
{
"Id": "AnalyticsIngestorTarget",
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:AnalyticsIngestor",
"RoleArn": "arn:aws:iam::123456789012:role/EventBridgeInvokeRole",
"InputTransformer": {
"InputPathsMap": {
"signupTime": "$.time"
},
"InputTemplate": "{\"eventTime\": \"<signupTime>\", \"eventType\": \"UserSignup\"}"
},
"FilterPattern": "{\"detail\": {\"signupTimestamp\": [\">=2023-10-27T09:00:00Z\", \"<2023-10-27T17:00:00Z\"]}}"
}
]'
This FilterPattern tells EventBridge to only deliver the event to the AnalyticsIngestor target if the signupTimestamp within the detail field falls between 09:00:00Z and 17:00:00Z on that specific date. Note that filter patterns use a JSON-like syntax that’s a subset of the event pattern syntax, allowing for comparisons like greater than or equal to (>=) and less than (<).
The UserDatabaseUpdater might need every single signup event, regardless of time or region. For this target, we’d create a rule with a broad event pattern (or no specific event pattern if the event bus is dedicated) and attach the UserDatabaseUpdater as a target without any specific filter pattern.
aws events put-rule \
--name UserSignupDatabaseUpdateRule \
--event-bus-name default \
--description "Rule to update user database for all new user signups" \
--state ENABLED \
--event-pattern '{"source": ["com.mycompany.auth"], "detail-type": ["UserSignup"]}'
aws events put-targets \
--rule UserSignupDatabaseUpdateRule \
--event-bus-name default \
--targets \
'[
{
"Id": "UserDatabaseUpdaterTarget",
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:UserDatabaseUpdater",
"RoleArn": "arn:aws:iam::123456789012:role/EventBridgeInvokeRole"
}
]'
The key insight here is that EventBridge performs filtering before invoking targets. This means the cost savings are realized because EventBridge itself doesn’t incur charges for delivering events that are ultimately discarded by the filter. You pay for the EventBridge rule and the API calls to put-targets, but you avoid paying for subsequent target invocations for events that don’t match your criteria.
The most surprising thing about EventBridge filtering is that its filtering capabilities are not just about matching exact strings or numbers; they extend to range comparisons, array matching, and even simple logical operators when defining filter patterns for targets. This level of granular control allows you to drastically prune event delivery without needing to implement complex filtering logic within your Lambda functions or other targets.
The next hurdle you’ll face is managing increasingly complex filter patterns and ensuring they accurately reflect your business logic without introducing subtle bugs that cause legitimate events to be dropped.