Modern cloud applications rarely consist of a single service. A typical production system may contain APIs, background workers, databases, notification services, payment systems, analytics pipelines, and AI workloads.

When these services communicate directly with one another, the architecture can quickly become difficult to maintain.

For example:

Order API
   |
   +----> Email Service
   |
   +----> Inventory Service
   |
   +----> Payment Service
   |
   +----> Analytics Service

As more consumers are added, the number of connections increases.

An event-driven architecture provides another approach:

Order API
    |
    v
EventBridge Event Bus
    |
    +----> Inventory
    +----> Email
    +----> Analytics
    +----> Notifications

Amazon EventBridge allows applications to publish events and route them to targets based on event rules. Custom event buses are especially useful when an application needs its own event-routing boundary instead of putting every event onto a shared default bus.

This article explains how custom EventBridge event buses work, how to design reliable event-driven applications with them, and how to integrate them with .NET applications.

What Is an Event Bus?

An event bus is a logical destination where applications publish events.

An event represents something that happened.

Examples include:

OrderCreated
PaymentCompleted
CustomerRegistered
InvoiceGenerated
DocumentProcessed
AgentCompleted

The producer does not need to know which services consume the event.

For example:

Order Service
     |
     | OrderCreated
     v
Event Bus
     |
     +----> Inventory
     +----> Email
     +----> Analytics

This creates loose coupling between services.

The order service only needs to publish OrderCreated.

It does not need direct dependencies on inventory, email, or analytics services.

Default Event Bus vs Custom Event Bus

EventBridge provides a default event bus, but applications can also create custom event buses.

A custom event bus provides a dedicated routing boundary for a particular application, domain, team, or integration scenario.

For example:

Default Event Bus
      |
      +---- Application A
      +---- Application B
      +---- Application C

A custom design could instead use:

Order Bus
      |
      +---- Inventory
      +---- Shipping
      +---- Billing

Customer Bus
      |
      +---- CRM
      +---- Notifications
      +---- Analytics

The appropriate structure depends on organizational and application boundaries.

Why Use Custom Event Buses?

Custom event buses can provide several architectural benefits.

Domain Separation

Different business domains can have separate event-routing boundaries.

Commerce Bus
Customer Bus
Billing Bus
AI Bus

This can make ownership and event management easier to understand.

Access Control

Permissions can be scoped around event buses.

A producer may have permission to publish to one bus without automatically gaining access to unrelated event infrastructure.

Cleaner Routing Rules

A custom bus can contain rules relevant to a particular workload.

For example:

Order Bus

OrderCreated
   |
   +----> Inventory Rule
   +----> Notification Rule
   +----> Analytics Rule

This reduces unrelated rules being mixed together.

EventBridge Architecture

A simplified EventBridge architecture looks like this:

                  Producer
                     |
                     v
              Custom Event Bus
                     |
          +----------+----------+
          |          |          |
          v          v          v
        Rule A     Rule B     Rule C
          |          |          |
          v          v          v
       Target A   Target B   Target C

The important components are:

  • Event producer — publishes an event.

  • Event bus — receives events.

  • Event rule — determines which events should be processed.

  • Target — receives matching events.

Targets can include AWS services and supported endpoints.

Event Structure

A useful event should contain enough information for consumers to understand what happened.

A conceptual event might look like:

{
  "source": "orders",
  "detail-type": "OrderCreated",
  "detail": {
    "orderId": "ORD-1042",
    "customerId": "CUS-204",
    "total": 149.99
  }
}

The event should describe the business fact rather than dictate what consumers must do.

For example:

Good:
OrderCreated

Less useful:
SendEmailToCustomer

OrderCreated describes something that happened.

SendEmailToCustomer describes an implementation action.

Keeping events focused on business facts allows more consumers to use them later.

Creating a Custom Event Bus

The infrastructure can be created through AWS tooling or infrastructure-as-code.

Conceptually:

Custom Event Bus
Name:
orders-production

