include::{mod-loc}shared/all-attributes.adoc[]

[id="registry-events_{context}"]
= {registry} change events and the outbox pattern

[role="_abstract"]
{registry} emits change events whenever artifacts, versions, groups, or rules are created, updated, or deleted. These events enable integration with external systems for auditing, automation, and event-driven workflows.

This chapter explains the event system architecture, available event types, and how to configure event delivery for your storage variant:

* xref:registry-events-overview_{context}[]
* xref:registry-event-types-reference_{context}[]
* xref:registry-events-architecture_{context}[]
* xref:configuring-events-sql-debezium_{context}[]
* xref:configuring-events-kafkasql_{context}[]
* xref:registry-events-config-reference_{context}[]


[id="registry-events-overview_{context}"]
== Overview of {registry} change events

[role="_abstract"]
{registry} generates structured change events as part of its normal operations. Every time the state of the registry changes, an event is emitted with details about what changed.

Events follow the *outbox pattern*: they are produced as part of the same transaction that modifies the registry state, guaranteeing that events are consistent with the data. The delivery mechanism depends on which storage variant you use:

* **SQL storage (PostgreSQL, MSSQL)**: Events are written to an `outbox` database table and consumed using Debezium Change Data Capture (CDC).
* **KafkaSQL storage**: Events are published directly to a dedicated Kafka topic.

Typical use cases for registry events include:

* *Auditing and compliance* -- Track every change to schemas and API definitions.
* *CI/CD automation* -- Trigger pipeline runs when a new artifact version is registered.
* *Cache invalidation* -- Notify downstream services when a schema changes.
* *Event-driven architectures* -- React to registry state changes in real time.

NOTE: Event publishing is supported only with PostgreSQL and MSSQL when using SQL storage. MySQL and H2 do not support the outbox table. KafkaSQL supports events natively.


[id="registry-event-types-reference_{context}"]
== Event types reference

[role="_abstract"]
{registry} defines the following event types. Each event includes a JSON payload with details about the change.

[discrete]
=== Artifact events

.Artifact event types
[cols="1,2",options="header"]
|===
|Event type
|Description

|`ARTIFACT_CREATED`
|A new artifact was registered.

|`ARTIFACT_DELETED`
|An artifact and all its versions were deleted.

|`ARTIFACT_METADATA_UPDATED`
|An artifact's name, description, or owner was updated.
|===

[discrete]
=== Artifact version events

.Artifact version event types
[cols="1,2",options="header"]
|===
|Event type
|Description

|`ARTIFACT_VERSION_CREATED`
|A new version of an artifact was created.

|`ARTIFACT_VERSION_DELETED`
|An artifact version was deleted.

|`ARTIFACT_VERSION_METADATA_UPDATED`
|A version's metadata (name, description, labels) was updated.

|`ARTIFACT_VERSION_STATE_CHANGED`
|A version's lifecycle state changed (for example, from `ENABLED` to `DEPRECATED`).
|===

[discrete]
=== Group events

.Group event types
[cols="1,2",options="header"]
|===
|Event type
|Description

|`GROUP_CREATED`
|A new group was created.

|`GROUP_DELETED`
|A group was deleted.

|`GROUP_METADATA_UPDATED`
|A group's metadata was updated.
|===

[discrete]
=== Rule events

.Rule event types
[cols="1,2",options="header"]
|===
|Event type
|Description

|`GLOBAL_RULE_CONFIGURED`
|A global compatibility or validity rule was created, updated, or deleted.

|`GROUP_RULE_CONFIGURED`
|A group-level rule was created, updated, or deleted.

|`ARTIFACT_RULE_CONFIGURED`
|An artifact-level rule was created, updated, or deleted.
|===

[discrete]
=== Event payload structure

All events share a common base structure:

[source,json]
----
{
  "id": "<uuid>",
  "eventType": "<EVENT_TYPE>",
  "groupId": "<group-id>",
  "artifactId": "<artifact-id>"
}
----

Additional fields vary by event type:

