Event processing platform

Turn incoming events into reliable actions.

Tengen accepts business events over HTTP or RabbitMQ, evaluates them against configurable rules, and delivers durable webhook actions. This guide covers the complete console workflow from the first event to production operations.

i
How to use this guide. Follow the sections in order for a first setup. The API reference is intentionally last so the product workflow stays the primary path. For AI-assisted queries, use the Tengen LLM reference.

01 / Start here

Getting started

Run the supporting services, start the two application layers, and create the first event-processing workflow.

01

Start infrastructure

PostgreSQL and the optional RabbitMQ broker run in Docker.

02

Start Tengen

Run the Spring Boot backend and Next.js admin console separately.

03

Build a workflow

Create an API key, create a rule, then send a JSON event.

Prerequisites

  • Docker with Docker Compose
  • Java 21
  • Node.js and npm

Start local dependencies

docker compose -f tengen/docker-compose.yml --profile rabbitmq up -d db rabbitmq

Start the backend

cd tengen
./mvnw spring-boot:run

Start the frontend

cd frontend
npm ci
npm run dev
ServiceLocal addressPurpose
Admin consolehttp://localhost:3000Rules, events, deliveries, replay, connectors, keys, and settings.
Backend APIhttp://localhost:8080Event ingestion and admin API.
RabbitMQ AMQPlocalhost:5672Optional broker connection.
RabbitMQ managementhttp://localhost:15672Local broker administration; defaults are tengen / tengen.
!
The development admin login defaults to admin / admin. Change ADMIN_PASSWORD, JWT_SECRET, and WEBHOOK_SIGNING_SECRET before using the application outside local development.

First workflow

  1. Sign in. Open the admin console and use the configured admin credentials.
  2. Create an ingestion key. Open API Keys, choose a name, and optionally scope event types and sources.
  3. Create a rule. Start with a condition rule and use data.amount >= 1000 as the condition.
  4. Send an event. Use the HTTP example in the next section with the raw key shown once at creation time.
  5. Inspect the result. Use Events to see the accepted event, rule outcome, event-time status, and any webhook delivery.

02 / Understand the model

Core concepts

Everything in Tengen is organized around an event envelope, a rule revision, and a durable action outcome.

The event envelope

FieldRequiredMeaning
typeYesBusiness event name, such as payment or login.failed.
sourceYesProducer or domain name, such as billing or identity.
timestampNoEvent time as an ISO-8601 timestamp. If omitted, Tengen uses the current time.
dataYesJSON object containing business fields used by conditions, aggregates, grouping, and sequence correlation.

Example event

{
  "type": "payment",
  "source": "billing",
  "timestamp": "2026-08-04T14:30:00Z",
  "data": {
    "amount": 2500,
    "currency": "PHP",
    "country": "PH",
    "userId": "user-123"
  }
}

What happens to an accepted event?

1Authorize

HTTP requests use an API key. RabbitMQ uses the API key saved on the connector.

2Classify time

Tengen compares the event time with a durable watermark for its type and source.

3Persist

The event and its origin are saved before the processing result is returned.

4Evaluate

Active, valid rules evaluate the event, aggregate state, sequence progress, or absence state.

5Queue actions

Eligible webhooks are written to a durable outbox and delivered by a worker.

6Trace

Event Explorer records matches, queued actions, suppressions, and linked delivery records.

03 / Find your way around

Console tour

The sidebar keeps the main operational areas in one place. Use the screenshots as a quick visual map.

Rules, Run Test, Event Explorer, and Settings screens
Rules, testing, Event Explorer, and settings.
Rule creation, test result, event detail, and delivery detail screens
Rule creation, test results, event details, and delivery details.
Deliveries, replay, RabbitMQ connector, and API key screens
Deliveries, replay, RabbitMQ connector, and API keys.
Replay detail, RabbitMQ configuration, history, and login screens
Replay detail, RabbitMQ configuration, history, and login.

04 / Ingest events

HTTP event ingestion

Use POST /api/events when an application or service can make an HTTP request for each event.

Before sending an event

  1. Open API Keys and choose New API Key.
  2. Give the key a recognizable name.
  3. Optionally enter comma-separated allowed event types and sources. Leave a field blank to allow all values for that dimension.
  4. Choose Compact summary for normal producers or Full details when the producer needs aggregate and sequence results.
  5. Copy the raw tg_... key immediately. It is shown only once; Tengen stores only its hash.

Send a payment event

curl -X POST http://localhost:8080/api/events \
  -H "Content-Type: application/json" \
  -H "X-API-Key: tg_your_raw_key" \
  -H "Idempotency-Key: payment-123" \
  -d '{
    "type": "payment",
    "source": "billing",
    "timestamp": "2026-08-04T14:30:00Z",
    "data": {
      "amount": 2500,
      "country": "PH"
    }
  }'

Producer response

Every accepted request returns HTTP 200. The response mode belongs to the API key, so processing is always complete even when the producer receives a compact projection.

Compact response

{
  "status": "accepted",
  "matched": true,
  "rules": ["large-payment"],
  "queuedRules": [],
  "suppressedRules": [],
  "eventTimeStatus": "ON_TIME"
}

