SIEM forwarding¶
Forward every preflight decision to your SIEM without leaving your process. Runs on a background thread, fire-and-forget, never blocks the SDK caller.
Quickstart¶
Wire a forwarder into the on_decision constructor arg:
from zotniq import Zotniq
from zotniq.siem import SplunkForwarder
client = Zotniq(
api_key="zot_sk_...",
on_decision=SplunkForwarder(
url="https://splunk.acme.com:8088/services/collector",
token="hec-token-here",
),
)
# Every preflight.check() call fires an async POST to Splunk.
result = client.preflight.check("...", destination="AI_TOOL")
Built-in forwarders¶
Splunk HEC¶
from zotniq.siem import SplunkForwarder
SplunkForwarder(
url="https://splunk.acme.com:8088/services/collector",
token="hec-token",
source="zotniq-sdk", # default
sourcetype="zotniq:decision", # default
index="dlp_events", # optional
verify_ssl=True, # set False for self-signed HEC
)
Auth via the Splunk <token> Authorization header per HEC docs. Batches events efficiently, retries transient failures.
Datadog Logs API v2¶
from zotniq.siem import DatadogForwarder
DatadogForwarder(
api_key="dd-api-key",
site="datadoghq.com", # or datadoghq.eu, us3.datadoghq.com, etc.
ddsource="zotniq", # default
service="chat-backend", # your service name
tags=["env:prod", "team:security"],
)
Multi-region: site maps to the correct intake host automatically.
Generic webhook¶
Any SIEM with a JSON ingestion endpoint (Cribl, Sumo Logic, Elastic HTTP JSON input, custom SOC pipelines):
from zotniq.siem import WebhookForwarder
WebhookForwarder(
url="https://soc.acme.com/hooks/dlp",
headers={"Authorization": "Bearer secret-token"},
timeout=10.0,
)
Local file (ndjson)¶
For pipelines that tail files (older Splunk deployments, Vector, Filebeat):
Thread-safe. Rotation is deliberately out of scope — use standard logrotate against the target file.
Custom forwarders¶
Any Callable[[PreflightResult, dict], None] works as on_decision:
from zotniq import Zotniq
from zotniq.types import PreflightResult
def my_forwarder(result: PreflightResult, ctx: dict) -> None:
my_soc_client.emit(
decision=result.decision.value,
findings=len(result.findings),
text_hash=ctx["text_hash"],
request_id=ctx["request_id"],
)
client = Zotniq(api_key="zot_sk_...", on_decision=my_forwarder)
Failures inside your callback are caught and logged at WARNING. The SDK caller is never affected.
What's in the event¶
The context dict passed to your forwarder carries decision metadata only — never raw content:
| Field | Type | Example |
|---|---|---|
timestamp |
ISO8601 UTC | 2026-08-25T20:00:00+00:00 |
destination |
str | AI_TOOL |
mode_used |
str | cloud or local |
text_hash |
SHA256 hex | abc123... (for correlation, not decryption) |
text_length |
int | bytes |
api_key_fingerprint |
str | ...abc123 (last 6 chars) |
request_id |
str | server's x-request-id or local UUID |
sdk_version |
str | 0.1.0 |
Plus the built-in forwarders wrap this in an event envelope with:
| Field | Example |
|---|---|
event |
"zotniq.preflight.decision" |
decision |
"ALLOWED_WITH_MASKING" |
summary |
"Masked before send (EMAIL)." |
findings_count |
2 |
finding_types |
["EMAIL", "SSN"] |
Privacy invariant¶
Raw text and masked_text NEVER cross the hook boundary. If you need the raw content in your SIEM (for correlation with other events in your SOC), capture it in your own code before calling preflight.check — in your process, never touched by the SDK.
Overflow policy¶
The internal queue caps at 10,000 events. If your SIEM is slow enough that events pile up faster than they drain, the SDK drops the oldest event and logs a WARNING. The drop counter is available via client.siem_stats():
Right thing to do if you see drops in production: increase your SIEM's ingestion capacity, or add a queue between the SDK and the SIEM (Kinesis, Kafka, etc.) and use a custom forwarder to push into that queue instead.
Server-side audit stream vs SDK-side forwarding¶
Two orthogonal forwarding surfaces:
- Server-side audit stream (see Audit Stream guides) — configured in your Zotniq dashboard, forwards decisions from the server's
preflight_audittable. Cross-tenant, works for every SDK/API call regardless of client. - SDK-side SIEM forwarder (this page) — configured in your app code, forwards decisions from inside your process. Zero-latency, works even in
mode="local", no dashboard config needed.
Most customers use one or the other, not both. Pick based on where your SIEM ingestion pipeline lives.