Skip to main content

Universal capture

The polling connector catalog covers ~26 vendors. Universal capture is the escape hatch for everything else.

If a tool can do any of the following, AiSOC can ingest it:

  • POST a webhook to a URL.
  • Send email to a mailbox.
  • Emit CEF over syslog.
  • Send to a Splunk HTTP Event Collector (HEC) endpoint.

Each path lands in the same OCSF event stream as native connectors — same detections, same correlation, same lake.

When to use this vs. a polling connector

SituationUse
Vendor is in the catalogNative connector — better fidelity, schema drift detection, freshness SLO
Vendor exposes webhooksPOST /v1/inbox/{tenant_token}
Vendor only emails alertsPOST /v1/inbox/email/{tenant_token} (or pair the Email Inbox connector)
Legacy on-prem appliance/v1/inbox/cef over syslog-to-HTTPS
Drop-in Splunk HEC replacement/v1/inbox/hec

If the vendor offers both webhooks and a polled API, prefer the polled API — you get historical backfill on outage and structured pagination. Webhooks are at-most-once on the vendor side.

How it works

  1. Operator picks a template in the console (PagerDuty, Cloudflare Logpush, generic JSON, etc.).
  2. Console mints an inbox token scoped to that template + tenant. The full token is shown once; only the SHA-256 fingerprint is stored.
  3. Operator pastes the resulting URL into the vendor's webhook field.
  4. Vendor POSTs JSON. The ingest service:
    • resolves the token from tenant_inbox_tokens (LRU-cached, 60s TTL),
    • optionally verifies HMAC if a signing secret was set on mint,
    • applies the YAML template to map vendor fields → OCSF,
    • publishes to the same Kafka topic as polled events.
  5. The detection pipeline doesn't know — or care — that the event arrived push-side.

The token never grants any privilege beyond "write events scoped to this template into this tenant." Rotation is a one-click operation in the console; the cache invalidates within 60 seconds, sooner if Invalidate() is called by the rotation flow.

Endpoints

POST /v1/inbox/{tenant_token} — generic JSON webhook

The default route. Accepts a single JSON object or an NDJSON batch.

curl -X POST https://ingest.tryaisoc.com/v1/inbox/aisoc_inbox_xxxxx \
-H "Content-Type: application/json" \
-d '{
"event": {
"event_type": "incident.triggered",
"occurred_at": "2026-05-08T09:00:00Z",
"data": {
"id": "Q1234ABCD",
"title": "Database CPU > 90%",
"urgency": "high",
"service": {"summary": "prod-postgres"}
}
}
}'

Headers

HeaderRequiredNotes
Content-Typeyesapplication/json or application/x-ndjson
X-Signature / X-Hub-Signature-256if signing secret sethex(HMAC-SHA256(secret, body)), optionally prefixed sha256=
X-AiSOC-Idempotency-KeyoptionalIf set, repeated POSTs with the same key inside 24h are deduped

Response

{ "accepted": 1, "template": "pagerduty" }

A 4xx response means the token, signature, or body shape is wrong — the vendor will retry, but until you fix the cause it'll keep bouncing. Watch aisoc_ingest_inbox_requests_total{outcome!="ok"} in Prometheus.

POST /v1/inbox/email/{tenant_token} — email-relay JSON

Designed for SES inbound, SendGrid Inbound Parse, Postmark, or any service that turns email into a JSON POST.

The template email-forwarded extracts:

  • from, to, subjectfinding.title, evidence,
  • plaintext body → message,
  • attachments listed → finding.evidence.file.

If your relay can't push directly, see Email Inbox connector.

POST /v1/inbox/cef — CEF over HTTPS

Common Event Format from legacy SIEM, firewalls, and on-prem appliances. The path is token-less because most syslog forwarders can't set custom URL paths reliably; use a shared secret + IP allowlist on the load balancer.

CEF:0|Vendor|Product|1.0|100|User login failed|3|src=10.0.0.1 suser=alice cs1=mfa

ParseCEFBatch handles newline-delimited inputs and unescapes \|, \\, and \= per the CEF spec. Malformed lines are dropped and counted in aisoc_ingest_inbox_requests_total{outcome="malformed"} rather than rejecting the whole batch.

POST /v1/inbox/hec — Splunk HEC compatible

Drop-in replacement for services/collector/event — useful for on-prem fleets already configured to write to Splunk HEC. Authorization is the standard Authorization: Splunk <token> header where <token> is the inbox token minted in the console.

curl -X POST https://ingest.tryaisoc.com/v1/inbox/hec \
-H "Authorization: Splunk aisoc_inbox_xxxxx" \
-H "Content-Type: application/json" \
-d '{"event": {"action": "login_failed", "user": "alice"}, "sourcetype": "auth"}'

Templates