Full response

{
  "event": { "type": "payment", "source": "billing", "data": { "amount": 2500 } },
  "status": "accepted",
  "matched": true,
  "rules": ["large-payment"],
  "queuedRules": ["large-payment"],
  "aggregates": {},
  "sequences": {},
  "suppressedRules": [],
  "eventTimeStatus": "ON_TIME"
}

Safe retries with Idempotency-Key

Use one idempotency key for each logical event. The key is scoped to the API key that sent it.

SituationResult
Same key and equivalent payloadThe original completed response is returned. The event is not processed twice.
Same key with a different payload409 Conflict; the key cannot be reused for another event.
Same key while the first request is processing409 Conflict; retry after the first request completes.
New requestX-Idempotency-Replayed: false.
Completed replayX-Idempotency-Replayed: true.
i
API-key scope is checked after authentication. A missing or invalid key returns 401; an authenticated key used outside its allowed event type or source scope returns 403.

HTTP ingestion guardrails

  • Request bodies are limited by INGESTION_MAX_BODY_BYTES (1 MiB by default).
  • Each API key is rate-limited by INGESTION_RATE_LIMIT_PER_MINUTE (600 by default). A limited request returns 429 and a Retry-After header.
  • Event timestamps too far into the future are rejected. The default maximum future skew is five minutes.
  • type and source are required; data must be a JSON object.

05 / Ingest events

RabbitMQ ingestion

Use the RabbitMQ connector when producers already publish to a broker or when ingestion should be asynchronous and decoupled from the producer request.

!
Tengen manages one backend consumer for one existing input queue. The browser never connects to RabbitMQ. The queue and dead-letter exchange must exist before you test the connector.

Local broker and topology

docker compose -f tengen/docker-compose.yml --profile rabbitmq up -d db rabbitmq

For the local Compose broker, the AMQP port is 5672, the management UI is 15672, and the default credentials are tengen / tengen.

tengen.input       --events------> tengen.events
tengen.dead-letter --dead-letter-> tengen.events.dlq
Use direct exchanges and durable queues. The names are examples; the connector accepts your configured names.

Configure the connector

  1. Open API Keys and create an active key whose allowed event types and sources cover broker messages.
  2. Open Connectors and choose RabbitMQ.
  3. Enter the broker connection, queue, dead-letter, API-key, and retry settings described below.
  4. Choose Save draft. Tengen increments the configuration version and clears the previous test result.
  5. Choose Test connection. The test checks credentials, virtual-host access, the input queue, and the dead-letter exchange.
  6. Choose Enable consumption. Enabling is blocked until the current saved version has a successful test.
Connector fieldWhat to enter
Display nameA label for the connector.
Host / PortBroker address and AMQP port. Use localhost when the backend runs on the host; use the Compose service name when the backend runs in Docker.
Virtual hostUsually / for the local broker.
TLS enabledEnable when the broker requires TLS.
Username / PasswordBroker credentials. The password is write-only in the UI and encrypted before storage.
Existing input queueThe durable queue Tengen consumes.
Dead-letter exchange / routing keyThe route used for malformed, permanently invalid, or exhausted messages.
Ingestion API keyAn active Tengen API key. Its event type/source policy applies to RabbitMQ messages.
Maximum body bytesPer-message body limit, from 1 byte to 10 MiB.
Retry attempts1 to 20 attempts for transient processing failures.
Retry initial delay / multiplier / maximum delayBackoff controls for transient processing failures.

Message contract

Publish persistent UTF-8 JSON with content_type=application/json and a unique AMQP message_id. The body uses the same event envelope as HTTP ingestion.

{
  "type": "payment",
  "source": "billing",
  "timestamp": "2026-08-04T14:30:00Z",
  "data": { "amount": 2500, "country": "PH" }
}
Message conditionConnector behavior
Valid message and new message IDPersist, evaluate, record the RabbitMQ origin, then acknowledge.
Repeated message IDUse the durable receipt to deduplicate and acknowledge without creating another event.
Missing ID, invalid JSON, wrong content type, body too large, or scope rejectionClassify as permanent, publish to the configured dead-letter route after confirmation, then acknowledge the original.
Transient processing failureRetry with the configured backoff. After retries are exhausted, publish to dead letter and acknowledge.
Dead-letter publish cannot be confirmedPause the connector so a message is not acknowledged without a confirmed dead-letter copy.

RabbitMQ watermark header

Watermark processing is enabled by default. To explicitly opt out for one message, set the AMQP header below:

x-tengen-watermark: false
  • Missing, malformed, or non-false values keep watermark processing enabled.
  • An opted-out message is still API-key validated, persisted, and evaluated.
  • An opted-out message does not create or update a watermark and has no event-time lateness classification.
  • This header is a processing hint, not a security control.

06 / Stream behavior

Event time and lateness

Tengen keeps a durable watermark per event type and source so out-of-order events can be accepted without moving rule state backwards.

How classification works

