AWS EventBridge

By Vishvesh Patel · DevOps EngineerUpdated September 16, 2026

Route events by content, not topic. Event buses, pattern matching, AWS service events, scheduled rules, archive and replay, and why the DLQ belongs on the target.

Amazon EventBridge

EventBridge is a router for events. Something publishes a JSON event onto a bus, rules match it on content, and matching events are delivered to targets. Lambda, SQS, Step Functions, another bus, an API endpoint.

The difference from SNS is where the routing decision lives. With SNS, a publisher chooses a topic and subscribers take what that topic carries. With EventBridge, publishers emit events onto one bus and each consumer declares a pattern describing what it wants. Adding a consumer with new interests requires no change to the publisher and no new topic.

It was previously called CloudWatch Events, which is why some console URLs, IAM actions and older documentation still say events.

Buses

Every account has a default bus, which is where AWS's own service events arrive. Create your own for application events:

aws events create-event-bus --name order-events
aws events list-event-buses --query 'EventBuses[].Name' --output table

Keeping application events off the default bus is worth doing: the default carries a high volume of AWS service noise, and a separate bus gives you cleaner rules, separate archives and its own resource policy for cross-account access.

Events and patterns

An event is JSON with a small required envelope:

{
  "Source": "orders.api",
  "DetailType": "OrderPlaced",
  "Detail": "{\"orderId\":\"A-4821\",\"amount\":149.99,\"region\":\"eu-west-1\"}",
  "EventBusName": "order-events"
}
aws events put-events --entries file://event.json
{ "FailedEntryCount": 0, "Entries": [{ "EventId": "8f3c1e2a-4b7d-11ef-9c2a-0242ac120002" }] }

Check FailedEntryCount. put-events returns HTTP 200 even when individual entries fail, so a script that only checks the exit code reports success while dropping events.

A rule matches on structure and values:

{
  "source": ["orders.api"],
  "detail-type": ["OrderPlaced"],
  "detail": {
    "amount": [{ "numeric": [">", 100] }],
    "region": ["eu-west-1", "eu-central-1"]
  }
}
aws events put-rule --name high-value-orders \
  --event-bus-name order-events \
  --event-pattern file://pattern.json

aws events put-targets --rule high-value-orders \
  --event-bus-name order-events \
  --targets 'Id=1,Arn=arn:aws:lambda:eu-west-1:123456789012:function:fraud-check'

Pattern matching rules that catch people out:

  • Field names are lowercase in patterns (detail-type) even though put-events takes DetailType. A pattern using DetailType matches nothing, silently.
  • A field you omit is a wildcard. Omitting source matches every source, which is usually not intended.
  • All listed fields must match, and within a field the array is an OR.
  • detail is a JSON string on the wire but matched as a nested object.

Test before deploying:

aws events test-event-pattern \
  --event-pattern file://pattern.json \
  --event file://event.json
{ "Result": true }

AWS service events

The default bus receives events from AWS itself, which lets you react to infrastructure changes without polling:

{
  "source": ["aws.ec2"],
  "detail-type": ["EC2 Instance State-change Notification"],
  "detail": { "state": ["terminated", "stopped"] }
}

Useful examples: an ECS task stopping unexpectedly, a CodePipeline stage failing, an S3 object being created, a GuardDuty finding, an RDS snapshot completing, an AWS Health event announcing planned maintenance. The aws.health source in particular is worth a rule. It is how you find out about scheduled hardware retirement before it happens rather than afterwards.

Scheduled rules

aws events put-rule --name nightly-report \
  --schedule-expression 'cron(0 3 * * ? *)'

EventBridge cron has six fields, not five, the extra one is year, and the day-of-week field uses ? where the other day field is specified. A five-field expression is rejected, which is the usual first error. All schedules are UTC.

rate(5 minutes) is simpler where you just need an interval.

For anything non-trivial, prefer EventBridge Scheduler, the newer dedicated service: it supports time zones with automatic daylight-saving handling, one-off schedules, and flexible time windows to avoid every schedule firing at exactly the same second.

Transforming the input

Targets often want a different shape from the event. An input transformer reshapes it without a Lambda in the middle:

aws events put-targets --rule high-value-orders --event-bus-name order-events \
  --targets '[{
    "Id": "1",
    "Arn": "arn:aws:sqs:eu-west-1:123456789012:fraud-review",
    "InputTransformer": {
      "InputPathsMap": { "order": "$.detail.orderId", "amt": "$.detail.amount" },
      "InputTemplate": "{\"orderId\": \"<order>\", \"amount\": <amt>, \"priority\": \"high\"}"
    }
  }]'

Failures go to the target's DLQ

This is the part that most often gets missed. If a target fails, EventBridge retries for up to 24 hours and then discards the event. There is no queue holding it, and nothing tells you.

Attach a dead-letter queue per target:

aws events put-targets --rule high-value-orders --event-bus-name order-events \
  --targets '[{
    "Id": "1",
    "Arn": "arn:aws:lambda:eu-west-1:123456789012:function:fraud-check",
    "DeadLetterConfig": { "Arn": "arn:aws:sqs:eu-west-1:123456789012:eventbridge-dlq" },
    "RetryPolicy": { "MaximumRetryAttempts": 3, "MaximumEventAgeInSeconds": 3600 }
  }]'

Then alarm on that queue's depth. Without it, a broken target loses events quietly for as long as it stays broken.

Archive and replay

EventBridge can retain matching events and replay them later. Genuinely useful after fixing a consumer bug, and a capability SNS does not have:

