Every serious security team wants the same thing from a vendor: the audit log, in their SIEM, in real time. Not a weekly export. Not a scheduled sync. A live stream that lands in Splunk or Elastic before the actor has clicked away from the settings page.
Ithura's SIEM streaming ships exactly that. Register an HTTPS webhook and a shared secret in Settings, and every row that gets written to the workspace audit log is POSTed to that URL within milliseconds, HMAC-signed with the same scheme workspace webhooks and agent-dispatch runners use. A receiver you already have for either of those two surfaces can be reused as-is; only the URL changes.
The wire contract, on one line
Ithura POSTs one JSON object per audit event:
POST https://your-siem-ingest.example.com/ithura
Content-Type: application/json
User-Agent: Ithura-SIEM/1.0
X-Ithura-Signature: <hex hmac_sha256(secret, body)>
{
"id": "9f14e5c2-...-b7",
"workspace_id": "1c2d...-52",
"actor_user_id": "e7a9...-b8",
"actor_label": "alice@acme.com",
"action": "workspace.member.invited",
"entity_type": "workspace_member",
"entity_id": "5b02...-1a",
"metadata": { "email": "bob@acme.com", "role": "member" },
"ip_address": "203.0.113.42",
"user_agent": "Mozilla/5.0 ...",
"created_at": "2026-08-03T14:22:07.148Z"
}Verifying it on the receiver is four lines:
# Python receiver
import hashlib, hmac, os
def verify(body: bytes, signature: str) -> bool:
want = hmac.new(os.environ["ITHURA_SIEM_SECRET"].encode(),
body, hashlib.sha256).hexdigest()
return hmac.compare_digest(want, signature)Why fanned out on write, not polled
- Latency. A SOC that sees an event within a few hundred milliseconds of it happening can correlate it with the same authentication burst that produced it. A ten minute polling window loses that context.
- Delivery pressure. Polling requires the customer to keep a job running, remember the cursor, and catch up after downtime. A push contract keeps the state machine on Ithura's side, where we already have workers.
- Same code path as your other webhooks.Signature verification, replay protection, and error handling live in one place in the receiver.
What lands in the stream
Everything that lands in audit_logs lands in the stream. That includes:
- Every SSO sign-in, SCIM provisioning event, session revoke, MFA change.
- Every workspace membership change, role change, invite, removal.
- Every integration created, secret rotated, webhook enabled or disabled.
- Every agent dispatch, agent runner registration, dispatch callback.
- Every settings surface change on the workspace, project, or org.
Field mutations on issues and pages ride a separate activity surface (they would drown the audit channel), and Ithura Cloud traffic itself is not surfaced (see Sovereign for what stays inside the tenant boundary versus what does not).
What is not in v1
- Retention pruning. v1 keeps the audit log forever. A per-workspace "auto-delete rows older than N days" job is next; the compliance argument for keeping streaming and retention as separate primitives is that shipping to a SIEM is often the reason you feel safe pruning the source.
- Deliveries table with retries. v1 fires once, logs failures, does not retry. A deliveries table with retry-with-backoff and a per-workspace success rate widget is planned; the receivers we validated against were all HA already, so the missing retry is fine for a v1.
- Per-scope filters. Right now every event gets streamed. A UI to say "only
workspace.member.*andsecurity.*" is a natural v1.1.
Related
- docs/features/audit-siem, the full wire contract and admin setup.
- docs/tutorials/audit-siem-first-stream, the 15-minute walkthrough that stands up a Python receiver and confirms events arrive with valid signatures.
- docs/sovereign/audit-export, the batch CSV counterpart for auditors who want a point-in-time snapshot rather than a live feed.