Skip to main content

Kafka Topics

The platform uses Apache Kafka (segmentio/kafka-go) for all domain events. Topics are organised by bounded context — one topic per domain — and partitioned by entity ID to guarantee in-order delivery for each entity.


Naming Convention

All platform topics follow the pattern platform.{domain}.events.

✅ platform.auth.events
✅ platform.money.events
✅ platform.billing.events
❌ platform.auth.user.events (entity-level — too granular)
❌ platform.auth.user.123.events (per-instance — would create millions of topics)

One topic per bounded context. This is the industry standard that balances Kafka ordering guarantees with operational manageability. Splitting by entity would require one topic per record (millions of topics, unmanageable). Merging everything into one topic would destroy per-entity ordering guarantees.


Platform Topics

TopicDomainRetention
platform.auth.eventsIAM7 days
platform.module.eventsModule Registry7 days
platform.money.eventsMoney Service7 days
platform.files.eventsFile Storage7 days
platform.notify.eventsNotify Service7 days
platform.audit.eventsAudit Service30 days
platform.billing.eventsBilling7 days
platform.data.eventsData Layer (CDC)7 days
note

Event Catalog For a comprehensive list of all specific events published to each domain topic, please refer to the detailed Event Catalog.

platform.audit.events uses a 30-day retention override (KAFKA_AUDIT_RETENTION_HOURS=720). This prevents loss of audit events during extended maintenance windows or incident investigations. All other topics default to 7 days (KAFKA_RETENTION_HOURS=168).


Partition Key

The partition key for all events is entityId — the primary identifier of the entity the event is about:

EvententityId
auth.user.createduserId
money.wallet.crediteduserId
billing.plan.changedtenantId
crm.contact.updatedcontactId

All events about the same entity are routed to the same Kafka partition. This guarantees per-entity ordering:

Events for user A (userId: 01j9pa5...):
partition 2 → [auth.user.created, auth.role.changed, auth.user.logged_in]
Delivered to consumer in this exact order. ✅

Events for user A and user B:
partition 2 → user A events
partition 5 → user B events
Relative ordering between A and B is NOT guaranteed. ❌

Cross-entity ordering is explicitly not guaranteed. This is the fundamental Kafka partition trade-off. If workflow correctness requires strict cross-entity ordering, implement a saga coordinator — do not rely on Kafka topic ordering across different entities.


Consumer Groups

Consumer groups ensure that each event is processed by exactly one instance of a consuming service, even when multiple pods are running.

Naming convention

{consuming-service}.{eventType}-handler

Examples:
crm.auth.user.created-handler (CRM listens to auth.user.created)
notify.money.wallet.credited-handler (Notify listens to money.wallet.credited)
audit.auth-logger (Audit logs all auth events)

All pods of the same service join the same consumer group. Kafka distributes partitions across pods automatically.

Consumer group per handler

If a service subscribes to multiple event types, each subscription creates its own consumer group:

Module: crm
subscribe('auth.user.created') → group: crm.auth.user.created-handler
subscribe('billing.plan.changed') → group: crm.billing.plan.changed-handler

This allows each handler to progress through its topic independently. A slow auth.user.created handler does not block the billing.plan.changed handler.


Retention

ParameterDefaultOverride
Default retention7 days (168 hours)KAFKA_RETENTION_HOURS=168
platform.audit.events30 days (720 hours)KAFKA_AUDIT_RETENTION_HOURS=720

Replay beyond the retention window is blocked:

{
"type": "https://api.septemcore.com/problems/validation-error",
"status": 400,
"detail": "Requested replay start is beyond retention window (168h).",
"code": "EVENTS_RETENTION_EXCEEDED"
}

For longer-term replay needs (disaster recovery, compliance), events must be archived to object storage (S3-compatible) via a separate Kafka Connect sink before they age out.


Max Event Payload

The maximum event payload is 1 MB — the Kafka default message.max.bytes.

Message overhead (envelope headers): ~1 KB
Available for data field: ~1022 KB

This limit covers the combined size of the entire serialised Kafka message, including the envelope fields (id, type, source, tenantId, schemaVersion, timestamp, traceId) plus data.

Claim Check Pattern for Large Payloads

If an event payload would exceed 1 MB (e.g. a report generation result, a bulk import manifest), use the Claim Check pattern:

1. Upload the large payload to object storage (S3-compatible bucket)
2. Get the signed URL / storage key
3. Publish the event with a reference instead of the inline payload

Example:
Event: report.generated
Data: {
"reportId": "01j9parse700000000000000",
"storageKey": "reports/01j9p3kz.../01j9parse7.../report.pdf",
"bucket": "platform-reports",
"expiresAt": "2026-04-22T10:30:00.000Z"
}

Consumer:
1. Receive event with reference
2. Download payload from object storage using storageKey
3. Process full payload

The Claim Check pattern keeps Kafka topics fast and prevents broker memory pressure from large payloads.


Dead Letter Topics

Each domain has a corresponding dead-letter topic for events that fail consumer processing after 3 retries:

Source topicDead-letter topic
platform.auth.eventsplatform.auth.dlq
platform.money.eventsplatform.money.dlq
platform.billing.eventsplatform.billing.dlq
… (all domains)platform.{domain}.dlq

Dead-letter topics use the same retention as their source topic. Events in DLQ are visible in Admin → Events → DLQ and can be retried via POST https://api.septemcore.com/v1/events/dlq/{eventId}/retry.


Inspecting Topics

Available topics for the current tenant are discoverable via the SDK:

const topics = await kernel.events().listTopics();
// ['platform.auth.events', 'platform.data.events', ...]

Topic schema (Protobuf message definition) for a given event type:

const schema = await kernel.events().topicSchema('auth.user.created');
// { schemaVersion: '1.0.0', fields: [...], protoFile: '...' }

REST:

GET https://api.septemcore.com/v1/events/topics
Authorization: Bearer <access_token>
GET https://api.septemcore.com/v1/events/topics/{eventType}/schema
Authorization: Bearer <access_token>

Configuration Reference

Environment variableDefaultDescription
KAFKA_BROKERSComma-separated broker addresses
KAFKA_RETENTION_HOURS168Default topic retention (7 days)
KAFKA_AUDIT_RETENTION_HOURS720Audit topic retention override (30 days)
KAFKA_CONSUMER_LAG_ALERT_THRESHOLD10000Lag threshold in events before alerting
DLQ_REPLAY_RATE_LIMIT50Max DLQ replay events per second