aws events create-archive --archive-name order-archive \
  --event-source-arn "arn:aws:events:eu-west-1:123456789012:event-bus/order-events" \
  --retention-days 30

aws events start-replay --replay-name reprocess-sept \
  --event-source-arn "arn:aws:events:eu-west-1:123456789012:archive/order-archive" \
  --event-start-time 2026-09-01T00:00:00Z \
  --event-end-time 2026-09-02T00:00:00Z \
  --destination '{"Arn":"arn:aws:events:eu-west-1:123456789012:event-bus/order-events","FilterArns":["arn:aws:events:eu-west-1:123456789012:rule/order-events/high-value-orders"]}'

Use FilterArns to replay into one rule rather than every rule on the bus, or every consumer reprocesses everything.

Common errors

ValidationException: Parameter ScheduleExpression is not valid

Five cron fields instead of six, or a missing ? in the day-of-week position.

FailedEntryCount: 1
ErrorCode: NotAuthorizedForSourceException

The caller lacks events:PutEvents on that bus, or the bus resource policy denies the source account.

Rule exists, target never fires, no errors anywhere. Nearly always the pattern. Run test-event-pattern against a real event. The usual culprits are DetailType instead of detail-type, or a typo in source that makes the pattern match nothing.

Quick reference

TaskCommand
Create a busaws events create-event-bus --name <name>
Publishaws events put-events --entries file://e.json
Create a ruleaws events put-rule --event-pattern file://p.json
Add a targetaws events put-targets --rule <r> --targets …
Test a patternaws events test-event-pattern --event-pattern … --event …
Schedule--schedule-expression 'cron(0 3 * * ? *)'
Disable a ruleaws events disable-rule --name <r>
Archiveaws events create-archive --retention-days 30
Replayaws events start-replay …

Key takeaways

  • Consumers declare what they want. Adding one needs no publisher change and no new topic.
  • Patterns use lowercase field names (detail-type), unlike put-events, and a mismatch fails silently.
  • An omitted field is a wildcard, so always pin source.
  • test-event-pattern before deploying: it is the fastest way to debug a rule that never fires.
  • Check FailedEntryCount. put-events returns 200 with failed entries inside.
  • Cron has six fields and runs in UTC; use EventBridge Scheduler for time zones.
  • Attach a DLQ per target, or failed events are discarded after 24 hours with no trace.
  • Archive gives you replay, which is the main operational advantage over SNS.

Check your understanding

Exercise

A rule that never fires

A rule on a custom bus has a pattern of {"source":["orders.api"],"DetailType":["OrderPlaced"]}. Events are published successfully with FailedEntryCount 0, but the Lambda target is never invoked and no errors appear. What is wrong?

Exercise

Events lost during an outage

A Lambda target was broken for three hours. Afterwards, the events from that window are nowhere to be found and no alarm fired. What was missing?


Frequently Asked Questions

What is the difference between EventBridge and SNS?

Both deliver events to multiple consumers; they differ in who decides routing. With SNS the publisher picks a topic and subscribers accept whatever that topic carries, so a new consumer with different interests usually means a new topic. With EventBridge, publishers emit onto a bus and each consumer writes a pattern describing the events it wants, matched on content. EventBridge also integrates with AWS service events, supports scheduling, and offers archive and replay. SNS has lower latency and higher throughput, and remains the better fit for simple fan-out to queues.

Why is my EventBridge rule not triggering?

Test the pattern first: aws events test-event-pattern --event-pattern file://p.json --event file://e.json returns true or false in one call. The usual cause is field naming, put-events takes DetailType and Detail, while patterns match the delivered event's lowercase detail-type and detail, so a pattern using the PascalCase names matches nothing without erroring. Also confirm the rule is on the right bus, that it is enabled, and that the target's resource policy allows EventBridge to invoke it.

How do I schedule something with EventBridge cron?

--schedule-expression 'cron(0 3 * * ? *)' runs at 03:00 UTC daily. EventBridge cron has six fields rather than the usual five, minute, hour, day-of-month, month, day-of-week, year, and requires ? in whichever day field you are not specifying. All times are UTC, with no daylight-saving handling. For anything involving time zones, one-off schedules or jitter, use EventBridge Scheduler instead, which is a separate and more capable service.

What happens if an EventBridge target fails?

EventBridge retries with exponential backoff for up to 24 hours, then discards the event permanently. It is a router and retains nothing. That means an outage longer than the retry window silently loses events, with no backlog to notice afterwards. Attach a DeadLetterConfig to each target so undeliverable events land in an SQS queue, set a RetryPolicy to control attempts, and alarm on the DLQ depth. Creating an archive additionally lets you replay a time window after fixing the cause.

Should I use the default event bus or create my own?

Create your own for application events. The default bus carries every AWS service event in the account, which is a high volume of noise to write rules against, and mixing your events into it makes archives and rule management harder. A custom bus gives you a clean namespace, its own archive and retention, and a resource policy you can use to grant specific accounts permission to publish. Keep the default bus for reacting to AWS service events.

How does EventBridge archive and replay work?

An archive retains events matching an optional pattern for a retention period you set, and a replay re-publishes them onto the bus for a chosen time range. It is the main operational advantage over SNS: after fixing a consumer bug you can reprocess exactly the events that were mishandled. Use the FilterArns field on the replay to target specific rules. Replaying onto the whole bus makes every consumer reprocess everything, which is rarely what you want.

Continue Learning

Explore Related Topics

When it breaks

Related Resources