The Ithura Blog
Sovereign6 min read

Every audit event, streamed to your SIEM in real time

Ithura now streams every workspace audit event to a customer-owned webhook in real time, HMAC-signed with the same scheme used by workspace webhooks and agent-dispatch runners. Zero polling, zero export window, zero cron. Register a URL in Settings, verify one signature in your receiver, and watch events land in Splunk or Elastic within a few hundred milliseconds of the actor clicking the button.

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.

Three audited events, each POSTed at the moment it happened
TimeEventActorDetail
14:22:07workspace.member.invitedalice@acme.comrole member, target bob@acme.com
14:22:31workspace.sso.loginbob@acme.comSAML, provider Okta, first-time
14:23:04workspace.audit_siem.configuredalice@acme.comwebhook rotated, active=true

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.* and security.*" is a natural v1.1.

Related