KubeMQ
ConnectorsCloudEventsHow-to guides

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:

  1. CE subject attribute — if the event sets subject, its value is used directly as the channel name.
  2. ?channel= query parameter — used only when subject is absent (or empty).
  3. 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.

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 subject for 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:

  1. Auth claims — when authentication is enabled and the request carries valid credentials, the authenticated ClientID from the JWT claims overrides every other source.
  2. CE source attribute — used as the default ClientID when auth is disabled, or when the authenticated claim is anonymous.

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.

ConditionClientID used
Auth enabled, valid claimsAuthenticated 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:

AttributeAuto-generated whenValue
idempty or missinga new UUID v4
timezero or missingthe 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 subject for channel resolution — it makes the CloudEvent self-describing and portable across environments.
  • Use ?channel= only for compatibility with external CE sources that cannot set subject.
  • Keep channel names consistent across publishers and subscribers to avoid silent routing mismatches.
  • When authentication is enabled, rely on the authenticated ClientID rather than source for identity-sensitive logic — claims always override source.

Was this page helpful?

On this page