> For the complete documentation index, see [llms.txt](https://brightercommand.gitbook.io/paramore-brighter-documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://brightercommand.gitbook.io/paramore-brighter-documentation/using-an-external-bus/cloudeventssupport.md).

# Cloud Events Support

CloudEvents is a CNCF specification for describing event data in a common, standardized way.

> **How-to** · Applies to **Brighter V10**

## What are CloudEvents?

[CloudEvents](https://cloudevents.io/) is a [CNCF](https://www.cncf.io/) specification for describing event data in a common, standardized way. Without a standard for metadata, each messaging framework uses its own set of metadata fields, making it difficult to write interoperable code that works across different messaging systems.

CloudEvents solves this problem by providing a [specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md) that defines:

* A standard set of metadata attributes for events
* Multiple content modes (binary and structured)
* Protocol bindings for various transport protocols

Brighter V10 provides full CloudEvents specification support, making it easy to build interoperable, event-driven systems.

## Content Modes

CloudEvents can be transmitted in two modes, and Brighter supports both:

### Binary-Mode (Recommended)

In binary-mode, CloudEvents attributes are mapped to protocol headers, and the event data is placed in the message body. The default `JsonMessageMapper<T>` writes binary mode, on every transport.

**When to use binary mode:**

* Your consumers read the attributes from headers
* You want efficient serialization
* You want to inspect event metadata without deserializing the body

**Example RabbitMQ message with binary CloudEvents**, as `Paramore.Brighter.MessagingGateway.RMQ.Async` writes it:

```
Properties:
  content-type: application/json

Headers:
  cloudEvents_id: "01a0e7d8-3aef-7360-bf91-1f2c771a9cb3"
  cloudEvents_source: "https://example.com/orders"
  cloudEvents_type: "com.example.order.created"
  cloudEvents_specversion: "1.0"
  cloudEvents_time: "2026-09-28T11:48:22.895Z"

Body:
  {"orderId":"12345","correlationId":null,"id":"01a0e7d8-3aef-7360-bf91-1f2c771a9cb3"}
```

Each transport names the headers its own way — `ce_` on Kafka, one `cloudeventheaders` attribute on AWS SNS/SQS — see [CloudEvents Across Transports](/paramore-brighter-documentation/using-an-external-bus/cloudeventssupport/cloudeventsreference.md#cloudevents-across-transports).

### Structured Content Mode

In structured mode, both CloudEvents attributes and data are placed in the message body as a JSON object. `CloudEventJsonMessageMapper<T>` writes structured mode; choosing it is what selects the mode, whichever transport carries the message.

**When to use structured mode:**

* A consumer reads the event from the body alone
* You need to preserve all metadata in a single payload

**Example message body with structured CloudEvents:**

```json
{
  "id": "01a0e7d8-3e13-7fbe-abce-bce954e1b9b7",
  "specversion": "1.0",
  "source": "https://example.com/orders",
  "type": "com.example.order.created",
  "datacontenttype": "application/json",
  "dataschema": null,
  "subject": null,
  "time": "2026-09-28T11:48:23.699316+00:00",
  "data": {
    "orderId": "12345",
    "correlationId": null,
    "id": "01a0e7d8-3e13-7fbe-abce-bce954e1b9b7"
  }
}
```

## Setting CloudEvents in Publication

CloudEvents properties are configured in the `Publication` when you register producers. The Publication is passed to your message mapper, which can access these properties.

### Basic Publication with CloudEvents

```csharp
var publication = new Publication
{
    Topic = new RoutingKey("com.example.orders"),
    RequestType = typeof(OrderCreated),

    // CloudEvents attributes
    Source = new Uri("https://example.com/orders"),
    Type = new CloudEventsType("com.example.order.created"),
    Subject = "order/12345"
};

services.AddBrighter(options => { /* ... */ })
    .AddProducers(configure =>
    {
        configure.ProducerRegistry = new RabbitMQProducerRegistryFactory(
            new RmqMessagingGatewayConnection { /* ... */ },
            new[] { publication }
        ).Create();
    });
```

### Additional CloudEvents Properties

You can set additional CloudEvents properties in the Publication:

```csharp
var publication = new Publication
{
    Topic = new RoutingKey("com.example.orders"),
    RequestType = typeof(OrderCreated),

    // Required CloudEvents
    Source = new Uri("https://example.com/orders"),
    Type = new CloudEventsType("com.example.order.created"),

    // Optional CloudEvents
    DataSchema = new Uri("https://example.com/schemas/order-created.json"),
    Subject = "order/12345",
};
```

## Using CloudEvents in Message Mappers

In V10, Brighter passes the `Publication` to your message mapper, giving you access to CloudEvents properties. However, most users won't need custom mappers anymore thanks to [default message mappers](/paramore-brighter-documentation/using-an-external-bus/messagemappers/defaultmessagemappers.md).

### Default Mappers Handle CloudEvents Automatically

Brighter V10 includes default message mappers that automatically handle CloudEvents:

* **`JsonMessageMapper<T>`**: Uses **binary mode** CloudEvents (headers)
* **`CloudEventJsonMessageMapper<T>`**: Uses **structured mode** CloudEvents (JSON body)

If you're using default mappers (recommended), CloudEvents are handled automatically based on your Publication configuration.

### Custom Mapper with CloudEvents

If you need a custom mapper (e.g., for transforms), you can access CloudEvents from the Publication:

```csharp
public class OrderCreatedMapper : IAmAMessageMapper<OrderCreated>
{
    public IRequestContext? Context { get; set; }

    public Message MapToMessage(OrderCreated request, Publication publication)
    {
        // CloudEvents properties come from the Publication
        var header = new MessageHeader(
            messageId: request.Id,
            topic: publication.Topic,
            messageType: MessageType.MT_EVENT,
            source: publication.Source,           // CloudEvents source
            type: publication.Type                 // CloudEvents type
        );

        var body = new MessageBody(
            JsonSerializer.Serialize(request, JsonSerialisationOptions.Options)
        );

        return new Message(header, body);
    }

    public OrderCreated MapToRequest(Message message)
    {
        // CloudEvents attributes available in message header, available via RequestContext's OriginatingMessage property
        var orderCreated = JsonSerializer.Deserialize<OrderCreated>(
            message.Body.Value,
            JsonSerialisationOptions.Options
        );

        return orderCreated!;
    }
}
```

## CloudEvents Type for Message Routing

One powerful use of CloudEvents is content-based routing using the `type` attribute. This allows multiple message types on a single channel.

See [Dynamic Message Deserialization](/paramore-brighter-documentation/using-an-external-bus/dynamicmessagedeserialization.md) for details on using CloudEvents type for routing.

**Example:**

```csharp
using System;
using Paramore.Brighter;
using Paramore.Brighter.Actions;
using Paramore.Brighter.MessagingGateway.Kafka;

new KafkaSubscription(
    new SubscriptionName("paramore.example.orders"),
    channelName: new ChannelName("orders"),
    routingKey: new RoutingKey("orders"),
    getRequestType: message => message.Header.Type switch
    {
        var t when t == new CloudEventsType("com.example.order.created")
            => typeof(OrderCreated),
        var t when t == new CloudEventsType("com.example.order.updated")
            => typeof(OrderUpdated),
        _ => throw new InvalidMessageAction($"Unknown CloudEvents type: {message.Header.Type}")
    },
    groupId: "order-processor"
);
```

## OpenTelemetry Integration

CloudEvents includes extensions for distributed tracing that integrate with OpenTelemetry:

* **traceparent**: W3C Trace Context propagation
* **tracestate**: Vendor-specific trace information

Brighter automatically propagates OpenTelemetry trace context using CloudEvents distributed tracing extensions. See [OpenTelemetry Integration](/paramore-brighter-documentation/health-checks-and-observability/telemetry.md) for details.

```csharp
using Paramore.Brighter;

public override MyCommand Handle(MyCommand command)
{
    // Access the current span, a System.Diagnostics.Activity, from context;
    // OpenTelemetry exports its tags as span attributes
    Context?.Span?.SetTag("custom.attribute", "value");

    // Trace context is automatically propagated via CloudEvents headers
    // when you publish/post messages

    return base.Handle(command);
}
```

## Claim Check Pattern with DataRef

CloudEvents supports the [DataRef extension](https://github.com/cloudevents/spec/blob/main/cloudevents/extensions/dataref.md) for the Claim Check pattern, where large payloads are stored externally and only a reference is included in the message.

```csharp
using System.Text.Json;
using Paramore.Brighter;
using Paramore.Brighter.JsonConverters;
using Paramore.Brighter.Transforms.Attributes;

public class LargeOrderMapper : IAmAMessageMapper<LargeOrder>
{
    public IRequestContext? Context { get; set; }

    [ClaimCheck(step: 0, thresholdInKb: 256)]
    public Message MapToMessage(LargeOrder request, Publication publication)
    {
        // If the message exceeds 256KB, Brighter will:
        // 1. Store the payload in a luggage store (e.g., S3)
        // 2. Set the CloudEvents dataref attribute to the storage location
        // 3. Send a lightweight message with just the reference

        var header = new MessageHeader(
            messageId: request.Id,
            topic: publication.Topic,
            messageType: MessageType.MT_EVENT
        );

        var body = new MessageBody(
            JsonSerializer.Serialize(request, JsonSerialisationOptions.Options)
        );

        return new Message(header, body);
    }

    [RetrieveClaim(step: 0)]
    public LargeOrder MapToRequest(Message message)
    {
        // Brighter automatically retrieves the payload using the dataref
        var order = JsonSerializer.Deserialize<LargeOrder>(
            message.Body.Value,
            JsonSerialisationOptions.Options
        );

        return order!;
    }
}
```

See [Claim Check Pattern](/paramore-brighter-documentation/using-an-external-bus/claimcheck.md) for more details.

### Breaking Changes

When migrating to V10, be aware of these CloudEvents-related breaking changes:

1. **Message ID**: Changed from `Guid` to `Id`, which converts implicitly from a `string`

   ❌ **V9 — superseded**

   ```csharp
   // ...
   var messageId = Guid.NewGuid();
   ```

   ✅ **V10 — current**

   ```csharp
   using System;
   using Paramore.Brighter;

   Id messageId = Guid.NewGuid().ToString(); // or any unique string
   ```
2. **Correlation ID**: Changed from `Guid` to `Id`, which converts implicitly from a `string`

   ❌ **V9 — superseded**

   ```csharp
   // ...
   var correlationId = Guid.NewGuid();
   ```

   ✅ **V10 — current**

   ```csharp
   using System;
   using Paramore.Brighter;

   Id correlationId = Guid.NewGuid().ToString(); // or any unique string
   ```
3. **Publication passed to mapper**: Message mappers now receive `Publication` parameter

   ❌ **V9 — superseded**

   ```csharp
   // ...
   public Message MapToMessage(OrderCreated request)
   ```

   ✅ **V10 — current**

   ```csharp
   // ...
   public Message MapToMessage(OrderCreated request, Publication publication)
   ```

See the [V10 Migration Guide](/paramore-brighter-documentation/v10-migration/v10migrationguide.md) for complete migration instructions.

## CloudEvents Best Practices

### 1. Choose the Right Content Mode

* Use **binary mode**, the default mapper's, when your consumers read headers; every transport Brighter supports carries the attributes beside the body
* Use **structured mode**, `CloudEventJsonMessageMapper<T>`, when a consumer expects the whole envelope in the body
* The mapper selects the mode, not the transport

### 2. Use Meaningful CloudEvents Type

The `type` attribute should follow reverse-DNS naming:

```csharp
// Good - Reverse DNS, specific
Type = new CloudEventsType("com.example.orders.order.created")

// Bad - Too generic
Type = new CloudEventsType("OrderCreated")

// Bad - Not following conventions
Type = new CloudEventsType("CREATE_ORDER")
```

### 3. Include Source Context

The `source` should uniquely identify where the event originated:

```csharp
// Good - Specific service and environment
Source = new Uri("https://prod.example.com/orders-service")

// Good - Resource-specific
Source = new Uri("/orders/12345")

// Bad - Too vague
Source = new Uri("https://example.com")
```

### 4. Use Subject for Fine-Grained Routing

The `subject` provides additional context in the scope of the source:

```csharp
// For an order event
Source = new Uri("https://example.com/orders")
Subject = "order/12345"

// For a customer event
Source = new Uri("https://example.com/customers")
Subject = "customer/67890/profile"
```

### 5. Leverage Default Mappers

In most cases, use Brighter's default message mappers rather than implementing custom mappers:

```csharp
// Recommended - Use default mapper
services.AddBrighter(options => { })
    .AddProducers(configure => { })
    .AutoFromAssemblies([typeof(OrderCreated).Assembly]);

// No explicit mapper needed! CloudEvents handled automatically.
```

Only create custom mappers when you need [transform pipelines](/paramore-brighter-documentation/using-an-external-bus/messagemappers/messagetransforms.md).

## Further Reading

* [CloudEvents Primer](https://github.com/cloudevents/spec/blob/main/cloudevents/primer.md) - Introduction to CloudEvents
* [CloudEvents Specification](https://github.com/cloudevents/spec/blob/v1.0.2/cloudevents/spec.md) - Full specification
* [CloudEvents Reference](/paramore-brighter-documentation/using-an-external-bus/cloudeventssupport/cloudeventsreference.md) - The attribute tables and the per-transport matrix
* [Default Message Mappers](/paramore-brighter-documentation/using-an-external-bus/messagemappers/defaultmessagemappers.md) - Using automatic message mapping
* [Dynamic Message Deserialization](/paramore-brighter-documentation/using-an-external-bus/dynamicmessagedeserialization.md) - Content-based routing with CloudEvents
* [OpenTelemetry Integration](/paramore-brighter-documentation/health-checks-and-observability/telemetry.md) - Distributed tracing with CloudEvents
* [Claim Check Pattern](/paramore-brighter-documentation/using-an-external-bus/claimcheck.md) - Large message handling with DataRef
* [Dapr CloudEvents](https://docs.dapr.io/developing-applications/building-blocks/pubsub/pubsub-cloudevents/) - How Dapr uses CloudEvents


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://brightercommand.gitbook.io/paramore-brighter-documentation/using-an-external-bus/cloudeventssupport.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