The watermark is based on the highest observed event time minus the allowed lateness period. The default grace period is 300 seconds, controlled by INGESTION_ALLOWED_LATENESS_SECONDS.

  • ON_TIME: the event is at or beyond the current stream progress.
  • LATE_ACCEPTED: the event is older than the current maximum but still newer than the watermark.
  • TOO_LATE: the event is at or before the watermark.
ON_TIMEPersisted and evaluated normally.
LATE_ACCEPTEDPersisted and evaluated; watermark stays monotonic.
TOO_LATEPersisted and accepted, but no rule matches, state changes, or webhook actions.
i
Absence deadlines close against the expected event stream watermark. When a stream is idle, the absence worker can advance that route using wall-clock progress so pending expectations do not wait forever for another event.

07 / Build rules

Rule builder

Rules are named, versioned configurations that turn event patterns into log records or webhook actions.

Common creation workflow

  1. Open Rules and choose New Rule.
  2. Enter a unique rule name. Rule names are limited to 100 characters.
  3. Choose one of CONDITION, AGGREGATE, SEQUENCE, or ABSENCE.
  4. Choose an action: LOG records the match only; WEBHOOK also creates a delivery intent.
  5. Fill in the type/source, conditions, windows, grouping, threshold, and webhook options required by the selected rule type.
  6. Leave Active selected when the rule should start evaluating immediately, or save it inactive for review.
  7. Choose Save Rule. Tengen validates the complete rule before it can execute.

Shared rule fields

FieldUsed byGuidance
NameAllUnique human-readable identifier.
ActionAllLOG or WEBHOOK.
Event type / sourceCondition, aggregate, absence startExact string pre-filter before the Aviator condition runs.
ConditionCondition, aggregate, absence startVisual Builder or raw Aviator expression.
ThresholdAggregateThe rule matches when the calculated value is greater than or equal to this number.
Window (seconds)Aggregate, sequence, absenceEvent-time window. It must be positive for these rule types.
Group by fieldAggregate, sequence, absenceOptional dotted path such as data.userId. Blank means global state.
ActiveAllOnly active, non-archived, valid rules participate in live evaluation.

Conditions: Visual Builder or Raw Aviator

The visual builder creates an Aviator expression. Use groups to combine conditions with AND or OR. Each leaf has a field, operator, and value.

Supported operators in the builder

==equals !=not equal >greater than >=greater or equal <less than <=less or equal containsstring contains not containsstring does not contain

Useful field paths

typesourcetimestampdata.amountdata.countrydata.currencydata.statusdata.userId

The event environment also exposes the complete data object, so raw Aviator can reference other fields.

data.amount >= 1000 && data.country == 'PH'

Raw expressions are validated by Aviator before the rule is saved or activated. The default maximum expression length is 10,000 characters.

08 / Rule type

Condition rules

A condition rule matches one event after its exact type/source pre-filter and Aviator expression both pass.

A
Use it for: high-value payments, suspicious countries, failed status values, or any single-event alert.
Configure: event type, source, condition, action, and optional webhook settings.

Example: high-value payment

FieldValue
Namelarge-payment
Rule typeCONDITION
Event typepayment
Sourcebilling
Conditiondata.amount >= 1000 && data.currency == 'PHP'
ActionWEBHOOK or LOG

09 / Rule type

Aggregate rules

An aggregate rule calculates a value over a moving event-time window and matches when that value reaches its threshold.

COUNTnumber of matching events
SUMtotal numeric field value
AVGaverage numeric field value
MINminimum numeric field value
MAXmaximum numeric field value

How to create one

  1. Choose AGGREGATE as the rule type.
  2. Set the event type, source, and optional condition that selects events for the aggregate.
  3. Choose an aggregate function. COUNT does not need an aggregate field; the other functions require a numeric dotted path such as data.amount.
  4. Set the window in seconds and the threshold. The current event is included in the calculation.
  5. Optionally set Group by field to maintain separate totals per user, account, device, or order.
  6. Choose ONCE_PER_WINDOW when a webhook should be reserved once per fixed event-time bucket rather than once for every matching event.

Example: five failed logins

Rule type:     AGGREGATE
Event type:    login.failed
Source:        identity
Aggregate:     COUNT
Window:        300 seconds
Threshold:     5
Group by:      data.userId

Window semantics

The lower time boundary is excluded and the current event is included. A sum, average, minimum, or maximum ignores events whose aggregate field is not numeric. A grouped aggregate with no usable group key does not produce a grouped match.

10 / Rule type

Sequence rules

A sequence rule detects two to five ordered event steps completed within one event-time window.

Configure the sequence

  1. Choose SEQUENCE as the rule type.
  2. Keep the default two steps or add steps up to a maximum of five. Use the arrows to reorder them.
  3. For every step, enter an event type, source, and condition.
  4. Set the sequence window in seconds.
  5. Optionally set one shared group-by path, such as data.orderId or data.userId. Every step must resolve to the same value for correlation.
  6. Use LOG or WEBHOOK. Sequence webhooks use EVERY_MATCH; the edge and once-per-window trigger modes are not available for sequence rules.

Example: order workflow

