The situation
A media company runs two teams touching S3 events.
- The media team owns
uploads-prod, a single S3 bucket. On every uploaded image, a thumbnail Lambda should run within seconds. Volume is modest: a few thousand uploads a day. One bucket, one consumer, one event type. - The compliance team owns fourteen S3 buckets across three AWS accounts. They need to observe every object-level event, creates, deletes, replication completions, lifecycle transitions, restore-from-Glacier completions, route them into a central data lake for audit, and run Lambda-based alerting on specific patterns. Many buckets, many consumers, many event types, cross-account.
Both teams have found the S3 Events settings page. Both teams have also found the EventBridge option. The question is: which mechanism fits which job, and how to decide without relying on which page of the console the engineer happened to open first?
What actually matters
Before reaching for a check-box, it’s worth asking what we’re actually trading.
S3 has emitted events for over a decade. The original mechanism, S3 Event Notifications, sits in the bucket’s own notification configuration: for a matching prefix and suffix and event type, send the event to one of three destinations. SNS topic, SQS queue, or Lambda function. Simple, direct, AWS’s first answer.
In 2021, S3 gained a second mechanism: Amazon EventBridge integration. Tick one box on the bucket (“Send notifications to Amazon EventBridge”), and every object-level event the bucket emits flows into the account’s default EventBridge bus, where rules can route by content-based patterns to the EventBridge target catalogue.
The two mechanisms cover overlapping but not identical ground, and the differences are worth listing.
The first thing to ask is: how many consumers care about the event? S3 Event Notifications allow one destination per event-filter combination, the bucket notification config is a list of {filter, destination} pairs, and two destinations for the same filter means two config entries that both fire. EventBridge treats one event as fannable out to as many rules/targets as match, with no per-bucket config change.
The second thing to ask is: what event types are needed? S3 Event Notifications cover a curated list: s3:ObjectCreated:* (and specific variants Put, Post, Copy, CompleteMultipartUpload), s3:ObjectRemoved:*, s3:ObjectRestore:*, s3:Replication:*, s3:LifecycleExpiration:*, s3:LifecycleTransition:*, and a few more. EventBridge, once enabled, receives a broader set of events, including Object Access Tier Changed, Object Storage Class Changed, and a richer set of replication events that don’t appear in the legacy notification list.
The third is filtering. S3 Event Notifications filter by prefix and suffix only – images/ + .jpg, for example. EventBridge rules filter by content of the full event JSON, size above a threshold, bucket name matching a pattern, object key matching a prefix, ETag present or absent. If the filter is richer than “this folder with this extension,” EventBridge is cleaner.
The fourth is cross-account. S3 Event Notifications can target SNS, SQS, or Lambda, and those destinations can live in other accounts via resource policies. EventBridge buses can directly receive events from S3 buckets in other accounts (via bus policies and target rules in the receiving account), which usually reads cleaner in a multi-account landing zone.
The fifth is target variety. S3 Event Notifications can target SNS, SQS, or Lambda, three options. EventBridge can target Lambda, Step Functions, SQS, SNS, Kinesis, ECS tasks, API destinations (arbitrary HTTPS), Firehose, and dozens more.
And finally, a softer one: cost shape. S3 Event Notifications are free for the notification itself (the destinations cost what they cost). EventBridge charges per event published and per custom-event delivery. For very high event volumes the cost difference is real; for normal application volumes it’s rounding.
Side by side
| Attribute | S3 Event Notifications | EventBridge S3 events |
|---|---|---|
| Destinations | SNS, SQS, Lambda | Full EventBridge target catalogue |
| Events covered | Curated list | Broader, including newer object-lifecycle events |
| Filtering | Prefix + suffix only | Content-based on full event JSON |
| Cross-account | Via destination resource policies | Via EventBridge bus policies |
| Multiple consumers per event | One destination per filter entry | Unlimited rules per event |
| Ordering guarantees | Best-effort (Lambda async retries, SNS fan-out) | Best-effort |
| Cost | Destinations only | Per event + target delivery |
| Configuration surface | Per bucket, notification config | Bucket flag + EventBridge rules |
Reading the table by use case rather than by mechanism:
- Media team, one bucket, one consumer, one event type, no cross-account, prefix-filterable. S3 Event Notifications direct to the Lambda. Three lines of bucket config; no EventBridge involved. Simpler, cheaper, equally reliable.
- Compliance team, fourteen buckets, many consumers, broad event coverage, cross-account, needs rich filtering. Enable EventBridge integration on every bucket; route in the central account via rules. The single config flag on each bucket replaces fourteen notification-config-per-bucket entries.
Mapping shape to mechanism
The picks in depth
Media team → S3 Event Notifications direct to Lambda. The config lives in the bucket’s notification configuration. Deploy via CloudFormation:
UploadsBucket:
Type: AWS::S3::Bucket
Properties:
BucketName: uploads-prod
NotificationConfiguration:
LambdaConfigurations:
- Event: s3:ObjectCreated:Put
Filter:
S3Key:
Rules:
- Name: prefix
Value: images/
- Name: suffix
Value: .jpg
Function: !GetAtt ThumbnailLambda.Arn
The Lambda’s resource policy grants s3.amazonaws.com permission to invoke it, scoped by SourceArn: arn:aws:s3:::uploads-prod. Failure handling uses Lambda’s async destinations: the function’s OnFailure destination is an SQS queue that receives failed invocations after exhaustion. No EventBridge rules, no bus, no extra billing events per upload.
One pitfall worth naming: S3 Event Notifications to Lambda are asynchronous. The notification is queued internally and Lambda retries on failure with exponential back-off for up to six hours. The DLQ on the function catches truly-failed invocations; CloudWatch AsyncEventErrors metrics show the rate.
Compliance team → EventBridge on all buckets. On each bucket, NotificationConfiguration: { EventBridgeConfiguration: {} }. That single configuration turns on every object-level event flowing into the account’s default EventBridge bus. In the central compliance account, a custom bus compliance-central receives events from each member account via a bus policy:
{
"Sid": "AllowComplianceMembers",
"Effect": "Allow",
"Principal": { "AWS": ["arn:aws:iam::111111111111:root", "arn:aws:iam::222222222222:root"] },
"Action": "events:PutEvents",
"Resource": "arn:aws:events:eu-west-1:999999999999:event-bus/compliance-central"
}
Each member account has a rule on its default bus matching source: ["aws.s3"] with a target of the central bus. The central bus then has the rules that actually do work: Firehose target for long-term archive, a Lambda target for real-time alerting, a Step Function target for the large-object review flow.
Rule patterns are content-based. A rule matching s3:ObjectRemoved:* events looks like:
{
"source": ["aws.s3"],
"detail-type": ["Object Deleted"]
}
A rule matching large uploads:
{
"source": ["aws.s3"],
"detail-type": ["Object Created"],
"detail": { "object": { "size": [{ "numeric": [">", 1073741824] }] } }
}
No per-bucket configuration beyond the flag; all the logic is in rules and lives in the compliance account.
When both are on the same bucket
A subtle behaviour worth naming: both mechanisms can be on at once. S3 sends the event through both channels, direct destinations and EventBridge, and they don’t deduplicate. A thumbnail Lambda subscribed via notification config plus a rule on the default bus targeting the same Lambda means two invocations per upload. The Lambda has to be idempotent, or one of the mechanisms has to be turned off.
The usual rule: pick one per bucket. Use direct notifications when the bucket has a small, known set of consumers; use EventBridge when the consumer set is larger or when filtering needs go beyond prefix/suffix.
A worked flow comparison
An upload of images/cat.jpg (2 MB) to uploads-prod:
With direct notifications:
t=0ms PUT object to S3
t=20ms S3 stores object
t=30ms S3 emits notification to configured Lambda ARN
t=50ms Lambda async invocation arrives at queue
t=100ms Lambda runtime picks up, executes handler
t=700ms Thumbnail written back; success
One path, no fan-out, no bus.
With EventBridge:
t=0ms PUT object to S3
t=20ms S3 stores object
t=30ms S3 publishes event to the account's default bus
t=40ms Rule matches (source: aws.s3, detail-type: Object Created)
t=50ms Target Lambda invoked (plus target Firehose, plus target Step Function)
t=100ms Lambda begins
t=700ms Thumbnail written back
Similar end-to-end timing; different shape. EventBridge publishes cost per event; direct notifications are free at the notification layer.
What’s worth remembering
- S3 has two event mechanisms, and they coexist. The bucket’s notification configuration (direct to SNS/SQS/Lambda) and the EventBridge integration flag are independent. Both can be on; neither deduplicates.
- Direct notifications are prefix/suffix-filterable, three destination types. Good for point-to-point; less flexible for multi-consumer.
- EventBridge carries every object event into the EventBridge target catalogue. Good for fan-out, cross-account routing, content-based filtering.
- EventBridge exposes events that direct notifications don’t. Newer object-lifecycle events, richer replication events; worth reading the current event list for the latest.
- Filtering is the first tell. If the filter is “prefix + suffix,” direct is fine. If it’s “size > X,” “ETag present,” or “key matches regex-ish pattern,” EventBridge.
- Consumer count is the second tell. One consumer: direct. Many consumers: EventBridge.
- Cross-account is the third tell. Direct notifications work cross-account via destination resource policies but get brittle at scale; EventBridge bus policies and rule routing are cleaner for landing-zone patterns.
- Async retries matter. S3’s direct-to-Lambda path uses Lambda async invocation (with 6-hour retry window and DLQ); EventBridge has its own retry + DLQ per target.
- Cost shape differs. EventBridge is per-event; direct notifications are free for the notification itself. At high volumes the math matters; at application volumes it’s rounding.
- Pick one per bucket. Both on the same bucket leads to duplicate invocations unless the consumers are deliberately set up to handle it.
Direct notifications for a single bucket with a single consumer and a simple filter; EventBridge when the shape is many buckets, many consumers, content-based filtering, or cross-account. Two mechanisms, two shapes, two matches. The work isn’t picking a favourite, it’s reading the shape of the bucket’s event consumer graph and matching the mechanism.