Skip to content

JSON Serializer

The default serializer. Human-readable, universally supported.

Configuration

kete.routes.<name>.serializer.kind=json

Example Output

User Event

{
  "id": "a7c2f8e1-4b3d-4a9e-8f7c-2d1e5b9a3c4f",
  "time": 1704816000000,
  "type": "LOGIN",
  "realmId": "master",
  "realmName": "master",
  "clientId": "my-app",
  "userId": "550e8400-e29b-41d4-a716-446655440000",
  "sessionId": "abc-session-123",
  "ipAddress": "192.168.1.100",
  "error": null,
  "details": {
    "username": "john.doe",
    "remember_me": "true"
  }
}

Admin Event

{
  "id": "b9d3e7f2-5c4e-4b1f-9a8d-3e2f6c1b4a5e",
  "time": 1704816000000,
  "realmId": "master",
  "realmName": "master",
  "authDetails": {
    "realmId": "master",
    "realmName": "master",
    "clientId": "admin-cli",
    "userId": "admin-uuid",
    "ipAddress": "192.168.1.100"
  },
  "resourceType": "USER",
  "operationType": "CREATE",
  "resourcePath": "users/user-uuid",
  "representation": "{\"username\":\"newuser\"}",
  "error": null,
  "resourceTypeAsString": "USER"
}

Schemas

JSON Schema (draft 2020-12) files describing both message shapes are in the schemas/json/ directory. Use them to validate or generate types for messages on the consumer side (e.g. ajv, jsonschema, json-schema-validator). The eventkind message header (x-eventkind for HTTP and SOAP) tells you which schema applies: EVENT or ADMIN_EVENT.

Event

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/FortuneN/kete/develop/schemas/json/event.json",
  "title": "Event",
  "description": "Keycloak user event as produced by the KETE JSON serializer (message header eventkind = EVENT; x-eventkind for HTTP and SOAP)",
  "type": "object",
  "properties": {
    "id": {
      "type": ["string", "null"],
      "description": "Unique event identifier (UUID)"
    },
    "time": {
      "type": "integer",
      "description": "Event timestamp in milliseconds since Unix epoch (0 when unset)"
    },
    "type": {
      "type": ["string", "null"],
      "description": "Event type name (e.g. LOGIN, LOGOUT, REGISTER, CODE_TO_TOKEN, REFRESH_TOKEN, …)"
    },
    "realmId": {
      "type": ["string", "null"],
      "description": "Keycloak realm ID"
    },
    "realmName": {
      "type": ["string", "null"],
      "description": "Keycloak realm name"
    },
    "clientId": {
      "type": ["string", "null"],
      "description": "OAuth2/OIDC client ID that triggered the event"
    },
    "userId": {
      "type": ["string", "null"],
      "description": "Keycloak user ID (UUID)"
    },
    "sessionId": {
      "type": ["string", "null"],
      "description": "Keycloak session ID"
    },
    "ipAddress": {
      "type": ["string", "null"],
      "description": "IP address of the client"
    },
    "error": {
      "type": ["string", "null"],
      "description": "Error code when the event represents a failure (null on success)"
    },
    "details": {
      "type": ["object", "null"],
      "description": "Arbitrary key-value pairs with extra context (e.g. username, redirect_uri, response_type)",
      "additionalProperties": {
        "type": "string"
      }
    }
  },
  "required": ["id", "time", "type", "realmId", "realmName", "clientId", "userId", "sessionId", "ipAddress", "error", "details"]
}

Admin Event

{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/FortuneN/kete/develop/schemas/json/admin_event.json",
  "title": "AdminEvent",
  "description": "Keycloak admin event as produced by the KETE JSON serializer (message header eventkind = ADMIN_EVENT; x-eventkind for HTTP and SOAP)",
  "type": "object",
  "properties": {
    "id": {
      "type": ["string", "null"],
      "description": "Unique event identifier (UUID)"
    },
    "time": {
      "type": "integer",
      "description": "Event timestamp in milliseconds since Unix epoch (0 when unset)"
    },
    "realmId": {
      "type": ["string", "null"],
      "description": "Keycloak realm ID"
    },
    "realmName": {
      "type": ["string", "null"],
      "description": "Keycloak realm name"
    },
    "authDetails": {
      "description": "Details about the admin who performed the action",
      "oneOf": [
        { "type": "null" },
        { "$ref": "#/$defs/AuthDetails" }
      ]
    },
    "resourceType": {
      "type": ["string", "null"],
      "description": "Resource type name (e.g. USER, CLIENT, REALM, ROLE, GROUP, …); CUSTOM when Keycloak does not recognise the raw value — see resourceTypeAsString"
    },
    "resourceTypeAsString": {
      "type": ["string", "null"],
      "description": "Raw resource type string as recorded by Keycloak (same as resourceType unless resourceType is CUSTOM)"
    },
    "operationType": {
      "type": ["string", "null"],
      "description": "Operation performed (CREATE, UPDATE, DELETE, ACTION)"
    },
    "resourcePath": {
      "type": ["string", "null"],
      "description": "Path to the affected resource within the realm (e.g. users/uuid)"
    },
    "representation": {
      "type": ["string", "null"],
      "description": "JSON representation of the resource state after the operation (only when include-representation is enabled)"
    },
    "error": {
      "type": ["string", "null"],
      "description": "Error code when the event represents a failure (null on success)"
    },
    "details": {
      "type": ["object", "null"],
      "description": "Arbitrary key-value pairs with extra context; only present when KETE runs on Keycloak 26.0 or later",
      "additionalProperties": {
        "type": "string"
      }
    },
    "resourceId": {
      "type": ["string", "null"],
      "description": "Segment after the last '/' of resourcePath; only present when KETE runs on Keycloak 26.4 or later"
    }
  },
  "required": ["id", "time", "realmId", "realmName", "authDetails", "resourceType", "resourceTypeAsString", "operationType", "resourcePath", "representation", "error"],
  "$defs": {
    "AuthDetails": {
      "title": "AuthDetails",
      "description": "Identity of the admin who triggered the event",
      "type": "object",
      "properties": {
        "realmId": {
          "type": ["string", "null"],
          "description": "Realm ID of the authenticating admin"
        },
        "realmName": {
          "type": ["string", "null"],
          "description": "Realm name of the authenticating admin"
        },
        "clientId": {
          "type": ["string", "null"],
          "description": "Client ID used by the admin (e.g. security-admin-console, admin-cli)"
        },
        "userId": {
          "type": ["string", "null"],
          "description": "Keycloak user ID of the admin (UUID)"
        },
        "ipAddress": {
          "type": ["string", "null"],
          "description": "IP address of the admin client"
        }
      },
      "required": ["realmId", "realmName", "clientId", "userId", "ipAddress"]
    }
  }
}

Null Fields

Every property is always present. Unset fields serialize as null (time is a primitive and serializes as 0 when unset), so consumers can distinguish "not set" from "empty string".

Keycloak Version Differences

Messages are serialized straight from Keycloak's own Event and AdminEvent classes, so the exact set of properties depends on the Keycloak version KETE runs in:

Property Message Available since
details Admin Event Keycloak 26.0
resourceId Admin Event Keycloak 26.4

The schemas describe these as optional properties. Consumers should ignore properties they do not recognise so that future Keycloak versions do not break them.