1order.createdsource: checkout, condition: data.orderId != nil
2payment.completedsource: billing, condition: data.status == 'paid'
3shipment.createdsource: fulfillment, condition: data.orderId != nil

Important behavior

  • Steps are evaluated in order.
  • A single event advances at most one sequence instance.
  • Progress is durable and isolated by rule revision and optional group key.
  • An event that matches a later step cannot skip the expected next step.
  • When the final step completes, the response contains the matched step IDs and times.

11 / Rule type

Absence rules

An absence rule opens an expectation when a starting event matches, then triggers only if the expected event does not arrive before the event-time deadline.

Configure the start

  • Set the starting event type and source.
  • Build the starting condition.
  • Set a positive absence window in seconds.
  • Optionally group by a key such as data.orderId.

Configure the expected event

  • Set the expected event type and source.
  • Build the expected-event condition.
  • When an expected event arrives in order, within the window, and with the same group key, the pending instance is satisfied.
  • When the deadline closes without satisfaction, the instance triggers and may create a webhook.
start matched->expectation pending->satisfiedortriggered
i
Absence rules are finalized by the background absence worker. Their webhook trigger mode is always EVERY_MATCH; use cooldown to reduce repeated delivery when necessary.

12 / Validate safely

Testing rules

The Run Test page simulates rule evaluation without creating live processing side effects.

Single rule

  • Select one saved rule and provide an event JSON document.
  • For a sequence, provide one event JSON document per configured step in order.
  • For an absence rule, provide the starting event and optionally an expected event. Leave the expected event blank to simulate a trigger.
  • Aggregate tests may read persisted events in the window, but the sample event is not saved.

All active rules

  • Choose the all-rules mode to evaluate a sample against every active, non-archived rule.
  • Review per-rule condition, aggregate, sequence, and match outcomes.
  • Testing does not change trigger state, watermarks, absence state, or sequence progress.
  • Testing never sends a webhook, including for rules whose action is configured as LOG or WEBHOOK.

13 / Manage change

Rule lifecycle and history

Rule changes are auditable and revisioned. The current rule is a projection; the revision history is immutable.

+Create

Creates revision 1 after validation.

Update

Creates a new revision and resets runtime state for the changed configuration.

Activate

Only valid, non-archived rules can be activated.

||Deactivate

Stops live evaluation without deleting the rule or its history.

Archive

The delete route archives the rule and deactivates it.

Restore

Copies an older snapshot into a new revision and starts it inactive.

Runtime state reset

Updating, activating, deactivating, archiving, unarchiving, or restoring a rule clears action cooldown/window state and cancels active sequence or pending absence instances for that rule. This prevents a new configuration from inheriting progress from an older revision.

!
When using the API, the rule response includes an ETag containing the current revision. Send If-Match when you want a mutation to fail instead of overwriting a newer change.

14 / Trigger actions

Webhook actions

Webhook actions are asynchronous. Tengen commits the event and the delivery intent before the worker makes the outbound request.

Configure a webhook rule

  1. Choose WEBHOOK in the rule action field.
  2. Enter a callback URL. It must be HTTPS and use a public host/address.
  3. Choose the trigger mode appropriate for the rule.
  4. Optionally set a cooldown in seconds. Cooldown controls repeated delivery, not whether the rule matches.
  5. Save and activate the rule. The delivery worker will process the outbox automatically when enabled.
Trigger modeAvailable forBehavior
EVERY_MATCHAll rule typesQueues one logical webhook for every eligible match.
EDGECondition and aggregate rulesQueues only when the rule changes from not matching to matching. A later non-match resets the edge.
ONCE_PER_WINDOWAggregate rules onlyReserves at most one webhook per fixed event-time window and group. A failed delivery can retry.
CooldownWebhook rulesSuppresses repeated delivery for a configured period per group or globally. It does not change match evaluation.

Callback URL restrictions

Tengen validates the URL when the rule is saved and again before delivery. The destination must:

  • Use https://.
  • Contain a host and no embedded username/password.
  • Contain no URL fragment.
  • Resolve to public addresses. Localhost, loopback, private, link-local, multicast, metadata-service, documentation, and other reserved ranges are rejected.
  • Be reachable without relying on redirects; the client does not follow redirects.

For local development, use a public HTTPS tunnel or disable the worker. http://localhost callbacks are intentionally rejected.

Delivery payload

Each delivery is a JSON document containing the original event and the rule evaluation details available for that match.

{
  "event": {
    "type": "payment",
    "source": "billing",
    "timestamp": "2026-08-04T14:30:00Z",
    "data": { "amount": 2500, "country": "PH" }
  },
  "status": "accepted",
  "matched": true,
  "rules": ["large-payment"],
  "aggregates": {},
  "sequences": {}
}

Aggregate matches add an entry under aggregates. Completed sequences add an entry under sequences. Absence triggers add an entry under absences with the start event, expected event, deadline, group, and triggering watermark.

Signed delivery headers

