Skip to main content

Event Catalog

The Event Catalog is the authoritative reference for every event published on the Platform-Kernel event bus. It covers schema definitions, Kafka topic configuration, publisher-to-consumer mappings, and replay/retention rules.

Events are the primary mechanism for cross-service communication and frontend real-time updates (via the WebSocket bridge in the Notify Service).

Source of truth: All schemas are generated from Protobuf definitions in proto/platform/events/v1/schemas/v1/. Any breaking schema change requires a new event type — never modify existing field semantics.

See also:


Topic Overview

TopicDomainEventsPartition keyRetention
platform.auth.eventsIAM9user_id7 days
platform.money.eventsMoney8wallet_id7 days
platform.module.eventsModule Registry6module_id7 days
platform.data.eventsData Layer (CDC)record_id7 days
platform.files.eventsFile Storagefile_id7 days
platform.notify.eventsNotifyuser_id7 days
platform.audit.eventsAuditentity_id30 days
platform.billing.eventsBillingtenant_id7 days

The Data Layer, Files, Notify, Audit, and Billing topics are internal CDC or operational streams. Their schemas are defined in respective service layers and are not part of the typed Protobuf schema package.

This catalog documents the three core typed topics (auth, money, module) whose schemas live in proto/platform/events/v1/schemas/v1/.


Event Envelope

Every event, regardless of topic, is wrapped in an EventEnvelope before publishing. Consumers receive the envelope and unpack the typed payload with proto.Unmarshal / Any.unpack.

// proto/platform/events/v1/schemas/v1/common.proto
message EventEnvelope {
string event_id = 1; // UUID v7 — idempotency key
string event_type = 2; // domain.entity.action
string source = 3; // Publishing service
string tenant_id = 4; // Gateway-injected — never trust client
string schema_version = 5; // "1.0.0"
google.protobuf.Timestamp timestamp = 6;
string trace_id = 7; // OpenTelemetry trace ID
string correlation_id = 8; // Links related events (saga)
google.protobuf.Any payload = 9; // Typed domain event
string entity_id = 10; // Kafka partition key
}
FieldDescription
event_idUUID v7. Time-sortable. Used as Kafka message key for idempotent replay.
entity_idSet to the domain partition key (e.g., user_id, wallet_id). Deterministic partition routing per entity.
correlation_idLinks multiple events in a saga or request chain.
tenant_idInjected by the Gateway — SDK and consumers MUST NOT trust caller-provided values.

Schema Versioning Policy

RuleDetail
Backward-compatible changesAdd optional fields freely — old consumers ignore unknown fields (proto3 default)
Breaking changesCreate a new event_type (e.g., auth.user.created.v2) — never mutate existing field numbers or semantics
CI guardbuf breaking --use WIRE_JSON runs on every PR — wire-breaking changes fail the pipeline
Deprecation windowOld event_type kept alive for 2 full retention windows (14 days for 7-day topics) before removal
Schema version fieldschema_version in envelope is informational — event_type is the canonical discriminator

Replay Rules

RuleDetail
Consumer seekEventService.ReplayFrom seeks a consumer group to any past timestamp within retention
Replay boundCannot replay beyond Kafka topic retention (KAFKA_RETENTION_HOURS=168 for 7-day topics)
Financial event dedupmoney.* events: permanent dedup via processed_event_ids PostgreSQL table (no TTL)
Non-financial dedupOther events: Valkey dedup, 24-hour TTL — safe to replay within 24h without side-effects
Ordering guaranteeEvents with the same entity_id are always processed in order within a consumer group (Kafka partition guarantee)
Bulk suppressWhen consumer lag > 100 000 events, lag alerts suppressed for 30 minutes (KAFKA_BULK_IMPORT_SUPPRESS_MINUTES=30)

platform.auth.events

Kafka config:

ParameterValue
Topicplatform.auth.events
Partition keyuser_id
Retention7 days (KAFKA_RETENTION_HOURS=168)
PublisherIAM Service (services/iam)
Proto packageplatform.events.v1.schemas.v1
Proto fileproto/platform/events/v1/schemas/v1/auth.proto

Consumer mapping:

Consumer serviceConsumer groupEvents consumed
Audit Serviceaudit.auth-event-handlerAll auth events
Notify Servicenotify.user-event-handlerauth.user.created, auth.user.logged_in
Billing Servicebilling.user-event-handlerauth.user.created (tenant init)

auth.user.created

Published when a new user account is successfully created.

message UserCreatedEvent {
string user_id = 1;
string email = 2;
string display_name = 3;
string role = 4; // "member" | "admin" | "owner"
string tenant_id = 5;
google.protobuf.Timestamp created_at = 6;
}

Example envelope:

{
"eventId": "019614a8-0000-7000-8000-000000000001",
"eventType": "auth.user.created",
"source": "iam",
"tenantId": "tenant-acme",
"schemaVersion": "1.0.0",
"timestamp": "2026-04-22T10:56:00Z",
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
"payload": {
"@type": "type.googleapis.com/platform.events.v1.schemas.v1.UserCreatedEvent",
"userId": "usr-01234",
"email": "[email protected]",
"displayName": "Alice Smith",
"role": "member",
"tenantId": "tenant-acme",
"createdAt": "2026-04-22T10:56:00Z"
}
}