* *Version events* include a `version` field.
* *State change events* include `oldState` and `newState` fields.
* *Metadata update events* include `name`, `description`, and `owner` fields where applicable.
* *Rule events* include `ruleType` and `rule` (the rule configuration) fields.

.Example: ARTIFACT_VERSION_CREATED payload
[source,json]
----
{
  "id": "550e8400-e29b-41d4-a716-446655440000",
  "eventType": "ARTIFACT_VERSION_CREATED",
  "groupId": "com.example",
  "artifactId": "my-schema",
  "version": "1.2.0",
  "name": "My Schema",
  "description": "Updated with new fields"
}
----

.Example: ARTIFACT_VERSION_STATE_CHANGED payload
[source,json]
----
{
  "id": "550e8400-e29b-41d4-a716-446655440001",
  "eventType": "ARTIFACT_VERSION_STATE_CHANGED",
  "groupId": "com.example",
  "artifactId": "my-schema",
  "version": "1.0.0",
  "oldState": "ENABLED",
  "newState": "DEPRECATED"
}
----


[id="registry-events-architecture_{context}"]
== Event system architecture

[role="_abstract"]
The event system uses CDI (Contexts and Dependency Injection) events internally to decouple the storage layer from event delivery. When a storage operation modifies state, it fires a CDI event. A storage-specific event processor observes the CDI event and routes it to the appropriate delivery mechanism.

[discrete]
=== Event flow

----
Storage operation (e.g., createArtifact)
       |
       v
  Fire CDI event
  (SqlOutboxEvent or KafkaSqlOutboxEvent)
       |
       +---------------------------+
       |                           |
       v                           v
  SQL storage                 KafkaSQL storage
  SqlEventsProcessor          KafkaSqlEventsProcessor
       |                           |
       v                           v
  Insert into outbox table    Publish to Kafka topic
  (PostgreSQL / MSSQL)        (registry-events)
       |                           |
       v                           |
  Debezium CDC connector           |
       |                           |
       +---------------------------+
       |
       v
  External consumers
  (Kafka topic: registry-events)
----

[discrete]
=== SQL storage: outbox pattern with Debezium

With SQL storage, events are written to an `outbox` database table as part of the same database transaction that performs the registry operation. This guarantees that events are only produced for changes that are actually committed.

The outbox table uses an *insert-then-delete* pattern: the event row is inserted and then immediately deleted within the same transaction. Debezium CDC captures the insert before the deletion takes effect, ensuring the event is published to Kafka without the outbox table growing indefinitely.

The outbox table schema:

[source,sql]
----
CREATE TABLE outbox (
    id            VARCHAR(128)  NOT NULL PRIMARY KEY,
    aggregatetype VARCHAR(255)  NOT NULL,
    aggregateid   VARCHAR(255)  NOT NULL,
    type          VARCHAR(255)  NOT NULL,
    payload       JSONB         NOT NULL
);
----

[discrete]
=== KafkaSQL storage: direct Kafka publishing

With KafkaSQL storage, events bypass the database entirely and are published directly to a dedicated Kafka topic. The events topic uses a single partition to guarantee total ordering of all events. The event's aggregate ID is used as the Kafka message key.


[id="configuring-events-sql-debezium_{context}"]
== Configuring events with SQL storage and Debezium

[role="_abstract"]
When using SQL storage with PostgreSQL or MSSQL, you can use Debezium to capture events from the outbox table and publish them to Kafka.

.Prerequisites

* {registry} is deployed with SQL storage using PostgreSQL or MSSQL.
* A Kafka cluster is available.
* Debezium is deployed with the Kafka Connect framework.

.Procedure

. Enable logical replication on your PostgreSQL database. Set the following in `postgresql.conf`:
+
[source]
----
wal_level = logical
----