HeaderMeaning
X-Tengen-Delivery-IdStable outbox delivery ID. Use it for receiver-side deduplication.
X-Tengen-TimestampUnix epoch seconds used for signing.
X-Tengen-Signaturev1= followed by the hexadecimal HMAC-SHA256 signature.

The signature is computed over <timestamp>.<raw-json-body> with WEBHOOK_SIGNING_SECRET as the HMAC key. Verify against the raw request body before parsing or reserializing JSON.

message = X-Tengen-Timestamp + "." + raw_request_body
signature = HMAC_SHA256_HEX(WEBHOOK_SIGNING_SECRET, message)
expected = "v1=" + signature

Delivery lifecycle

PENDING -> PROCESSING -> DELIVERED or RETRY_SCHEDULED -> DEAD_LETTER
  • 2xx responses are successful.
  • Timeouts, connection failures, 408, 429, and 5xx responses are retryable.
  • Permanent failures and exhausted attempts become DEAD_LETTER.
  • The default worker allows eight attempts with exponential backoff capped at 15 minutes.
  • Delivery is at least once. A receiver should deduplicate by X-Tengen-Delivery-Id.

Delivery history

Open Deliveries to filter by status, rule, event, time range, or search text. Open a row to inspect the destination, payload metadata, attempts, status code, error, and timeline. Retry a dead-lettered delivery from its detail view; the same outbox record is reused.

15 / Access control

API keys

API keys authenticate producers and can limit which event types and sources they may send.

Create a key

  1. Open API Keys and choose New API Key.
  2. Enter a descriptive name.
  3. Enter allowed event types and sources as comma-separated lists, or leave them blank for all.
  4. Choose compact or full producer responses.
  5. Copy the raw key from the one-time success message.

Manage a key

  • The table shows the key ID, name, visible prefix, response mode, scope, status, and expiration.
  • Raw keys are never returned after creation. Tengen stores only a SHA-256 hash.
  • Revoke a key to prevent future HTTP or RabbitMQ ingestion.
  • A connector cannot be enabled with an inactive or expired key.

16 / Investigate

Event Explorer

Use Events to find accepted events and follow their processing trace from ingestion to webhook delivery.

Available filters

event IDtypesourceAPI key IDmatchedtrace availabletimingoriginoccurred from/to

List view

The list shows event ID, type, source, ingestion origin, match outcome, timing classification, API key, occurred time, and received time. Use local or UTC display from Settings.

Detail view

Open an event to inspect the original payload, event-time handling, matched-rule outcomes, aggregate or sequence details, absence instances, RabbitMQ connector metadata, and linked webhook deliveries.

i
New events have a durable processing trace. Older records created before event tracing may show an explicit legacy or trace-unavailable state.

17 / Analyze history

Replay and backfill

Replay jobs evaluate historical events against an immutable rule revision for analysis. They are deliberately isolated from live processing.

!
Replay jobs never send webhooks and never change live rule state, watermarks, event traces, or delivery records. They are safe for comparison and investigation.

Create a replay

  1. Open Replays.
  2. Select a rule and an immutable revision.
  3. Select an occurred-time range. The start is inclusive and the end is exclusive.
  4. Optionally filter the input events by API key.
  5. Choose Start replay job. The request is immutable after creation.
Replay capabilityBehavior
Supported rule typesCondition and aggregate revisions. Sequence and absence revisions are not supported by the current replay MVP.
RangeUp to TENGEN_REPLAY_MAX_RANGE_DAYS days, 31 by default.
Materialized outputCapped by TENGEN_REPLAY_MAX_MATERIALIZED_OUTPUT_EVENTS, 10,000 by default.
Aggregate warmupThe worker reads the required preceding window so aggregate values at the requested start are meaningful.
StatusQUEUED, RUNNING, PAUSE_REQUESTED, PAUSED, CANCEL_REQUESTED, COMPLETED, FAILED, or CANCELLED.
ControlsPause, resume, cancel, and retry failed retryable jobs. Retry resumes from the last committed checkpoint.
AuditView progress, outcomes, errors, and immutable transition history.

Replay control requests use the job version in If-Match. Refresh the job before controlling it when another operator may have changed its state.

18 / Operate safely

Settings and security

The application separates producer authentication from administrator sessions and keeps console preferences server-backed.

Console settings

  • Theme: light, dark, or system.
  • Accent: blue, indigo, purple, teal, green, orange, yellow, red, pink, grey, black, or neon.
  • Time display: local time or UTC.
  • Reset: light theme, blue accent, and local time.

Administrator sessions

  • Login exchanges the configured admin credentials for access and refresh JWTs.
  • The Next.js console stores them in httpOnly cookies; browser JavaScript does not read the tokens.
  • Access tokens expire after 15 minutes by default; refresh sessions last 7 days by default.
  • Refresh sessions are rotated and revoked on replay or logout.
  • Login attempts are throttled by IP and username.
i
The frontend proxy attaches the access token as a Bearer token when forwarding admin requests to the backend and refreshes an expired access token once.

19 / Operate safely

Configuration

Use environment variables for deployment behavior. The defaults below match the included local Compose setup.

Sample .env files

