EventBridge’s promise of "at-least-once" delivery means that an event will arrive at its destination at least one time, but potentially more.

Let’s see this in action. Imagine a simple scenario: a new user signs up for our service. This event needs to trigger two actions: sending a welcome email and updating a user profile counter.

Here’s a simplified representation of the EventBridge event and the rules we’d set up:

// Sample Event
{
  "source": "user.signup",
  "detail-type": "UserCreated",
  "detail": {
    "userId": "user-12345",
    "email": "test@example.com"
  }
}

Now, let’s define the rules that would route this event:

Rule 1: Send Welcome Email

  • Event Pattern:
    {
      "source": ["user.signup"],
      "detail-type": ["UserCreated"]
    }
    
  • Target: An AWS Lambda function named SendWelcomeEmailFunction.

Rule 2: Update User Counter

  • Event Pattern:
    {
      "source": ["user.signup"],
      "detail-type": ["UserCreated"]
    }
    
  • Target: An AWS Step Functions state machine named UpdateUserCounterStateMachine.

When the UserCreated event fires, EventBridge receives it. It then evaluates this event against all its configured rules. For each rule whose event pattern matches, EventBridge invokes the associated target(s).

The "at-least-once" delivery guarantee comes into play because EventBridge doesn’t just send an event and forget about it. It actively monitors the delivery process. If a target acknowledges successful receipt of an event (e.g., a Lambda function returning successfully, or a Step Functions state machine receiving the event), EventBridge considers the delivery successful for that specific invocation. However, if EventBridge doesn’t receive this acknowledgment within a certain timeframe, or if the target reports an error, EventBridge will retry the delivery. This retry mechanism is the core of the "at-least-once" guarantee.

The internal workings involve EventBridge maintaining a persistent log of events it has attempted to deliver and the status of those deliveries. When a target is invoked, EventBridge waits for a signal of success. If this signal doesn’t arrive, or if it’s a signal of failure, EventBridge queues the event for a subsequent retry. These retries are not immediate; they follow an exponential backoff strategy, increasing the delay between retries to avoid overwhelming a struggling target. EventBridge will continue to retry delivery for a configurable period, up to 24 hours, before eventually giving up and sending the event to a Dead-Letter Queue (DLQ) if one is configured.

This retry behavior is precisely why you might see duplicate events. If an event is successfully delivered to the target, but the acknowledgment from the target to EventBridge is lost or delayed due to network issues, EventBridge might still interpret this as a failed delivery and retry. The target then receives the event a second time. Similarly, if the target processes the event but crashes after processing but before sending the acknowledgment back to EventBridge, EventBridge will retry.

The primary lever you control is the configuration of your targets and their acknowledgment mechanisms. For Lambda, a successful execution of the handler function is an implicit acknowledgment. For other services like SQS, the successful addition of a message to the queue is the acknowledgment. Crucially, if your target fails to process the event (e.g., a database error during an update), it should signal this failure. EventBridge will then retry. This is why idempotent design is critical for your targets – they must be able to handle receiving the same event multiple times without causing unintended side effects. You can configure retry policies and Dead-Letter Queues (DLQs) for EventBridge itself, which allows you to specify how long EventBridge should keep retrying and where to send events that it ultimately fails to deliver.

The most surprising aspect of EventBridge’s "at-least-once" guarantee is that it’s not solely about the reliability of EventBridge’s own infrastructure. A significant part of the guarantee relies on the ability of your target services to correctly signal success or failure back to EventBridge. If your Lambda function throws an unhandled exception, EventBridge sees that as a failure and retries. If it returns successfully but the network between Lambda and EventBridge is momentarily broken, EventBridge might also retry. The system is designed to err on the side of delivering an event more than once rather than risking losing it altogether.

The next challenge you’ll likely encounter is implementing robust idempotency in your event consumers to handle these potential duplicate deliveries gracefully.

Want structured learning?

Take the full Eventbridge course →