auth.user.updated

Published when a user's profile is modified. Uses a sparse update pattern: only changed fields are populated. Consumers must treat absent optional fields as "unchanged".

message UserUpdatedEvent {
string user_id = 1;
optional string email = 2; // Set only if email changed
optional string display_name = 3;
optional bool is_active = 4;
string updated_by = 5;
google.protobuf.Timestamp updated_at = 6;
}

auth.user.deleted

Published when a user account is soft-deleted.

message UserDeletedEvent {
string user_id = 1;
string deleted_by = 2;
string reason = 3; // "self" | "admin" | "policy"
google.protobuf.Timestamp deleted_at = 4;
}

auth.user.logged_in

Published on every successful authentication, regardless of method.

message UserLoggedInEvent {
string user_id = 1;
string session_id = 2;
string ip_address = 3;
string user_agent = 4;
string auth_method = 5; // "password" | "oauth" | "api_key"
google.protobuf.Timestamp logged_in_at = 6;
}

auth.user.logged_out

Published when a session is terminated.

message UserLoggedOutEvent {
string user_id = 1;
string session_id = 2;
string reason = 3; // "manual" | "expired" | "revoked"
google.protobuf.Timestamp logged_out_at = 4;
}

auth.role.changed

Published when a user's role is upgraded or downgraded. Triggers RBAC cache invalidation on the Gateway.

message RoleChangedEvent {
string user_id = 1;
string previous_role = 2;
string new_role = 3;
string changed_by = 4; // User ID of the admin/owner actor
string tenant_id = 5;
google.protobuf.Timestamp changed_at = 6;
}

auth.password.changed

Published when a user's password is changed or reset via any method.

message PasswordChangedEvent {
string user_id = 1;
string method = 2; // "self_change" | "admin_reset" | "forgot_password"
google.protobuf.Timestamp changed_at = 3;
}

Security note: Password hashes are never included in events. Only the method enum is published — sufficient for audit without exposing secrets.


auth.api_key.created

Published when a new API key is generated for a user.

message APIKeyCreatedEvent {
string key_id = 1;
string user_id = 2;
string key_prefix = 3; // First 8 chars only — never full key
string name = 4; // User-provided label
google.protobuf.Timestamp expires_at = 5;
google.protobuf.Timestamp created_at = 6;
}

auth.api_key.revoked

Published when an API key is revoked. Triggers immediate Valkey cache invalidation for the _apikey:{prefix} key on the Gateway.

message APIKeyRevokedEvent {
string key_id = 1;
string user_id = 2;
string revoked_by = 3;
string reason = 4;
google.protobuf.Timestamp revoked_at = 5;
}

platform.money.events

Kafka config:

ParameterValue
Topicplatform.money.events
Partition keywallet_id
Retention7 days
PublisherMoney Service (services/money)
Proto fileproto/platform/events/v1/schemas/v1/money.proto

Financial event dedup: All money.* events are deduplicated using a permanent PostgreSQL table (processed_event_ids). Valkey TTL-based dedup is NOT used for financial events. This prevents duplicate ledger entries regardless of replay or consumer restart.

Consumer mapping:

Consumer serviceConsumer groupEvents consumed
Audit Serviceaudit.money-event-handlerAll money events
Notify Servicenotify.money-event-handlerCredit / debit / transfer events
Billing Servicebilling.wallet-event-handlermoney.wallet.credited (revenue tracking)

money.wallet.created

message WalletCreatedEvent {
string wallet_id = 1;
string tenant_id = 2;
string currency = 3; // ISO 4217 (e.g. "USD", "EUR")
string owner_id = 4;
string owner_type = 5; // "user" | "merchant" | "system"
google.protobuf.Timestamp created_at = 6;
}

money.wallet.credited

message WalletCreditedEvent {
string wallet_id = 1;
string transaction_id = 2; // Idempotency key — used for dedup
string tenant_id = 3;
int64 amount = 4; // Minor units (cents). int64 — no float precision
string currency = 5;
int64 balance_after = 6; // Running balance (minor units)
string source = 7; // "deposit"|"refund"|"transfer_in"|"bonus"
string reference_id = 8; // External payment processor reference
string description = 9;
google.protobuf.Timestamp credited_at = 10;
}

Example:

{
"walletId": "wlt-99887766",
"transactionId": "txn-aabbcc001122",
"tenantId": "tenant-acme",
"amount": 10000,
"currency": "USD",
"balanceAfter": 150000,
"source": "deposit",
"referenceId": "stripe_pi_3P4K2",
"description": "Invoice #INV-2026-042 payment",
"creditedAt": "2026-04-22T10:56:00Z"
}

amount = 10000 = $100.00 USD. Always store and compare in minor units (cents). Never use float64 for money.


money.wallet.debited

message WalletDebitedEvent {
string wallet_id = 1;
string transaction_id = 2;
string tenant_id = 3;
int64 amount = 4;
string currency = 5;
int64 balance_after = 6;
string destination = 7; // "withdrawal"|"purchase"|"transfer_out"|"fee"
string reference_id = 8;
string description = 9;
google.protobuf.Timestamp debited_at = 10;
}