Save one of these examples as .env in the repository root before running Docker Compose. The values below are examples; do not commit real credentials or production secrets.

Local development

# .env - local Docker Compose
SPRING_PROFILES_ACTIVE=
ADMIN_USER=admin
ADMIN_PASSWORD=admin
JWT_SECRET=dev-secret-change-me-please-32-bytes-min
WEBHOOK_SIGNING_SECRET=dev-webhook-signing-secret-change-me
CORS_ALLOWED_ORIGINS=http://localhost:3000
WEBHOOK_WORKER_ENABLED=true
TENGEN_ABSENCE_WORKER_ENABLED=true
TENGEN_REPLAY_WORKER_ENABLED=true
DB_PORT=5432
APP_PORT=8080
FRONTEND_PORT=3000
RABBITMQ_AMQP_PORT=5672
RABBITMQ_MANAGEMENT_PORT=15672
RABBITMQ_DEFAULT_USER=tengen
RABBITMQ_DEFAULT_PASS=tengen

# Optional: required before saving a RabbitMQ connector
# TENGEN_CONNECTOR_MASTER_KEY=base64-encoded-random-32-byte-key

Production

# .env - production
SPRING_PROFILES_ACTIVE=prod
ADMIN_USER=admin
ADMIN_PASSWORD=replace-with-a-strong-password
JWT_SECRET=replace-with-a-random-secret-at-least-32-bytes
WEBHOOK_SIGNING_SECRET=replace-with-another-random-secret-at-least-32-bytes
CORS_ALLOWED_ORIGINS=https://console.example.com
WEBHOOK_WORKER_ENABLED=true
TENGEN_ABSENCE_WORKER_ENABLED=true
TENGEN_REPLAY_WORKER_ENABLED=true
RETENTION_ENABLED=true
RETENTION_DAYS=90
TENGEN_CONNECTOR_MASTER_KEY=replace-with-base64-encoded-random-32-byte-key
TENGEN_RABBITMQ_ALLOWED_HOSTS=rabbitmq.internal.example
DB_PORT=5432
APP_PORT=8080
FRONTEND_PORT=3000
i
These examples target the included Docker Compose file. Its app service uses the internal db:5432 hostname and Compose database credentials. When running Spring Boot outside Compose or using an external database, set DB_URL, DB_USER, and DB_PASSWORD in that process environment and update the deployment configuration accordingly.
!
Production startup rejects the development admin password and development JWT/webhook secrets. Generate unique values, keep .env outside version control, and provide an exact TENGEN_RABBITMQ_ALLOWED_HOSTS value when RabbitMQ is enabled.
Core application and security
VariableDefaultPurpose
SPRING_PROFILES_ACTIVEemptyUse prod or production for production startup checks.
DB_URL / DB_USER / DB_PASSWORDlocalhost database / tengen / tengenPostgreSQL connection.
ADMIN_USER / ADMIN_PASSWORDadmin / adminInitial administrator credentials.
JWT_SECRETdevelopment fallbackJWT signing secret. Use a random value of at least 32 bytes in production.
JWT_ISSUER / JWT_AUDIENCEtengen / tengen-adminJWT validation claims.
JWT_ACCESS_TTL_MINUTES / JWT_REFRESH_TTL_DAYS15 / 7Admin access and refresh lifetimes.
CORS_ALLOWED_ORIGINShttp://localhost:3000Allowed browser origins.
LOGIN_MAX_ATTEMPTS / LOGIN_WINDOW_SECONDS5 / 60Login throttling.
REFRESH_REPLAY_GRACE_SECONDS2Refresh-token replay handling grace period.
Ingestion and rule safety
VariableDefaultPurpose
RULE_MAX_EXPRESSION_LENGTH10000Maximum Aviator expression length.
INGESTION_MAX_BODY_BYTES1048576Maximum HTTP event body size.
INGESTION_RATE_LIMIT_PER_MINUTE600Per-API-key HTTP ingestion limit.
INGESTION_MAX_FUTURE_SKEW_SECONDS300Maximum event timestamp skew into the future.
INGESTION_ALLOWED_LATENESS_SECONDS300Event-time grace period used to classify late events.
Webhook worker
VariableDefaultPurpose
WEBHOOK_WORKER_ENABLEDtrueEnable background delivery.
WEBHOOK_WORKER_POLL_INTERVAL_MS / INITIAL_DELAY_MS1000 / 1000Polling and startup delay.
WEBHOOK_WORKER_BATCH_SIZE25Outbox rows claimed per poll.
WEBHOOK_WORKER_MAX_ATTEMPTS8Attempts before dead-lettering.
WEBHOOK_WORKER_BASE_DELAY_MS / MAX_DELAY_MS5000 / 900000Retry backoff base and cap.
WEBHOOK_WORKER_LEASE_DURATION_MS300000Claim lease for restart recovery.
WEBHOOK_WORKER_CONNECT_TIMEOUT_MS / READ_TIMEOUT_MS3000 / 5000Outbound HTTP timeouts.
WEBHOOK_SIGNING_SECRETdevelopment fallbackHMAC secret for signed callbacks. Must be at least 32 characters.
RabbitMQ connector
VariableDefaultPurpose
TENGEN_CONNECTOR_MASTER_KEYemptyBase64-encoded random 32-byte AES-256-GCM key used to encrypt broker passwords.
TENGEN_RABBITMQ_ALLOWED_HOSTSemptyExact comma-separated broker host allowlist; required in production.
TENGEN_RABBITMQ_CONNECTION_TIMEOUT_MS / HANDSHAKE_TIMEOUT_MS5000 / 5000Broker connection and AMQP handshake timeouts.
TENGEN_RABBITMQ_SHUTDOWN_TIMEOUT_MS / DEAD_LETTER_TIMEOUT_MS5000 / 5000Resource shutdown and publisher-confirm timeouts.
TENGEN_RABBITMQ_CONSUMERS / PREFETCH1 / 1Listener concurrency and prefetch. Increase only after load testing.
Replay, absence, retention, and metrics
VariableDefaultPurpose
TENGEN_REPLAY_WORKER_ENABLEDtrueEnable analysis-only replay processing.
TENGEN_REPLAY_WORKER_POLL_INTERVAL_MS / INITIAL_DELAY_MS1000 / 1000Replay worker polling and startup delay.
TENGEN_REPLAY_WORKER_BATCH_SIZE / LEASE_DURATION_MS100 / 300000Replay batch size and restart lease.
TENGEN_REPLAY_MAX_RANGE_DAYS31Maximum requested replay range.
TENGEN_REPLAY_MAX_MATERIALIZED_OUTPUT_EVENTS10000Maximum output events materialized per job.
TENGEN_ABSENCE_WORKER_ENABLEDtrueEnable absence deadline evaluation.
TENGEN_ABSENCE_WORKER_POLL_INTERVAL_MS / INITIAL_DELAY_MS1000 / 1000Absence worker polling and startup delay.
TENGEN_ABSENCE_WORKER_BATCH_SIZE100Absence instances processed per poll.
RETENTION_ENABLED / RETENTION_DAYStrue / 90Cleanup of terminal operational records. Rule revisions are retained.
RETENTION_BATCH_SIZE / RETENTION_SCHEDULE1000 / 03:15 UTC cronCleanup batch size and schedule.

