Skip to content

Guides

SIEM export

Send the evidence ledger to Splunk, Sentinel, Chronicle or any SIEM as OCSF 1.3, or to a tracing backend as OpenTelemetry. Pull it on a schedule, or have it pushed by signed webhook.

The ledger is the source of truth; exports are views of it. Both formats carry the same subject and trace ids the ledger does, so an event in your SIEM can be traced back to the chained, signed record.

#Pull: OCSF

GET /api/w/:wid/export/ocsf returns NDJSON, one OCSF 1.3 event per line, for readers with evidence rights.

Shell
curl "https://immiscible.fly.dev/api/w/$IMMISCIBLE_WORKSPACE/export/ocsf?since=2026-10-01T00:00:00Z&limit=5000" \
  -H "cookie: __Host-sid=$IMMISCIBLE_SESSION" > immiscible.ndjson
ParameterMeaning
since, untilISO 8601 times bounding the records
limithow many records at most

#OpenTelemetry

GET /api/w/:wid/export/otel returns OTLP/JSON logs, ready to post to a collector’s /v1/logs. Records keep their traceparent, so they land on the same traces as your own spans.

#Push: signed webhooks

For near real time, register a webhook. Owners and admins create them; the secret is shown once.

curl -X POST "https://immiscible.fly.dev/api/w/$IMMISCIBLE_WORKSPACE/webhooks" \
  -H "cookie: __Host-sid=$IMMISCIBLE_SESSION" -H "x-immiscible-csrf: 1" \
  -H "content-type: application/json" \
  -d '{ "url": "https://soar.example.com/hooks/immiscible", "events": ["agent_decision", "agent_freeze", "agent_incident"], "format": "ocsf" }'
  • events is a list of ledger kinds, or ["*"] for everything. A kind the ledger never writes is refused with 400 invalid_events, naming the closest real one (“did you mean agent_freeze?”). The kinds an agent’s life writes are agent_decision, agent_approval, agent_freeze, agent_incident, agent_settlement and mandate_change.
  • format is ocsf (the default) or native (the ledger record as it stands).
  • Up to five attempts with backoff. Private and internal addresses are refused.
  • POST .../webhooks/:id/test sends one now; GET .../deliveries shows what was sent (a SHA-256 of the body) and what came back (the status and the first 1 KB), and POST .../deliveries/:did/resend sends one again.

#Verifying a delivery

Each delivery carries immiscible-signature: t=<unix>,v1=<hex>, an HMAC-SHA256 of <t>.<raw body> with the webhook’s secret. Check it before trusting the body, and refuse old timestamps:

import { createHmac, timingSafeEqual } from 'node:crypto';

export function verifyImmiscible(rawBody: string, header: string, secret: string, toleranceSec = 300): boolean {
  const parts = Object.fromEntries(header.split(',').map((kv) => kv.split('=', 2)));
  const t = Number(parts.t);
  if (!Number.isFinite(t) || Math.abs(Date.now() / 1000 - t) > toleranceSec) return false;
  const expected = createHmac('sha256', secret).update(`${t}.${rawBody}`).digest();
  const given = Buffer.from(parts.v1 ?? '', 'hex');
  return given.length === expected.length && timingSafeEqual(given, expected);
}

#From a SOAR

A playbook that reads events from your SIEM usually wants to act too: freeze an agent, list waiting approvals, pull the evidence bundle. Give it a service token with exactly the scopes it needs, and read the kill switch for the safe way to freeze many agents.