Stream your first audit event to a SIEM
Time: 10 minutes. You'll need: workspace-admin rights on an Enterprise workspace, a machine reachable from Ithura at an HTTPS URL (ngrok, Tailscale funnel, or a plain public server), and a shell where you can run Python 3.
By the end you'll have a registered SIEM streaming config, a signed POST on the wire for every audit row, and a couple of real audit events (a label created, a member invited) verified on your own receiver.
What SIEM streaming is (and is not)
SIEM streaming is the live tail of your audit log. Every time an audit row is written, Ithura POSTs the same row out to a URL you control, signed with a secret only you and Ithura know. Your Splunk, Datadog, Elastic, or in-house collector ingests it.
It is not a retry queue and not a filter surface in v1. If your collector is down, the events do not land there (they stay in the audit log and in the CSV export). Per-scope filters are on the follow-up list.
1. Open Settings, SIEM streaming
From the workspace sidebar, open Settings. In the tab rail, find SIEM streaming (admin-only, Enterprise plan; if you don't see it, you need admin on the workspace and the workspace needs to be on Enterprise).
The page shows an empty form plus a Save button.
2. Stand up a minimal receiver
For the tutorial, a one-endpoint Python script is enough. It verifies the incoming HMAC and prints the audit row so you can watch each event land.
# siem_receiver.py
import hashlib, hmac, json, os
from http.server import BaseHTTPRequestHandler, HTTPServer
SECRET = os.environ["ITHURA_SIEM_SECRET"].encode()
def hex_sig(key: bytes, body: bytes) -> str:
return hmac.new(key, body, hashlib.sha256).hexdigest()
class H(BaseHTTPRequestHandler):
def do_POST(self):
body = self.rfile.read(int(self.headers["content-length"]))
got = self.headers.get("X-Ithura-Signature", "")
want = hex_sig(SECRET, body)
if not hmac.compare_digest(got, want):
self.send_response(401); self.end_headers(); return
row = json.loads(body)
print(f"[{row['created_at']}] {row['action']} "
f"actor={row.get('actor_label') or 'system'} "
f"entity={row['entity_type']}:{row.get('entity_id') or '-'}")
self.send_response(202); self.end_headers()
HTTPServer(("0.0.0.0", 8788), H).serve_forever()
Pick a shared secret (any 32+ character random string; openssl rand -hex 24 is fine) and run:
ITHURA_SIEM_SECRET=<your 48-hex secret> python3 siem_receiver.py
Expose it: ngrok http 8788 (or the tunnel of your choice) and copy
the resulting HTTPS URL. You'll paste it in the next step.
3. Register the SIEM config
Back in Settings, SIEM streaming, fill the form:
- Webhook URL: the HTTPS endpoint from step 2, e.g.
https://your-tunnel.ngrok.io/. It must be reachable from the Ithura API, not just from your browser. - HMAC secret: paste the same value you set in
ITHURA_SIEM_SECRET. Ithura never shows the secret back after save (the SPA cannot read the encrypted value), so keep your own copy in a password manager. - Timeout: leave at 30 seconds unless your collector is known to be slow. Cap is 300.
- Active: leave on.
Click Save. The config appears with a masked secret and a green Active badge. In your receiver terminal, you should already see one row land:
[2026-08-03T14:22:11.123Z] workspace.audit_siem.configured actor=you@acme.com entity=workspace:<uuid>
Yes: the SIEM config surface streams its own audit entries through itself. The config-changed row is the first live proof the pipe is open.
4. Trigger a few audit events
Now generate a couple of ordinary audit events and watch them arrive.
- Create a label. From the workspace sidebar, open any project,
then Settings, Labels. Add a label named
siem-test. - Invite a workspace member. From Settings, Members, invite
a throwaway address (your own
+testalias is fine).
Within a second or two, the receiver terminal prints:
[2026-08-03T14:23:04.510Z] workspace.label.created actor=you@acme.com entity=label:<uuid>
[2026-08-03T14:23:41.882Z] workspace.member.invited actor=you@acme.com entity=workspace_member:<uuid>
Each POST arrives with the full JSON envelope: id, workspace_id,
actor_user_id, actor_label, action, entity_type, entity_id,
metadata, ip_address, user_agent, created_at. The
X-Ithura-Signature header carries hex(hmac_sha256(secret, body)).
5. Verify the signature check works
Stop the receiver, edit the SECRET line to a wrong value, restart
it, and create another label. The receiver now replies 401 and
prints nothing. On the Ithura side, the delivery is logged as
failed. Restore the correct secret and the stream resumes on the
next event.
This is the check you want in production. Constant-time compare the computed HMAC against the header, reject on mismatch, then trust the body.
What next
- SIEM streaming: the full wire contract, the envelope schema in detail, the two self-audit rows that fire on config changes, and the delivery guarantees in v1.
- Audit CSV export: the batch counterpart. Same rows, batch shape, useful as a fallback for any window where the live stream dropped events.
- Agent Dispatch Queue: reuses the same outbound HMAC signing scheme, so the verification code you wrote in step 2 verifies dispatch payloads with no changes.