money.transfer.completed

message TransferCompletedEvent {
string transfer_id = 1; // Idempotency key
string from_wallet_id = 2;
string to_wallet_id = 3;
string tenant_id = 4;
int64 amount = 5;
string currency = 6;
string initiated_by = 7;
string description = 8;
google.protobuf.Timestamp completed_at = 9;
}

money.transfer.failed

message TransferFailedEvent {
string transfer_id = 1;
string from_wallet_id = 2;
string to_wallet_id = 3;
string tenant_id = 4;
int64 amount = 5;
string currency = 6;
string failure_reason = 7; // "insufficient_funds"|"wallet_frozen"|"limit_exceeded"
string initiated_by = 8;
google.protobuf.Timestamp failed_at = 9;
}

money.payment.processed

message PaymentProcessedEvent {
string payment_id = 1;
string wallet_id = 2;
string tenant_id = 3;
int64 amount = 4;
string currency = 5;
string provider = 6; // "stripe" | "apcopay"
string provider_ref = 7; // Provider's transaction reference
string status = 8; // "succeeded"|"failed"|"pending"|"refunded"
google.protobuf.Timestamp processed_at = 9;
}

money.wallet.frozen

message WalletFrozenEvent {
string wallet_id = 1;
string tenant_id = 2;
string frozen_by = 3; // "system" | "admin" | user_id
string reason = 4; // "suspicious_activity"|"compliance"|"manual"
google.protobuf.Timestamp frozen_at = 5;
}

money.wallet.unfrozen

message WalletUnfrozenEvent {
string wallet_id = 1;
string tenant_id = 2;
string unfrozen_by = 3;
string reason = 4;
google.protobuf.Timestamp unfrozen_at = 5;
}

platform.module.events

Kafka config:

ParameterValue
Topicplatform.module.events
Partition keymodule_id
Retention7 days
PublisherModule Registry Service (services/module-manager)
Proto fileproto/platform/events/v1/schemas/v1/module.proto

Consumer mapping:

Consumer serviceConsumer groupEvents consumed
Audit Serviceaudit.module-event-handlerAll module events
Gatewaygateway.module-cache-invalidatormodule.registry.activated, module.registry.deactivated
Notify Servicenotify.module-event-handlermodule.registry.activated

module.registry.registered

message ModuleRegisteredEvent {
string module_id = 1;
string name = 2;
string version = 3; // Semantic version (e.g. "1.2.0")
string manifest_hash = 4; // SHA-256 of manifest.json
string tenant_id = 5;
string registered_by = 6;
google.protobuf.Timestamp registered_at = 7;
}

module.registry.activated

message ModuleActivatedEvent {
string module_id = 1;
string tenant_id = 2;
string activated_by = 3;
google.protobuf.Timestamp activated_at = 4;
}

Gateway side-effect: This event triggers Gateway to invalidate modules:active:{tenantId} Valkey cache and reload xDS routing config via the Domain Resolver (LDS/RDS update). Zero-downtime, no restart.


module.registry.deactivated

message ModuleDeactivatedEvent {
string module_id = 1;
string tenant_id = 2;
string deactivated_by = 3;
string reason = 4; // "manual"|"policy"|"billing"|"violation"
google.protobuf.Timestamp deactivated_at = 5;
}

module.registry.updated

message ModuleUpdatedEvent {
string module_id = 1;
string previous_version = 2;
string new_version = 3;
string tenant_id = 4;
string updated_by = 5;
bool requires_migration = 6; // true → DB schema migration needed
google.protobuf.Timestamp updated_at = 7;
}

module.registry.config_changed

Published when a module's runtime configuration is changed via the admin API. Config values are intentionally omitted from the event — they may contain secrets. The event signals that a config cache refresh is needed.

message ModuleConfigChangedEvent {
string module_id = 1;
string tenant_id = 2;
string changed_by = 3;
string config_key = 4; // Which key changed (no value — may be secret)
google.protobuf.Timestamp changed_at = 5;
}

module.registry.health_changed

Published when the Gateway's health probe detects a module status transition.

message ModuleHealthChangedEvent {
string module_id = 1;
string previous_status = 2; // "healthy" | "degraded" | "unhealthy"
string new_status = 3;
string reason = 4;
google.protobuf.Timestamp changed_at = 5;
}

Publisher → Consumer Matrix

Event typeAuditNotifyBillingGateway
auth.user.created
auth.user.updated
auth.user.deleted
auth.user.logged_in
auth.user.logged_out
auth.role.changed✅ (RBAC cache)
auth.password.changed
auth.api_key.created
auth.api_key.revoked✅ (key cache)
money.wallet.created
money.wallet.credited
money.wallet.debited
money.transfer.completed
money.transfer.failed
money.payment.processed
money.wallet.frozen
money.wallet.unfrozen
module.registry.registered
module.registry.activated✅ (xDS reload)
module.registry.deactivated✅ (xDS reload)
module.registry.updated
module.registry.config_changed
module.registry.health_changed