Channel Resolution
Map a CloudEvent to a KubeMQ channel and ClientID using the subject attribute, the channel query parameter, and the source attribute.
Every CloudEvent the connector receives must land on exactly one KubeMQ channel, and is attributed to one ClientID. The connector derives both from the event itself — no separate addressing layer — so a well-formed CloudEvent is self-routing.
Overview
The CloudEvents connector accepts events over plain HTTP without any KubeMQ-specific addressing fields. Instead, it reads two things from each incoming event:
- the target channel — which KubeMQ channel the message is published to, and
- the ClientID — the identity recorded on the message.
Both are resolved on a fixed priority order, with sensible auto-generation for the optional id and time attributes. Getting this mapping right keeps your producers portable: the same CloudEvent works against KubeMQ or any other CloudEvents endpoint.
Channel resolution
The destination channel is resolved in priority order:
- CE
subjectattribute — if the event setssubject, its value is used directly as the channel name. ?channel=query parameter — used only whensubjectis absent (or empty).- HTTP 400 — if neither is present, the request is rejected with
{"is_error": true, "message": "channel is required"}.
This applies to every send endpoint (/ce/send/event, /ce/send/event-store, /ce/send/command, /ce/send/query, /ce/queue/send). SSE subscribe endpoints always take the channel from the required ?channel= query parameter instead.
How it works
A subject on the event wins; otherwise the connector falls back to the ?channel= query parameter, and rejects the request when neither is set.
The subject attribute resolves the channel first; ?channel= is the fallback, and a missing channel is a 400.
Using subject (recommended)
The subject attribute is part of the standard CloudEvents envelope and carries semantic meaning — what the event is about. Using it for channel resolution makes the event self-describing and portable: the same event routes correctly without any KubeMQ-specific query string.
The native examples below all set subject to the channel name (event.SetSubject(channel), .withSubject(channel), subject=channel, …), publish one CloudEvent, and confirm the subscriber received it on that channel.
# subject sets the KubeMQ channel (structured mode)
curl -X POST http://localhost:9090/ce/send/event \
-H "Content-Type: application/cloudevents+json" \
-d '{
"specversion": "1.0",
"type": "com.example.order.created",
"source": "order-service",
"subject": "orders",
"datacontenttype": "application/json",
"data": {"order_id": "12345"}
}'
# binary mode — subject travels in the ce-subject header
curl -X POST http://localhost:9090/ce/send/event \
-H "Content-Type: application/json" \
-H "ce-specversion: 1.0" \
-H "ce-type: com.example.order.created" \
-H "ce-source: order-service" \
-H "ce-subject: orders" \
-d '{"order_id": "12345"}'// Example: events/BasicPubSub
// Run: dotnet run
using CloudNative.CloudEvents;
using CloudNative.CloudEvents.SystemTextJson;
using System.Net.Http.Headers;
static string ServerUrl() =>
Environment.GetEnvironmentVariable("KUBEMQ_CE_URL") ?? "http://localhost:9090";
var base_ = ServerUrl();
var channel = "csharp-ce-events.basic-pubsub";
var clientId = "kubemq-ce-csharp-example";
// Build and publish a CloudEvent (structured mode).
var formatter = new JsonEventFormatter();
var cloudEvent = new CloudEvent
{
Id = Guid.NewGuid().ToString(),
Type = "com.kubemq.examples.events.sent",
Source = new Uri($"urn:{clientId}"),
Subject = channel, // subject = KubeMQ channel
DataContentType = "application/json",
Data = new { message = "Hello from C# CloudEvents example!" },
};
var eventBytes = formatter.EncodeStructuredModeMessage(cloudEvent, out var contentType);
using var httpClient = new HttpClient();
using var content = new ByteArrayContent(eventBytes.ToArray());
content.Headers.ContentType = MediaTypeHeaderValue.Parse(contentType.ToString());
var resp = await httpClient.PostAsync($"{base_}/ce/send/event", content);
Console.WriteLine($"Published to channel '{channel}': status={resp.StatusCode}");// Example: events/basic-pubsub
// Run: go run ./events/basic-pubsub/main.go
package main
import (
"encoding/json"
"fmt"
"log"
"net/http"
"os"
"strings"
cloudevents "github.com/cloudevents/sdk-go/v2"
)
func serverURL() string {
if u := os.Getenv("KUBEMQ_CE_URL"); u != "" {
return u
}
return "http://localhost:9090"
}
func main() {
base := serverURL()
channel := "go-ce-events.basic-pubsub"
// Build and send CloudEvent (structured mode).
event := cloudevents.NewEvent()
event.SetType("com.kubemq.examples.events.sent")
event.SetSource("kubemq-ce-go-example")
event.SetSubject(channel) // subject = KubeMQ channel
_ = event.SetData(cloudevents.ApplicationJSON, map[string]string{
"message": "Hello from Go CloudEvents example!",
})
body, _ := json.Marshal(event)
req, _ := http.NewRequest("POST", base+"/ce/send/event", strings.NewReader(string(body)))
req.Header.Set("Content-Type", "application/cloudevents+json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
log.Fatal("send event:", err)
}
defer resp.Body.Close()
fmt.Printf("Published to channel %q: status=%d\n", channel, resp.StatusCode)
}// Example: events/basic-pubsub
// Run: mvn compile exec:java
package io.kubemq.examples.events.basicpubsub;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.cloudevents.CloudEvent;
import io.cloudevents.core.builder.CloudEventBuilder;
import io.cloudevents.core.format.EventFormat;
import io.cloudevents.core.provider.EventFormatProvider;
import io.cloudevents.jackson.JsonFormat;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.time.OffsetDateTime;
import java.util.Map;
import java.util.UUID;
public class Main {
static final ObjectMapper MAPPER = new ObjectMapper();
public static void main(String[] args) throws Exception {
String base = System.getenv().getOrDefault("KUBEMQ_CE_URL", "http://localhost:9090");
String channel = "java-ce-events.basic-pubsub";
String clientId = "kubemq-ce-java-example";
// Build CloudEvent (structured mode).
EventFormatProvider.getInstance().registerFormat(new JsonFormat());
EventFormat format = EventFormatProvider.getInstance().resolveFormat(JsonFormat.CONTENT_TYPE);
CloudEvent event = CloudEventBuilder.v1()
.withId(UUID.randomUUID().toString())
.withType("com.kubemq.examples.events.sent")
.withSource(URI.create(clientId))
.withSubject(channel) // subject = KubeMQ channel
.withDataContentType("application/json")
.withTime(OffsetDateTime.now())
.withData("application/json",
MAPPER.writeValueAsBytes(Map.of("message", "Hello from Java CloudEvents example!")))
.build();
byte[] body = format.serialize(event);
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(base + "/ce/send/event"))
.POST(HttpRequest.BodyPublishers.ofByteArray(body))
.header("Content-Type", "application/cloudevents+json")
.build();
HttpResponse<String> response = HttpClient.newHttpClient()
.send(request, HttpResponse.BodyHandlers.ofString());
System.out.printf("Published to channel '%s': status=%d%n", channel, response.statusCode());
}
}// Example: events/basic-pubsub
// Run: npx tsx events/basic-pubsub/index.ts
import { CloudEvent, HTTP } from 'cloudevents';
function serverUrl(): string {
return process.env.KUBEMQ_CE_URL ?? 'http://localhost:9090';
}
async function main(): Promise<void> {
const base = serverUrl();
const channel = 'js-ce-events.basic-pubsub';
const clientId = 'kubemq-ce-js-example';
// Build and publish CloudEvent (structured mode).
const event = new CloudEvent({
type: 'com.kubemq.examples.events.sent',
source: clientId,
subject: channel, // subject = KubeMQ channel
datacontenttype: 'application/json',
data: { message: 'Hello from JavaScript/TypeScript CloudEvents example!' },
});
const message = HTTP.structured(event);
const resp = await fetch(`${base}/ce/send/event`, {
method: 'POST',
headers: message.headers as Record<string, string>,
body: message.body as string,
});
console.log(`Published to channel '${channel}': status=${resp.status}`);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});# Example: events/basic_pubsub
# Run: python events/basic_pubsub/main.py
from __future__ import annotations
import os
import requests
from cloudevents.v1.conversion import to_structured
from cloudevents.v1.http import CloudEvent
def server_url() -> str:
return os.environ.get("KUBEMQ_CE_URL", "http://localhost:9090")
def main() -> None:
base = server_url()
channel = "python-ce-events.basic-pubsub"
client_id = "kubemq-ce-python-example"
# Build and send CloudEvent (structured mode).
event = CloudEvent(
attributes={
"type": "com.kubemq.examples.events.sent",
"source": client_id,
"subject": channel, # subject = KubeMQ channel
"datacontenttype": "application/json",
},
data={"message": "Hello from Python CloudEvents example!"},
)
headers, body = to_structured(event)
resp = requests.post(f"{base}/ce/send/event", data=body,
headers=dict(headers), timeout=10)
print(f"Published to channel '{channel}': status={resp.status_code}")
if __name__ == "__main__":
main()# Example: events/basic_pubsub
# Run: ruby events/basic_pubsub/main.rb
require "net/http"
require "uri"
require "json"
require "securerandom"
require "cloud_events"
base = ENV.fetch("KUBEMQ_CE_URL", "http://localhost:9090")
channel = "ruby-ce-events.basic-pubsub"
client_id = "kubemq-ce-ruby-example"
# Build and publish CloudEvent (structured mode).
sdk = CloudEvents::HttpBinding.default
event = CloudEvents::Event::V1.new(
id: SecureRandom.uuid,
type: "com.kubemq.examples.events.sent",
source: URI("urn:#{client_id}"),
subject: channel, # subject = KubeMQ channel
spec_version: "1.0",
data_content_type: CloudEvents::ContentType.new("application/json"),
data: JSON.generate({ message: "Hello from Ruby CloudEvents example!" })
)
headers, body = sdk.encode_event(event, structured_format: "json")
uri = URI("#{base}/ce/send/event")
Net::HTTP.start(uri.host, uri.port) do |http|
req = Net::HTTP::Post.new(uri)
req["Content-Type"] = headers["Content-Type"]
req.body = body
res = http.request(req)
puts "Published to channel '#{channel}': status=#{res.code}"
end//! Example: events/basic-pubsub
//! Run: cargo run -p basic-pubsub
use cloudevents::{EventBuilder, EventBuilderV10};
use reqwest::Client;
use serde_json::json;
use std::env;
use uuid::Uuid;
fn server_url() -> String {
env::var("KUBEMQ_CE_URL").unwrap_or_else(|_| "http://localhost:9090".to_string())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let base = server_url();
let channel = "rust-ce-events.basic-pubsub";
let client_id = "kubemq-ce-rust-example";
// Build CloudEvent (structured mode using cloudevents-sdk).
let event = EventBuilderV10::new()
.id(Uuid::new_v4().to_string())
.ty("com.kubemq.examples.events.sent")
.source(format!("urn:{}", client_id))
.subject(channel) // subject = KubeMQ channel
.data(
"application/json",
json!({"message": "Hello from Rust CloudEvents example!"}),
)
.build()?;
let body = serde_json::to_string(&event)?;
let resp = Client::new()
.post(format!("{}/ce/send/event", base))
.header("Content-Type", "application/cloudevents+json")
.body(body)
.send()
.await?;
println!("Published to channel '{}': status={}", channel, resp.status());
Ok(())
}If subject is present but empty, the connector treats it as absent and falls through to the ?channel= query parameter.
Using ?channel=
The ?channel= query parameter is the fallback for cases where subject is unavailable or reserved for a different meaning. It works in both structured and binary content modes.
# channel via query parameter — no subject on the event
curl -X POST "http://localhost:9090/ce/send/event?channel=orders" \
-H "Content-Type: application/cloudevents+json" \
-d '{
"specversion": "1.0",
"type": "com.example.order.created",
"source": "order-service",
"data": {"order_id": "12345"}
}'Use ?channel= when:
- the CloudEvent schema reserves
subjectfor a different semantic purpose; - you are integrating with external CE producers that do not set
subject; or - you need to route the same event payload to different channels per deployment.
Avoid setting subject to a value different from the intended channel. Downstream CESQL routing can read CE attributes including subject, so a mismatch produces confusing routing behavior. When neither subject nor ?channel= is supplied, the connector returns HTTP 400 with {"is_error": true, "message": "channel is required"}.
ClientID resolution
Every message also carries a KubeMQ ClientID. The connector resolves it as follows:
- Auth claims — when authentication is enabled and the request carries valid credentials, the authenticated
ClientIDfrom the JWT claims overrides every other source. - CE
sourceattribute — used as the default ClientID when auth is disabled, or when the authenticated claim isanonymous.
So with auth off, the event's source becomes the ClientID; with auth on, the verified caller identity always wins, regardless of what source says. See Authentication for how claims are established.
| Condition | ClientID used |
|---|---|
| Auth enabled, valid claims | Authenticated ClientID from claims |
Auth disabled (or claim anonymous) | CE source attribute |
Auto-generated attributes
Two optional CloudEvent attributes are filled in by the connector before the event is processed, so you may omit them:
| Attribute | Auto-generated when | Value |
|---|---|---|
id | empty or missing | a new UUID v4 |
time | zero or missing | the current UTC time (RFC3339Nano) |
The required attributes — specversion, type, and source — are never auto-generated; omitting any of them is a validation error. For the full attribute-to-tag mapping (including how every CE attribute is stored as a ce_* tag), see the CE to KubeMQ mapping reference.
Best practices
- Prefer
subjectfor channel resolution — it makes the CloudEvent self-describing and portable across environments. - Use
?channel=only for compatibility with external CE sources that cannot setsubject. - Keep channel names consistent across publishers and subscribers to avoid silent routing mismatches.
- When authentication is enabled, rely on the authenticated ClientID rather than
sourcefor identity-sensitive logic — claims always overridesource.
Related
CE to KubeMQ mapping
The full attribute-to-tag table, extension handling, and data vs data_base64.
Events
Publish and subscribe to fire-and-forget CloudEvents on a channel.
CESQL routing
Route events by CloudEvent attributes instead of channel name alone.
Authentication
JWT Bearer auth and how claims override the source-derived ClientID.
Was this page helpful?