The application then publishes events to that bus.

A production environment should generally manage infrastructure declaratively rather than creating resources manually for every deployment.

Infrastructure-as-code tools such as AWS CloudFormation, AWS CDK, or Terraform can help maintain consistent environments.

Publishing Events From .NET

A .NET application can use the AWS SDK to publish EventBridge events.

A simplified example:

using Amazon.EventBridge;
using Amazon.EventBridge.Model;
using System.Text.Json;

public class OrderEventPublisher
{
    private readonly IAmazonEventBridge eventBridge;

    public OrderEventPublisher(
        IAmazonEventBridge eventBridge)
    {
        this.eventBridge = eventBridge;
    }

    public async Task PublishOrderCreatedAsync(
        string orderId,
        string customerId,
        decimal total,
        CancellationToken cancellationToken)
    {
        var detail = new
        {
            orderId,
            customerId,
            total
        };

        var request = new PutEventsRequest
        {
            Entries =
            [
                new PutEventsRequestEntry
                {
                    EventBusName = "orders-production",
                    Source = "orders",
                    DetailType = "OrderCreated",
                    Detail = JsonSerializer.Serialize(detail)
                }
            ]
        };

        await eventBridge.PutEventsAsync(
            request,
            cancellationToken);
    }
}

The producer only needs to know the event contract and destination bus.

It does not need to know which services will consume the event.

Registering the AWS Client in ASP.NET Core

The AWS SDK client can be registered with dependency injection.

builder.Services.AddAWSService<IAmazonEventBridge>();

Then inject the client into your event publishing service.

This keeps AWS infrastructure concerns outside controllers and business logic.

Event Rules

Publishing an event is only the first step.

Rules determine what should happen with matching events.

Suppose the bus receives:

OrderCreated
OrderCancelled
PaymentCompleted
ShipmentCreated

A rule can match only:

OrderCreated

and route it to a target.

Conceptually:

Event Bus
    |
    +---- OrderCreated ------> Inventory Lambda
    |
    +---- PaymentCompleted --> Receipt Service
    |
    +---- ShipmentCreated --> Notification Service

This allows multiple consumers to react independently.

Event Pattern Matching

Rules can filter events based on event attributes.

For example, a rule might match:

{
  "source": ["orders"],
  "detail-type": ["OrderCreated"]
}

Another rule could filter based on the contents of the detail.

For example:

{
  "source": ["orders"],
  "detail-type": ["OrderCreated"],
  "detail": {
    "total": [
      {
        "numeric": [">", 1000]
      }
    ]
  }
}

This allows different workflows to react to different types of events.

One Event, Multiple Consumers

One of the strongest characteristics of event-driven architecture is fan-out.

Consider:

                 OrderCreated
                      |
                      v
                EventBridge
              /      |       \
             /       |        \
            v        v         v
       Inventory   Email    Analytics

The producer publishes the event once.

Multiple consumers can independently process it.

This avoids direct dependencies such as:

Order Service
   |
   +----> Inventory
   |
   +----> Email
   |
   +----> Analytics

The event bus becomes the integration boundary.

Reliability Considerations

Event-driven systems are asynchronous, which changes how reliability should be designed.

An HTTP request might return an immediate success or failure.

An event publication can succeed while downstream processing happens later.

Therefore, distinguish between:

Event accepted
        |
        v
Event delivered
        |
        v
Target processed
        |
        v
Business operation completed

These are different states.

A producer should not assume that publishing an event means every downstream operation has already completed.

Handling Failed Consumers

Suppose an event reaches a target but processing fails.

OrderCreated
     |
     v
Consumer
     |
     X
  Failure

A reliable architecture needs a strategy for failures.

Depending on the target and architecture, this can include retries, dead-letter queues, error handling, and operational alerts.

A dead-letter queue can provide a place for events that could not be successfully processed after the configured delivery attempts.

Conceptually:

Event
 |
 v