20 / Ship safely

Production checklist

Use the Compose stack as a starting point, but replace every development secret and supply deployment-specific network values.

Minimum production environment

SPRING_PROFILES_ACTIVE=prod
ADMIN_PASSWORD=replace-with-a-strong-password
JWT_SECRET=replace-with-a-random-secret-at-least-32-bytes
WEBHOOK_SIGNING_SECRET=replace-with-another-random-secret
CORS_ALLOWED_ORIGINS=https://your-console.example

If RabbitMQ is used, also configure TENGEN_CONNECTOR_MASTER_KEY and an exact TENGEN_RABBITMQ_ALLOWED_HOSTS list.

Build and start

docker compose --env-file .env -f tengen/docker-compose.yml up --build -d

Add --profile rabbitmq when the RabbitMQ service should start with the stack.

Health

  • /actuator/health/liveness is public.
  • /actuator/health/readiness is public.
  • Other health details require admin authentication.

Metrics

/actuator/prometheus requires admin authentication. Metrics cover ingestion, event-time status, rule evaluation errors, webhook attempts, RabbitMQ messages, absence instances, replay jobs, and control operations.

!
Back up an existing PostgreSQL database before first startup with a version that introduces migrations. Flyway owns schema changes and Hibernate validates the resulting schema.

21 / Reference

Glossary

A short vocabulary guide for the concepts used throughout the console and API.

Event
A JSON business occurrence with type, source, optional event time, and data.
Rule
A named configuration that evaluates events and optionally creates an action.
Revision
An immutable snapshot of a rule configuration at a point in its lifecycle.
Group key
A value resolved from event data that isolates aggregate, sequence, absence, or action state.
Watermark
Durable event-time progress used to classify late events and close absence windows.
Outbox
A committed webhook delivery intent waiting for the background worker.
Dead letter
A terminal delivery or broker-message destination for work that cannot be processed normally.
Replay
An analysis-only evaluation of historical events against an immutable rule revision.

22 / Reference

API reference

The backend listens on http://localhost:8080 locally. The Next.js admin console proxies browser requests through its own /api/ routes; direct backend calls use the paths below.

Authentication model

  • Admin endpoints use Authorization: Bearer <access-token> when called directly.
  • POST /api/auth/login returns an access/refresh token pair. The frontend stores these in httpOnly cookies through its session route.
  • Event ingestion uses X-API-Key, not the admin JWT.
  • Public health probes are the liveness and readiness endpoints. Other API routes are protected.

Get an admin access token

curl -sS -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"admin","password":"admin"}'

Use the returned accessToken in an Authorization header for admin endpoints. Use the returned refreshToken with /api/auth/refresh when the access token expires.

Endpoint index

