Amazon EventBridge is designed for event-driven architectures, but when you need to process events in a strict, guaranteed order, especially across distributed systems, EventBridge’s default behavior doesn’t quite cut it. The core issue is that EventBridge is an "at-least-once" delivery service for its targets. When you fan out events to multiple targets, or even to a single target multiple times, there’s no inherent guarantee about the sequence in which those events will be processed by the target application, even if they arrive in a specific order. This becomes a critical problem when your application logic depends on events happening in a precise sequence, like updating a database record based on a series of user actions.
The standard way to achieve ordered processing with EventBridge involves a combination of SQS FIFO queues and EventBridge’s ability to target SQS queues directly. Here’s how it looks in action:
Let’s say we have a UserSignup event that needs to be processed in order. We want to ensure that a UserCreated event is always processed before any subsequent UserProfileUpdated events for the same user.
First, we set up an SQS FIFO queue. The key here is the MessageGroupId. For ordered processing, all messages belonging to the same logical sequence must share the same MessageGroupId.
{
"QueueName": "MyUserEventsFIFOQueue",
"Attributes": {
"FifoQueue": "true",
"ContentBasedDeduplication": "true"
}
}
Next, we create an EventBridge rule that matches our UserSignup event pattern. Instead of directly targeting a Lambda function or another service that might process events out of order, we set the target to be our SQS FIFO queue.
{
"Name": "UserSignupRule",
"EventPattern": {
"source": ["my.application"],
"detail-type": ["UserSignup"]
},
"State": "ENABLED",
"Targets": [
{
"Id": "MyUserEventsFIFOQueueTarget",
"Arn": "arn:aws:sqs:us-east-1:123456789012:MyUserEventsFIFOQueue",
"SqsParameters": {
"MessageGroupId": "$.detail.userId",
"MessageDeduplicationId": "$.detail.eventId"
}
}
]
}
The SqsParameters are crucial. MessageGroupId is set to $.detail.userId. This means all events related to the same userId will be routed to the same message group within the SQS FIFO queue. SQS FIFO guarantees that messages within the same message group are processed in the order they are sent. MessageDeduplicationId is set to $.detail.eventId to prevent duplicate processing if EventBridge retries sending the same event.
Now, a Lambda function (or any SQS consumer) is configured to poll this SQS FIFO queue. Because the messages are in a FIFO queue and grouped by userId, the Lambda function will receive and process events for a specific user strictly in the order they were published by EventBridge.
The mental model is this: EventBridge acts as the event bus, routing events based on their patterns. When ordered processing is required, it doesn’t guarantee processing order itself. Instead, it leverages SQS FIFO’s ordering capabilities by directing events into specific message groups. SQS FIFO then becomes the enforcer of order for messages within a given group. The downstream consumer simply reads from the queue, and SQS ensures it gets them in the correct sequence for each MessageGroupId.
What most people miss is that MessageGroupId doesn’t need to be a simple field from the event. You can construct it using JSONPath expressions that combine multiple fields or even use static values if your ordering requirement is global rather than per-entity. For example, if you had different types of events that needed separate ordering, you could use $.detail-type as a MessageGroupId, or a combination like $.detail.userId + '-' + $.detail-type. This allows for very granular control over which events are ordered together.
With this setup, the next logical step is to consider how to handle potential processing failures in your SQS consumer and how to implement dead-letter queues for those failed messages.