Consumer
 |
 +---- Success ---> Done
 |
 +---- Failure
          |
          v
       Retry
          |
          +---- Success
          |
          +---- Failure
                   |
                   v
                DLQ

The exact retry and dead-letter behavior depends on the EventBridge target configuration and downstream service.

Idempotency Is Essential

Retries introduce a common problem:

The same event may be processed more than once.

Suppose:

OrderCreated
     |
     v
Consumer
     |
     +---- Process
     |
     +---- Timeout

The producer or delivery system may retry.

The consumer could receive the same logical event again.

If processing is not idempotent, the application might:

Charge customer
Charge customer again

or:

Create shipment
Create duplicate shipment

Consumers should therefore use an event identifier or another idempotency key.

For example:

public async Task HandleAsync(
    string eventId,
    CancellationToken cancellationToken)
{
    if (await AlreadyProcessedAsync(eventId))
        return;

    await ProcessEventAsync(cancellationToken);

    await MarkProcessedAsync(
        eventId,
        cancellationToken);
}

The storage mechanism should be designed so that concurrent processing cannot create duplicate effects.

Event Ordering

Developers often assume that events will always be processed in the same order in which they were created.

That assumption can create bugs.

Consider:

OrderCreated
OrderUpdated
OrderCancelled

A consumer may need to account for the possibility that events are observed or processed independently.

If strict ordering is a business requirement, design explicitly for it rather than assuming that a general event bus provides the ordering semantics your application needs.

Schema Evolution

Events are APIs.

Once multiple consumers depend on an event structure, changing it carelessly can break downstream systems.

For example, an initial event might contain:

{
  "orderId": "ORD-100",
  "total": 100
}

Later, the producer may need:

{
  "orderId": "ORD-100",
  "total": 100,
  "currency": "USD"
}

Adding optional fields is generally easier for consumers to tolerate than removing or changing the meaning of existing fields.

Treat event contracts as versioned integration contracts.

Event Naming

Use consistent event names.

A practical convention is:

OrderCreated
OrderCancelled
PaymentCompleted
CustomerRegistered
InvoiceGenerated

Names should describe facts that have occurred.

Avoid vague names such as:

OrderEvent
ProcessOrder
DoSomething

Clear names make rules and consumers easier to understand.

Separating Business Events From Technical Events

Not every event needs to represent a business operation.

Technical events may also be useful:

ApplicationStarted
FileProcessed
JobCompleted

However, business events should remain focused on domain meaning.

For example:

PaymentCompleted

is more reusable than:

StripeWebhookReceived

The first describes the business fact.

The second exposes an implementation detail.

EventBridge and AI Applications

Event-driven architecture is also useful for AI applications.

An AI agent may produce events such as:

AgentStarted
ToolCalled
DocumentRetrieved
AgentCompleted

These events can be consumed by:

  • Monitoring systems

  • Audit services

  • Analytics pipelines

  • Notification systems

  • Workflow processors

For example:

AI Agent
    |
    v
AgentCompleted
    |
    v
EventBridge
   /   |    \
  v    v     v
Audit Analytics Notification

This prevents the agent application from directly integrating with every downstream system.

Event-Driven Architecture vs Direct API Calls

Approach

Direct API

Event-Driven

Communication

Synchronous

Asynchronous

Coupling

Higher

Lower

Immediate response

Yes

Usually no

Fan-out

Requires orchestration

Natural

Retry handling

Application-specific

Infrastructure-supported patterns

Failure isolation

More limited

Stronger

Workflow complexity

Can grow quickly

Better for independent consumers

Debugging

Often straightforward

Requires event tracing

Neither approach replaces the other.

Use direct APIs when the caller needs an immediate response.

Use events when the producer should notify one or more consumers without waiting for them to finish.

When Not to Use an Event Bus

Event-driven architecture is not automatically better.

A simple operation such as:

GetCustomerById()

may be better represented as a direct API call.

Introducing an event bus can add:

  • Operational complexity

  • Asynchronous behavior

  • Additional monitoring requirements

  • Event schema management

  • Retry handling

  • Debugging complexity