MethodPathAuthPurpose
Authentication
POST/api/auth/loginPublicExchange admin credentials for access and refresh tokens.
POST/api/auth/refreshPublicRotate a refresh session and issue a new token pair.
POST/api/auth/logoutPublicRevoke a refresh session.
Event ingestion
POST/api/eventsX-API-KeyValidate, persist, evaluate, and report one event. Supports Idempotency-Key.
Rules and rule tests
GET/api/rulesAdmin JWTList non-archived rules. Use ?includeArchived=true for all.
GET/api/rules/{id}Admin JWTRead a rule and its current revision ETag.
POST/api/rulesAdmin JWTCreate and validate a rule.
PUT/api/rules/{id}Admin JWTUpdate a rule; optionally protect with If-Match.
DELETE/api/rules/{id}Admin JWTArchive a rule. It does not physically delete history.
PATCH/api/rules/{id}/toggleAdmin JWTActivate or deactivate a rule.
POST/api/rules/{id}/unarchiveAdmin JWTUnarchive a rule; it remains inactive until activated.
GET/api/rules/{id}/revisionsAdmin JWTList immutable revisions.
GET/api/rules/{id}/revisions/{revision}Admin JWTRead one revision snapshot.
POST/api/rules/{id}/revisions/{revision}/restoreAdmin JWTRestore a snapshot as a new inactive revision.
POST/api/rules/testAdmin JWTRun a side-effect-free single-rule or all-active-rules simulation.
API keys
GET/api/keysAdmin JWTList key metadata; raw secrets are not returned.
POST/api/keysAdmin JWTCreate a scoped producer key and return its raw value once.
POST/api/keys/{id}/revokeAdmin JWTRevoke a producer key.
Event Explorer
GET/api/event-historyAdmin JWTSearch and paginate accepted event summaries.
GET/api/event-history/{id}Admin JWTRead event payload, outcomes, timing, origin, and linked deliveries.
Webhook delivery administration
GET/api/webhook-deliveriesAdmin JWTSearch delivery history by status, rule, event, time, or text.
GET/api/webhook-deliveries/{id}Admin JWTRead one delivery detail.
POST/api/webhook-deliveries/{id}/retryAdmin JWTRetry one dead-lettered delivery.
Replay and backfill
POST/api/replay-jobsAdmin JWTCreate an analysis-only job.
GET/api/replay-jobsAdmin JWTList and filter replay jobs.
GET/api/replay-jobs/{id}Admin JWTRead job status and progress.
GET/api/replay-jobs/{id}/transitionsAdmin JWTRead lifecycle transition audit.
GET/api/replay-jobs/{id}/outcomesAdmin JWTRead paginated event outcomes; filter by matched.
POST/api/replay-jobs/{id}/pauseAdmin JWTPause or request pause using If-Match.
POST/api/replay-jobs/{id}/resumeAdmin JWTResume a paused job.
POST/api/replay-jobs/{id}/cancelAdmin JWTCancel a queued, running, paused, or failed job.
POST/api/replay-jobs/{id}/retryAdmin JWTRetry a failed retryable job from its checkpoint.
RabbitMQ connector
GET/api/connectors/rabbitmqAdmin JWTRead connector configuration and runtime status.
PUT/api/connectors/rabbitmqAdmin JWTSave a connector draft; supports configuration version or If-Match.
POST/api/connectors/rabbitmq/testAdmin JWTTest the current saved connection and topology.
POST/api/connectors/rabbitmq/enableAdmin JWTStart the consumer after a successful current-version test.
POST/api/connectors/rabbitmq/disableAdmin JWTStop consumption while retaining saved configuration.
Console settings
GET/api/settingsAdmin JWTRead persisted theme, accent, and time-display preferences.
PUT/api/settingsAdmin JWTUpdate console preferences.
Actuator
GET/actuator/health/livenessPublicLiveness probe.
GET/actuator/health/readinessPublicReadiness probe.
GET/actuator/healthAdmin JWTAuthenticated health details.
GET/actuator/prometheusAdmin JWTPrometheus metrics.

Paging and filter parameters

EndpointParameters
/api/rulesincludeArchived (default false).
Revision, delivery, replay, and outcome listspage defaults to 0; size defaults to 25. The server validates the maximum for each list.
/api/event-historyeventId, type, source, apiKeyId, matched, traceAvailable, eventTimeStatus, ingestionOrigin (or origin), from, to.
/api/webhook-deliveriesstatus, ruleId, eventId, from, to, search.
/api/replay-jobsstatus, ruleId, ruleRevision, createdBy, jobId, from, to.
/api/replay-jobs/{id}/outcomesmatched filters matched-only or no-match-only outcomes.

Create a condition rule through the API

curl -sS -X POST http://localhost:8080/api/rules \
  -H "Authorization: Bearer $ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "large-payment",
    "ruleType": "CONDITION",
    "action": "LOG",
    "eventType": "payment",
    "source": "billing",
    "conditionScript": "data.amount >= 1000",
    "threshold": 0,
    "active": true,
    "sequenceSteps": []
  }'

For aggregate, sequence, absence, or webhook rules, add the fields described in the rule sections above. Keep the request contract stable: the backend validates the combination of fields for the selected rule type and action.