EventBridge event buses are a powerful tool for decoupling services, but the cost can escalate quickly if you’re not careful about what events are being processed. EventBridge charges per event published to an event bus, and then again per event delivered to a target. If your event producers are generating a lot of noise that your consumers don’t actually need, you’re paying for traffic you’re throwing away.
Let’s look at a common scenario. Imagine a system that emits OrderCreated events. Many services might be interested in this event, but some only care about orders from specific regions, or orders above a certain value. Without filtering, every OrderCreated event, regardless of its content, is published, routed, and potentially delivered to every interested target.
Here’s a sample OrderCreated event payload:
{
"version": "0",
"id": "a1b2c3d4-e5f6-7890-1234-abcdef123456",
"detail-type": "OrderCreated",
"source": "com.mycompany.orders",
"account": "123456789012",
"time": "2023-10-27T10:00:00Z",
"region": "us-east-1",
"resources": [],
"detail": {
"orderId": "ORD-987654",
"customerId": "CUST-112233",
"orderDate": "2023-10-27T10:00:00Z",
"totalAmount": 150.75,
"currency": "USD",
"shippingAddress": {
"street": "123 Main St",
"city": "Anytown",
"state": "CA",
"zip": "90210",
"country": "USA"
},
"items": [
{"sku": "SKU001", "quantity": 2, "price": 50.00},
{"sku": "SKU002", "quantity": 1, "price": 50.75}
]
}
}
Now, let’s say the eu-orders service only cares about orders shipped to Europe. Without filtering, this event, originating from us-east-1, would still be published and routed. If eu-orders was the only consumer interested in this specific event, you’d be paying for EventBridge to process and deliver an event that the consumer immediately discards.
This is where EventBridge’s content-based filtering shines. You can define rules on your event bus that inspect the event payload and only route events matching specific criteria to particular targets. This significantly reduces the number of events processed by downstream services and, crucially, the number of events EventBridge has to deliver, leading to direct cost savings.
Consider the eu-orders service again. Instead of having a rule that matches all OrderCreated events and then discarding them in the consumer, you can add a filter to the EventBridge rule itself.
Here’s how you’d set up a rule with content-based filtering using the AWS CLI:
First, create a new rule that matches OrderCreated events from your company’s order service:
aws events put-rule \
--name "EUOrderCreatedRule" \
--event-bus-name "default" \
--event-pattern "{\"detail-type\":[\"OrderCreated\"],\"source\":[\"com.mycompany.orders\"]}" \
--state ENABLED
Now, add a target to this rule, but with a specific filter pattern that only includes orders where the shipping country is "USA" (for demonstration, let’s assume we want US orders for a USOrderProcessing service):
aws events put-targets \
--rule "USOrderCreatedRule" \
--event-bus-name "default" \
--targets '[
{
"Id": "USOrderProcessingTarget",
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:USOrderProcessingFunction",
"InputTransformer": {
"InputPathsMap": {
"orderId": "$.detail.orderId",
"totalAmount": "$.detail.totalAmount"
},
"InputTemplate": "{\"order_id\": \"<orderId>\", \"amount\": \"<totalAmount>\"}"
}
}
]'
Crucially, you then add the filter to that specific target using the --filter-pattern argument when putting the target. This is the key to cost optimization: the filtering happens before EventBridge even attempts to invoke the target.
aws events put-targets \
--rule "USOrderCreatedRule" \
--event-bus-name "default" \
--targets '[
{
"Id": "USOrderProcessingTarget",
"Arn": "arn:aws:lambda:us-east-1:123456789012:function:USOrderProcessingFunction",
"InputTransformer": {
"InputPathsMap": {
"orderId": "$.detail.orderId",
"totalAmount": "$.detail.totalAmount"
},
"InputTemplate": "{\"order_id\": \"<orderId>\", \"amount\": \"<totalAmount>\"}"
},
"FilterPattern": "{\"detail\":{\"shippingAddress\":{\"country\":[\"USA\"]}}}"
}
]'
In this example, the USOrderCreatedRule is set up to catch all OrderCreated events from com.mycompany.orders. However, the USOrderProcessingTarget attached to this rule only receives events where $.detail.shippingAddress.country is exactly "USA". Any OrderCreated event with a different shipping country will be published to the bus, routed by the rule, but not delivered to the USOrderProcessingFunction. This saves you the cost of EventBridge delivering that event and the Lambda function invocation cost.
You can chain these filters. Suppose you also have a HighValueOrderRule that needs to process orders over $1000. You’d create a rule for OrderCreated events and attach a target with a filter pattern like this:
{
"detail": {
"totalAmount": [
{
"numeric": ">",
"value": 1000
}
]
}
}
This filter uses a numeric comparison to ensure only high-value orders trigger the target. The ability to combine string matching, numeric comparisons, and boolean logic within these filter patterns offers granular control over event routing.
The most surprising thing about content-based filtering is how it fundamentally changes the economics of event-driven architectures. Instead of paying for all events to be potentially processed, you pay only for events that are actually routed to a target based on their content. This means that even if your producer is noisy, your EventBridge costs can remain low if your consumers are selective.
When you add a filter pattern to a rule, EventBridge evaluates it before sending the event to the target. If the event does not match the filter, it is not delivered. This is a critical distinction from filtering within the target itself (e.g., in a Lambda function), which would still incur EventBridge delivery costs and target invocation costs.
The next challenge is managing complex event patterns and ensuring your filters accurately capture all necessary events without inadvertently dropping critical ones.