The bundled templates ship with the services/ingest binary and are loaded at startup from services/ingest/internal/normalizer/templates/:

Template IDUse for
generic-jsonAnything not in the list below — passes the body through with light timestamp normalization
pagerdutyPagerDuty Events API v2 webhooks
opsgenieOpsgenie alert webhooks
cloudflare-logpushCloudflare Logpush jobs (HTTP destination)
aws-eventbridgeAWS EventBridge → API destination
aws-snsAWS SNS → HTTPS subscription
splunk-hecAnything that already speaks Splunk HEC envelope
cef-syslogCEF lines posted as text to /v1/inbox/cef
email-forwardedInbound-email relays + the Email Inbox connector
microsoft-defender-emailDefender for Office 365 alert emails forwarded as text
github-security-advisoryGitHub security advisory webhooks

Adding a template is a single YAML file plus an entry in ALLOWED_TEMPLATE_IDS on the API service — see services/api/app/api/v1/inbox.py. Templates are validated at load time; a bad map blocks startup rather than silently dropping events.

Template structure

id: my-vendor
vendor_name: My Vendor
product_name: My Vendor Cloud
class_uid: 2001
class_name: Security Finding
activity_id: 1
field_map:
alert.id: finding.uid
alert.message: message
alert.url: url
severity_field: alert.priority
severity_map:
P1: 5
P2: 4
P3: 3
time_field: alert.created_at
constants:
source_type: my-vendor
metadata.product.feature.name: alerts

metadata.* paths are merged into the OCSF metadata object — they don't clobber the version/uid stamped at ingest time.

Email Inbox as last resort

When the vendor can't be configured to push at all but can email alerts, run the Email Inbox connector. It polls a dedicated mailbox over IMAPS, marks messages \Seen, and feeds each one through the email-forwarded template — the same pipeline /v1/inbox/email uses, just polled from your side instead of pushed from theirs.

Required config:

FieldNotes
hoste.g. imap.gmail.com
portDefaults to 993 (IMAPS)
usernameMailbox login (e.g. aisoc-alerts@example.com)
passwordApp-specific password — never the primary account password; encrypted at rest in the credential vault
mailboxDefaults to INBOX
max_messagesPer-poll cap; defaults to 50
use_sslDefaults to true; set false only on isolated networks

Recommended pairing: forward all vendor alerts to a single dedicated mailbox with a vendor-prefixed subject ([CrowdStrike] ..., [Tenable] ...) and let the detection pipeline route on vendor_name extracted from the subject.

Observability

MetricUse for
aisoc_ingest_inbox_requests_total{route, template, outcome}Per-vendor throughput, error rate, and template drift
aisoc_ingest_inbox_events_total{template}Event volume by template — basis for the freshness SLO
aisoc_ingest_inbox_token_cache_sizeCache pressure; spikes indicate token churn

The console connector cards expose a Push tab next to Polling that surfaces the request count, last-seen timestamp, and 24-hour error rate for each minted token. Rotating a token through the UI also pushes an Invalidate(token) to ingest so the cache flushes immediately rather than waiting out the TTL.

Security model

  • Token strength: 32 random bytes, base32-encoded with aisoc_inbox_ prefix; ~160 bits of entropy.
  • Storage: Only the SHA-256 fingerprint is persisted. The full token is shown once at mint time. Rotation is a one-click operation; the old token stops resolving after the next cache TTL (60s) at the latest.
  • Scope: A token is bound to a single template + tenant. It cannot pivot to other templates, other tenants, or any non-write API. There is no read surface on /v1/inbox/*.
  • Signing: Optional HMAC-SHA256 secret on mint. When set, requests without a valid X-Signature are rejected with 401 even if the token resolves.
  • RLS: tenant_inbox_tokens is row-level-secured by tenant_id. The API service only ever queries with the caller's tenant context.
  • Network: The token URL is unguessable, but treat it like a credential. Use the per-tenant rate limit (AISOC_INBOX_RATE_LIMIT_PER_MIN, default 600/min) to cap blast radius if it leaks.

Troubleshooting

SymptomLikely cause
401 invalid tokenToken rotated or revoked; mint a new one
401 invalid signatureWrong signing secret, or vendor canonicalized the body before signing — check vendor docs
400 unknown templateToken's template was removed from ALLOWED_TEMPLATE_IDS; revoke and re-mint
413 body too largeBody exceeded AISOC_INBOX_MAX_BODY_BYTES (default 10MiB); check for vendor-side pagination misconfig
429 too many requestsTenant rate limit hit; increase AISOC_INBOX_RATE_LIMIT_PER_MIN or have the vendor batch
Events accepted but never appearAlmost always a template mismatch — check aisoc_ingest_inbox_events_total{template="..."} to confirm and inspect the OCSF event in the lake