> For the complete documentation index, see [llms.txt](https://docs.openiam.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.openiam.com/administration-guide/operations/audit-and-export.md).

# Audit export to a SIEM

Stream audit events to Splunk, Sentinel, QRadar or any syslog collector through an audit export connector, and prove the events arrive.

## What this is, and when you need it

An **audit export connector** pushes audit events to an external system as they are written. It is the only supported way to get OpenIAM audit data out continuously, and — given that the audit indexes have no database source — the only practical way to retain events beyond the life of your OpenSearch cluster.

Two boundaries decide most designs before you start: only **parent** events are exported, never their children, and export is real-time only on this release. Finding one individual event is [Find audit evidence](/administration-guide/operations/find-audit-evidence.md).

## At a glance

|                          |                                                                                                                                                                                                                                                            |
| ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **You will end up with** | One enabled audit export connector whose events arrive at the destination and reconcile by audit log id and timestamp against `/webconsole/viewAuditLog`. While it is enabled, every matching parent audit event leaves the installation                   |
| **Where**                | **Administration → Audit Export Connectors**                                                                                                                                                                                                               |
| **You need first**       | A reachable collector — for syslog, one on a trusted segment — the audit action constants you want exported, and the **Audit Export Connectors** menu (`AUDIT_EXPORT_CONNECTORS_MENU`, display order 16 under **Administration**, shipped from 2026.3.2.0) |
| **Takes**                | 20–30 minutes, including the five-minute cache sweep you wait out after enabling                                                                                                                                                                           |
| **Reversible**           | Partly — clearing **Enabled** takes the connector out of the dispatch path at the next cache sweep, but nothing that has already reached the destination can be recalled                                                                                   |

## Before you begin

Decide the action list first: an empty **Actions** list exports every parent event, which on a busy installation is a great deal of traffic to a UDP socket or an HTTP endpoint. Have the collector's hostname and port ready, and read [Change configuration safely](/administration-guide/start/change-safely.md) — an enabled connector is a continuous outbound flow of audit data, not a one-off change.

## Create a connector and prove it works

The example is a syslog connector for entitlement changes; a webhook connector differs only in the transport fields.

{% stepper %}
{% step %}

### Create it disabled, with a narrow action list

**Administration → Audit Export Connectors → New**. Name it `SIEM relay – entitlements`, **Connector Type** `Syslog`, leave **Enabled** clear. Under **Actions**, select exactly the constants you need — for entitlement change: `ADD_USER_TO_ROLE`, `DELETE_USER_FROM_ROLE`, `ADD_USER_TO_GROUP`, `DELETE_USER_FROM_GROUP`, `ADD_USER_TO_RESOURCE`, `DELETE_USER_FROM_RESOURCE`, `ENTITLEMENT_BULK_OPERATION`. Leaving the list empty exports every parent event.
{% endstep %}

{% step %}

### Point it at a local relay, not the SIEM

{% hint style="warning" %}
Syslog export is plain UDP with no TLS and no authentication, so a wrong hostname puts audit records on the network in clear. Check the hostname twice, and keep the first destination on a trusted segment.
{% endhint %}

Under **Syslog Settings**, set **Hostname** to your relay on the same segment, **Port** `514`, **Facility** `LOCAL0`, **Severity** `INFORMATIONAL`, **Ident** `openiam`. Set **Message Order** to `action,principal,result,datetime,clientip` — the fifth token is supported but not in the default order.
{% endstep %}

{% step %}

### Save, then enable, then wait five minutes

Save. Enable. The connector is picked up when `AuditExportConnectorCache` next sweeps, at `org.openiam.audit.export.cache.refresh.ms` = 300,000 ms. Do not conclude it is broken before then.
{% endstep %}

{% step %}

### Generate exactly one event and reconcile it

Grant one role to `<TEST_USER>`. Confirm the `ADD_USER_TO_ROLE` event exists at `/webconsole/viewAuditLog`, note its id and timestamp, then find the matching datagram on the relay. The record will look like `Action:[ADD_USER_TO_ROLE] Principal:[<requester>] Result:[SUCCESS] DateTime:[…] ClientIP:[…]` prefixed with `<PRI>openiam:` .
{% endstep %}

{% step %}

### Prove the failure path

Stop the relay. Grant another role. Wait past two retry intervals — roughly ten minutes — then open the connector and check **Failed Dispatches** for a row naming that audit log id. Restart the relay and use **Retry**. Then check whether the relay received zero, one or two copies across the window: retry is at-least-once, and a UDP datagram that was actually delivered but appeared to fail cannot be distinguished.
{% endstep %}
{% endstepper %}

## Verification

| Evidence                                                        | Meaning                                                                                                                                                                                   |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| *"AuditExportConnectorCache refreshed: N connector(s)"* (debug) | N enabled connectors are live. A newly enabled connector missing here has not been picked up yet                                                                                          |
| The destination record                                          | The only proof of export. Reconcile by audit log id and timestamp, not by count alone                                                                                                     |
| A row in `AUDIT_EXPORT_FAILED_DISPATCH`                         | Three attempts failed, and `FAILURE_REASON` carries the cause. *"Could not dispatch to audit export connectors"* in the `audit-log-manager` log means it never reached a connector at all |

## When it goes wrong

| Symptom                                                         | Inspect                                                                             | Fix                                                                                                                                    |
| --------------------------------------------------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| Nothing arrives at the destination and no failures are recorded | `IS_ENABLED`, then the connector cache sweep, then whether `HTTP_ENDPOINT` is blank | Only enabled connectors are loaded; a blank endpoint is a silent no-op; a change takes up to five minutes                              |
| SIEM sees the operation but not the connector's reply           | Whether the missing event has a parent                                              | Only parent events are exported; child events never are, and the `SEND_CHILD_ROWS` column was dropped. Investigate steps in OpenSearch |
| Syslog records arrive with `null` in every field                | `audit-log-manager` log for *"transformer failed, using empty map"*                 | Your Groovy transformer threw. The dispatcher sends the frame anyway                                                                   |
| Webhook returns 401 after an edit                               | Whether the token field was left showing `***`                                      | A masked value is preserved, so an edit does not clear it — but it also means you cannot tell from the screen which token is stored    |
| Failed dispatches accumulating                                  | `FAILURE_REASON` on the newest rows                                                 | Three attempts, five minutes apart, then the row. Fix the destination, then retry each row                                             |
| Retry succeeds but the event is missing from the destination    | Whether the event still exists in `auditlog`                                        | Retry re-reads the source event; a pruned event cannot be re-sent                                                                      |

## Rollback or cleanup

Clear **Enabled** on the connector. Only enabled connectors are loaded, so that takes it out of the dispatch path entirely, and the change applies at the next cache sweep — up to five minutes. Read anything you still need from **Failed Dispatches** before deleting a connector. Nothing that has already reached the destination can be recalled.

## The screen

| Screen                         | Route                                     |
| ------------------------------ | ----------------------------------------- |
| Audit Export Connectors (list) | `/webconsole/audit-export-connectors`     |
| Edit one connector             | `/webconsole/audit-export-connector-edit` |

Both routes render the same screen. The menu resource is `AUDIT_EXPORT_CONNECTORS_MENU`, display order 16 under `ADMIN`, labelled **Audit Export Connectors**, and it ships from 2026.3.2.0 — if the menu item is missing, that is the release to check against.

Everything the connector supports is on that screen: creating one, editing it, deleting it, listing its failed dispatches and retrying one of them.

{% hint style="warning" %}
**Nothing audits the connector's own configuration.** Creating, editing and deleting an audit export connector write no audit event of any kind, so the object that decides which audit events leave the installation is itself the one object whose changes leave no trail. Record connector changes wherever you record configuration changes, and confirm the behaviour on your own release before you rely on it for an assurance argument.
{% endhint %}

## The columns, and the four that were removed

`AUDIT_EXPORT_CONNECTOR` in the `auditlog` schema, after every migration:

| Column                                 | Screen label          | Applies to | Notes                                                                                                        |
| -------------------------------------- | --------------------- | ---------- | ------------------------------------------------------------------------------------------------------------ |
| `ID`                                   | —                     | both       |                                                                                                              |
| `NAME`                                 | Name                  | both       | `varchar(255)`                                                                                               |
| `CONNECTOR_TYPE`                       | Connector Type        | both       | `SYSLOG` or `GENERIC_WEBHOOK`                                                                                |
| `IS_ENABLED`                           | Enabled               | both       | Only enabled connectors are loaded; clearing **Enabled** takes a connector out of the dispatch path entirely |
| `GROOVY_SCRIPT_ID`                     | Transformation Script | both       | Added in 2026.3.3.0 — absent on earlier releases                                                             |
| `HOSTNAME`                             | Hostname              | syslog     | Falls back to the host's own canonical name when blank                                                       |
| `PORT`                                 | Port                  | syslog     | Default **`514`** when null                                                                                  |
| `FACILITY`                             | Facility              | syslog     | A `SysLogFacility` label or numeric code; default **`LOCAL0`**                                               |
| `SEVERITY`                             | Severity              | syslog     | A `SysLogSeverity` name; default **`INFORMATIONAL`**                                                         |
| `IDENT`                                | Ident                 | syslog     | Default **`openiam`**                                                                                        |
| `MESSAGE_ORDER`                        | Message Order         | syslog     | Comma-separated field order; default **`action,principal,result,datetime`**                                  |
| `HTTP_ENDPOINT`                        | HTTP Endpoint URL     | webhook    | A blank endpoint makes `dispatch` a silent no-op                                                             |
| `AUTH_TOKEN`                           | Authentication Token  | webhook    | Masked on read; see below                                                                                    |
| `AUTH_HEADER_NAME`                     | Auth Header Name      | webhook    | Default **`Authorization`**                                                                                  |
| `AUDIT_EXPORT_CONNECTOR_ACTION.ACTION` | Audit Actions         | both       | Zero rows means *all* actions                                                                                |

Five columns from the first release of this feature were **dropped** in 2026.3.3.0: `EXPORT_MODE`, `SEND_CHILD_ROWS`, `SCHEDULE_INTERVAL_MS`, `LAST_EXPORT_TIMESTAMP` and `TLS_VERIFY`. The same release rewrote any connector whose `CONNECTOR_TYPE` was `SPLUNK_HEC` to `GENERIC_WEBHOOK`, which is why a Splunk HEC label still appears in the shipped message bundle with no connector type behind it. If `DESCRIBE AUDIT_EXPORT_CONNECTOR` against the `auditlog` schema still shows those columns, you are on a release before 2026.3.3.0.

{% hint style="warning" %}
Residue from those removals survives in the UI strings and in the properties. A **Real-time** / **Scheduled** mode choice is still declared behind the connector edit screen but is never rendered, and `org.openiam.audit.export.scheduled.sweep.ms`, which ships `60000`, is read by nothing. **There is no scheduled export mode in this release; export is real-time only.** Ignore any label, property or runbook that implies otherwise. The check is the edit screen: there is no mode field on it, and no interval field either.
{% endhint %}

## Parent events only

OpenIAM applies two filters before publishing an event to any connector: the event must have no parent, and its action must be in the connector's action list unless that list is empty.

**Child events are never exported**, and there is no longer a setting to change that — `SEND_CHILD_ROWS` was the dropped column. Your SIEM sees the operation, not its steps: a `PROVISIONING` parent arrives, the `CONNECTOR_RESPONSE` child that says what the target actually did does not. Plan detections accordingly, and keep the OpenSearch index for step-level investigation.

The action filter is inclusive: an empty **Actions** list exports every parent event. On a busy installation that is a lot of traffic to a UDP socket or an HTTP endpoint — name the actions you need.

## The transformer

Both transports ask the connector for an `IdmAuditLogExportTransformer` and send what its `map(doc)` returns. The base class your own script extends is

{% code overflow="wrap" expandable="true" %}

```java
package org.openiam.idm.srvc.audit.groovy;

public abstract class IdmAuditLogExportTransformer {

    @Autowired
    protected CustomJacksonMapper objectMapper;

    public abstract Map<String, Object> map(IdmAuditLogDoc doc);

    public Map<String, String> headers() {
        return Collections.emptyMap();
    }
}
```

{% endcode %}

`headers()` is honoured only by the webhook dispatcher, where its entries are added after the auth header — use it for a tenant id or a correlation header.

With no script selected, the connector sends **the whole document, as a flat map** — every field in the table earlier on this page, nothing dropped and nothing renamed. That is a useful default and a large payload; check what your SIEM does with it before assuming it is acceptable.

Selection is validated on save. OpenIAM instantiates the script at that moment and refuses anything that is not an `IdmAuditLogExportTransformer`, answering *"The Groovy script must extend IdmAuditLogExportTransformer"* — the underlying failure is `CANNOT_INSTANTIATE_GROOVY_CLASS`. A script that fails to compile gives `CHECK_GROOVY_EXCEPTION` instead. Both appear on the connector edit screen when you press **Save**, so a connector that saved cleanly has a script that at least loads.

At runtime, failure is quieter. OpenIAM falls back to the default transformer and logs *"Groovy script `<id>` does not implement IdmAuditLogExportTransformer, falling back to default"* or *"Failed to instantiate transformer for groovyScriptId=…"*. And if `map(doc)` throws at dispatch time, both dispatchers substitute an **empty map** and log *"transformer failed, using empty map"*. The syslog dispatcher then still sends a message — with `null` in every field. A stream of `Action:[null] Principal:[null]` records means a throwing transformer, not a broken source event.

The connector cache refreshes on `org.openiam.audit.export.cache.refresh.ms`, shipped **`300000`** ms, so a connector change — including enabling one — takes up to five minutes.

## Syslog transport

{% hint style="danger" %}
**Syslog export is plain UDP with no TLS, no authentication and no acknowledgement.** OpenIAM opens a plain UDP socket, builds a BSD-syslog frame by hand as `<PRI>` + `IDENT` + `": "` + message + a trailing `NUL`, and sends one datagram . `TLS_VERIFY` was dropped from the schema and there is no TCP or TLS option. A send failure that the OS reports raises `IOException` and is logged as *"error sending message"*; a datagram lost in the network is invisible to OpenIAM and will not appear in failed dispatches.

Use it only to a collector on a trusted segment — a local rsyslog or Splunk forwarder that relays over TLS. Do not point it across an untrusted network, and do not treat it as a chain-of-custody transport.
{% endhint %}

The message body is assembled field by field in the order given by `MESSAGE_ORDER`:

| `MESSAGE_ORDER` token | Rendered as     | Source field                                                                                                           |
| --------------------- | --------------- | ---------------------------------------------------------------------------------------------------------------------- |
| `action`              | `Action:[…]`    | `action`                                                                                                               |
| `principal`           | `Principal:[…]` | `principal`                                                                                                            |
| `result`              | `Result:[…]`    | `result`                                                                                                               |
| `datetime`            | `DateTime:[…]`  | `timestamp`, falling back to *now* if the key is absent                                                                |
| `clientip`            | `ClientIP:[…]`  | `clientIP`, falling back to `clientip` — the comment notes Jackson serialises the field as `clientIP`                  |
| anything else         | `<token>:[…]`   | direct key lookup in the transformer's map, skipped when null — this is how a Groovy transformer injects custom fields |

The priority byte is `(facility & 0x03F8) | severity`, resolved from the `FACILITY` and `SEVERITY` columns; an unrecognised facility label falls back to a numeric parse and then to `LOCAL0`, an unrecognised severity to `INFORMATIONAL`. An unresolvable `HOSTNAME` aborts that dispatch with *"cannot resolve hostname"* and no retry.

## Webhook transport

The transformer's map is serialised as JSON and posted to `HTTP_ENDPOINT` as `Content-Type: application/json`, with `AUTH_HEADER_NAME` (default `Authorization`) set to `AUTH_TOKEN` when present, plus any headers the transformer supplies. **A non-2xx response throws**, which is what feeds the retry chain:

{% code overflow="wrap" %}

```
HTTP connector [<id>]: POST to <endpoint> returned non-2xx status <status>
```

{% endcode %}

A blank `HTTP_ENDPOINT` returns immediately without dispatching or recording a failure.

The auth token is masked on read, and the save handler preserves it: if the incoming bean's token contains `***` and a stored connector exists, the stored value is kept. So editing a connector without touching the token field is safe — but a token you *intend* to contain `***` cannot be set through this screen at all, and nothing warns you that the value you typed was discarded.

## Retry, and the failed-dispatch list

Dispatch is asynchronous. OpenIAM publishes one message per document per enabled connector onto `AuditExportDispatchQueue`, and a listener does the sending. Retry semantics:

| Behaviour       | Value                                                                              |
| --------------- | ---------------------------------------------------------------------------------- |
| Maximum retries | **2**, not configurable                                                            |
| Retry delay     | `org.openiam.audit.export.retry.interval`, shipped **`300000`** ms                 |
| Retry mechanism | The request is re-published with `numRetries` incremented, scheduled for the delay |
| On exhaustion   | A row in `AUDIT_EXPORT_FAILED_DISPATCH`                                            |

So a failing destination gets three attempts in total, roughly ten minutes apart, before the event is recorded as failed. Each failure logs *"Dispatch failed for connector \[id] auditLogId \[id]: …"*.

`AUDIT_EXPORT_FAILED_DISPATCH` columns, and their labels in the **Failed Dispatches** section of the edit screen:

| Column                                                     | Label          |
| ---------------------------------------------------------- | -------------- |
| `AUDIT_LOG_ID` (255, not null)                             | Audit Log ID   |
| `FAILURE_REASON` (2000, silently truncated at that length) | Failure Reason |
| `FAILED_AT` (datetime, not null)                           | Failed At      |
| `RETRY_COUNT` (int, not null, default 0)                   | Retry Count    |
| `CONNECTOR_ID` (FK)                                        | —              |

Manual retry is the **Retry** action on the row. Because the audit event itself is still in the `auditlog` index, a retry re-reads and re-sends it — which also means a retry after the event has been pruned cannot succeed.

{% hint style="warning" %}
Nothing alerts on the failed-dispatch table. Watch the **Failed Dispatches** section of the connector's edit screen, or the `AUDIT_EXPORT_FAILED_DISPATCH` row count, and treat growth as an incident — every row is an audit event your SIEM does not have. And because syslog is UDP, a syslog connector can lose events without producing a single failed-dispatch row.
{% endhint %}

## Related

* [Find audit evidence](/administration-guide/operations/find-audit-evidence.md) — the record model, the search parameters and retention
* [Audit actions and retention](/administration-guide/reference/audit-actions-and-retention.md) — the 537 action constants to choose from
* [Raise a ServiceNow incident from an approval](/administration-guide/requests/approval-flows/raise-a-servicenow-incident.md) — the outbound side, and the ServiceNow incident field mapping
* [What a backup and restore must cover](/administration-guide/platform/backup-and-restore-scope.md) — why a database backup does not cover audit

*Applies to OpenIAM 2026.8.1. Last reviewed 7 August 2026.*


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation by asking a question.

Perform an HTTP GET request on the following URL with the `ask` and `goal` query parameters:

```
GET https://docs.openiam.com/administration-guide/operations/audit-and-export.md?ask=<question>&goal=<user_goal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is what the user is ultimately trying to achieve, the reason they need the answer. Sharing it helps GitBook give you a better, more relevant answer. A goal is most helpful when it describes the outcome the user wants rather than restating the question. For example, with `ask=how do I create an API token`, a goal like `automate deployments from our CI pipeline` lets GitBook tailor the answer to that use case.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