. Create a Debezium PostgreSQL connector that monitors the outbox table. Use the following connector configuration as a starting point:
+
[source,json]
----
{
  "name": "registry-outbox-connector",
  "config": {
    "connector.class": "io.debezium.connector.postgresql.PostgresConnector",
    "database.hostname": "<database-host>",
    "database.port": "5432",
    "database.user": "<database-user>",
    "database.password": "<database-password>",
    "database.dbname": "<database-name>",
    "topic.prefix": "registry",
    "schema.include.list": "public",
    "table.include.list": "public.outbox",
    "transforms": "outbox",
    "transforms.outbox.type": "io.debezium.transforms.outbox.EventRouter"
  }
}
----

. Deploy the connector to your Kafka Connect cluster:
+
[source,bash]
----
curl -X POST http://<kafka-connect-host>:8083/connectors \
  -H "Content-Type: application/json" \
  -d @connector-config.json
----

. Verify that events are being published by consuming from the output topic:
+
[source,bash]
----
kafka-console-consumer --bootstrap-server <kafka-host>:9092 \
  --topic outbox.event.registry-events --from-beginning
----

The Debezium `EventRouter` transformation reads the outbox table columns and routes events to Kafka topics based on the `aggregatetype` column.


[id="configuring-events-kafkasql_{context}"]
== Configuring events with KafkaSQL storage

[role="_abstract"]
When using KafkaSQL storage, events are published directly to a Kafka topic with no additional infrastructure required. The events topic is created automatically when {registry} starts.

.Prerequisites

* {registry} is deployed with KafkaSQL storage.

.Procedure

. Optionally configure the events topic name by setting the following environment variable:
+
[source]
----
APICURIO_EVENTS_KAFKA_TOPIC=registry-events
----
+
The default topic name is `registry-events`.

. Optionally configure topic properties such as replication factor:
+
[source]
----
APICURIO_EVENTS_KAFKA_TOPIC_REPLICATION_FACTOR=3
----

. Consume events from the configured topic:
+
[source,bash]
----
kafka-console-consumer --bootstrap-server <kafka-host>:9092 \
  --topic registry-events --from-beginning
----

The events topic is configured with the following defaults:

* *Partitions*: 1 (single partition for total ordering guarantee)
* *Cleanup policy*: `delete`
* *Retention*: Infinite (`-1`)


[id="registry-events-config-reference_{context}"]
== Configuration reference

[role="_abstract"]
The following table lists configuration properties related to the {registry} event system.

.Event configuration properties
[cols="2,1,2",options="header"]
|===
|Property
|Default
|Description

|`apicurio.events.kafka.topic`
|`registry-events`
|Name of the Kafka topic used for publishing registry change events. Applies to both SQL (via Debezium) and KafkaSQL storage variants.

|`apicurio.events.kafka.topic.replication.factor`
|Inherited from journal topic
|Replication factor for the events topic (KafkaSQL only). If not set, inherits from the main KafkaSQL journal topic configuration.

|`apicurio.events.kafka.topic.cleanup.policy`
|`delete`
|Cleanup policy for the events topic (KafkaSQL only).

|`apicurio.events.kafka.topic.retention.ms`
|`-1` (infinite)
|Retention time in milliseconds for the events topic (KafkaSQL only).
|===

[discrete]
=== Storage variant support

.Event support by storage variant
[cols="1,1,1,1",options="header"]
|===
|Storage variant
|Events supported
|Delivery mechanism
|Requirements

|SQL (PostgreSQL)
|Yes
|Outbox table + Debezium CDC
|Debezium connector, Kafka

|SQL (MSSQL)
|Yes
|Outbox table + Debezium CDC
|Debezium connector, Kafka

|SQL (MySQL)
|No
|--
|--

|SQL (H2)
|No
|--
|--

|KafkaSQL
|Yes
|Direct Kafka publishing
|None (built-in)

|GitOps
|No
|--
|--

|KubernetesOps
|No
|--
|--
|===

[role="_additional-resources"]
.Additional resources
* {registry-config-reference}
* link:https://debezium.io/documentation/reference/stable/transformations/outbox-event-router.html[Debezium Outbox Event Router documentation]