Use an event bus when asynchronous communication and decoupling provide a clear architectural benefit.

Monitoring Event-Driven Applications

Production systems should monitor:

Events published
Events matched
Target failures
Retry count
Dead-letter messages
Processing latency
Event age
Consumer health

A useful dashboard might contain:

Metric

Purpose

Published events

Producer activity

Failed invocations

Consumer reliability

Retry count

Delivery problems

DLQ messages

Events requiring investigation

Processing duration

Consumer performance

Event age

Processing backlog

Distributed tracing can also help connect the original HTTP request to the events it produced and the downstream operations triggered by those events.

Security

Event buses should be treated as infrastructure boundaries.

Follow least-privilege access.

For example:

Order Service
   |
   +---- Publish ---> Order Bus

Inventory Service
   |
   +---- Consume ---> OrderCreated

The order service does not necessarily need permissions to consume unrelated events.

Likewise, consumers should only receive the events required for their responsibilities.

Sensitive event data should also be minimized.

Do not place secrets or unnecessary personal information into event payloads.

Common Mistakes

Creating One Giant Event Bus

A single bus can become difficult to govern when unrelated domains and teams share it.

Use logical boundaries that reflect application ownership and operational needs.

Making Events Too Large

Events should contain the information consumers actually need.

Avoid turning events into complete database records unless there is a clear reason.

Putting Commands Into Event Names

An event should generally describe what happened.

Prefer:

OrderCreated

over:

CreateOrder

Ignoring Duplicate Processing

Assume that consumers may need to handle duplicate delivery safely.

Design idempotency into important operations.

Ignoring Dead-Letter Events

A DLQ is not a permanent storage system.

Create operational processes for investigating and replaying failed events where appropriate.

Changing Event Contracts Without Planning

Consumers may depend on the existing schema.

Treat event schemas as integration contracts.

Best Practices

When building applications with Amazon EventBridge custom event buses:

  1. Use events to represent meaningful facts.

  2. Keep producers independent from consumers.

  3. Use custom buses to establish clear application or domain boundaries.

  4. Create focused event rules.

  5. Design consumers to be idempotent.

  6. Plan for retries and failed processing.

  7. Monitor dead-letter events.

  8. Treat event schemas as APIs.

  9. Avoid unnecessary payload data.

  10. Use least-privilege permissions.

  11. Monitor event and target latency.

  12. Use tracing and correlation identifiers for troubleshooting.

  13. Do not assume event ordering unless the architecture explicitly guarantees it.

  14. Use direct APIs when synchronous communication is the better fit.

Conclusion

Amazon EventBridge custom event buses provide a useful foundation for building loosely coupled, event-driven applications.

Instead of creating direct dependencies between every service, applications can publish business events to an event bus and allow independent consumers to react to them.

A well-designed architecture looks like this:

Producer
   |
   v
Custom Event Bus
   |
   +---- Rule ----> Consumer A
   |
   +---- Rule ----> Consumer B
   |
   +---- Rule ----> Consumer C

The real value comes from the architectural separation.

The producer does not need to know who consumes the event. Consumers can evolve independently, and new consumers can often be added without modifying the original producer.

For production systems, reliability requires more than publishing events. Teams should design for retries, duplicate delivery, idempotency, dead-letter handling, schema evolution, observability, and security.

Used appropriately, EventBridge can turn tightly connected service integrations into a more flexible event-driven architecture.

Summary

Custom EventBridge event buses provide an event-routing boundary between producers and consumers. They are useful when applications need asynchronous communication, fan-out, domain separation, and reduced coupling between services.

For .NET applications, the AWS SDK makes it possible to publish strongly structured events from ASP.NET Core services. The surrounding architecture should then focus on reliable consumers, clear event contracts, idempotent processing, failure handling, and operational visibility.

The key principle is simple:

Publish what happened.
Let independent consumers decide what to do next.