CAIN-42 CAIN Studio

Developer documentation

SIEM, ITSM and alerting

Last reviewed 31 August 2026

All docs

SIEM forwarding#

Send every CAIN decision to the security tools your team already watches: Splunk, Cortex XSIAM, Datadog, Microsoft Sentinel, ServiceNow, Jira, PagerDuty, any OpenTelemetry backend, or any HTTPS endpoint that accepts JSON. Held actions, blocks and the kill switch can also go to a Slack or Microsoft Teams channel. Each decision is forwarded as it is recorded, with its verdict, its stages and its signed digest, so the event in your SIEM can be checked against the decision record format.

Forwarding is off until a workspace owner turns it on, per destination. It sends your workspace's decisions to a third party, so each destination is opt-in, https only, and authenticated with that collector's own token.

Splunk (HTTP Event Collector)#

Create an HEC token in Splunk, then:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://splunk.example.com:8088/services/collector/event",
       "format":"splunk_hec", "auth_token":"<your HEC token>",
       "events":["decision.recorded"], "description":"SOC Splunk"}'

Each event arrives as {"event": {...}, "sourcetype": "cain:decision.recorded", "source": "cain42", "time": <epoch>} with Authorization: Splunk <token>.

Cortex XSIAM (HTTP log collector)#

In Cortex XSIAM, create an HTTP log collector with the JSON log format and copy its API key. The collector URL has the form https://api-<your tenant>/logs/v1/event:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://api-<your tenant>/logs/v1/event",
       "format":"xsiam", "auth_token":"<collector API key>",
       "events":["decision.recorded"], "description":"Cortex XSIAM"}'

Each event is one JSON object followed by a newline, sent with the collector key in the Authorization header.

> Tested against: a local receiver that checks the request shape. Not yet tested against a live > Cortex XSIAM tenant. If your collector shows a different header in its sample request, tell us at > support@cainstudio.online and we will match it.

Datadog (Logs)#

Use a Datadog API key and your site's logs intake URL:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://http-intake.logs.datadoghq.com/api/v2/logs",
       "format":"datadog", "auth_token":"<Datadog API key>",
       "events":["decision.recorded"], "description":"Datadog"}'

Each event is sent as one log entry (ddsource:cain, service:cain-fabric, tags event:<event> and tenant:<tenant>) whose message is the signed envelope itself, so Datadog parses it as JSON. The key goes in the DD-API-KEY header.

Microsoft Sentinel (Logs Ingestion API)#

Create a data collection endpoint and a data collection rule with a custom stream, for example Custom-CAINDecisions_CL, with the columns TimeGenerated (datetime), Event, CainTenant, DeliveryId, Envelope (string) and Data (dynamic). Then register an Entra app, give it the *Monitoring Metrics Publisher* role on the rule, and pass its credentials as auth_token:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://<endpoint>.ingest.monitor.azure.com/dataCollectionRules/dcr-<immutable id>/streams/Custom-CAINDecisions_CL?api-version=2023-01-01",
       "format":"sentinel", "auth_token":"<entra tenant id>:<app client id>:<app client secret>",
       "events":["decision.recorded"], "description":"Sentinel"}'

CAIN exchanges the app credentials for an Entra token (client credentials, scope https://monitor.azure.com/.default) and caches it until shortly before it expires. If the exchange fails, nothing is sent and the fault is recorded in the deliveries list. Envelope is the exact signed body, so you can still check CAIN-Signature from a KQL query.

Slack and Microsoft Teams (approvals and alerts)#

Post held actions to the people who approve them:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://hooks.slack.com/services/T000/B000/XXXX", "format":"slack",
       "events":["approval.requested","decision.blocked","killswitch.engaged"]}'

For Teams, use a Workflows "post to a channel when a webhook request is received" URL with "format":"teams"; messages arrive as Adaptive Cards. Slack URLs must be on hooks.slack.com, and Teams URLs on *.webhook.office.com, *.logic.azure.com or *.powerplatform.com.

A message names the action, who asked, why it was held, when the hold expires, and the approval id with the command to approve it. It never includes the call's arguments. Text that came from an agent, such as a tool name or a reason, is escaped, so it cannot turn into a link, a fake "Approve" button or an @channel in your channel. Approving still happens in the console or with cainstudio approve, not from the message.

> Tested against: local receivers that check each request shape, including the Entra token exchange. > Not yet tested against live Datadog, Sentinel, Slack or Teams workspaces. If your service shows a > different sample request, tell us at support@cainstudio.online.

ServiceNow (incidents)#

Open a ServiceNow incident when an agent is blocked, an action is held for approval, or the kill switch is engaged. Create an integration user with the itil role (or an OAuth client), then:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://<instance>.service-now.com/api/now/table/incident", "format":"servicenow",
       "auth_token":"<integration user>:<password>",
       "events":["decision.blocked","approval.requested","killswitch.engaged"]}'

auth_token is <user>:<password> (basic auth) or Bearer <OAuth access token>. For ServiceNow Security Incident Response use /api/now/table/sn_si_incident. Only your own *.service-now.com (or *.servicenowservices.com) instance is accepted. Urgency and impact follow the event (the kill switch is 1/1, a block 2/2, a held action 2/3); category is security; correlation_id is cain-<approval or decision id>`, so you can match the incident to the CAIN record; the description carries the signed envelope.

Jira Cloud (issues)#

Create a Jira issue for every block or held action. Create an API token, then:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://<site>.atlassian.net/rest/api/3/issue", "format":"jira",
       "auth_token":"<email>:<API token>:SEC:Bug", "events":["decision.blocked","approval.requested"]}'

auth_token is <email>:<API token>:<PROJECT KEY> with an optional :<issue type> (default Task). Only your own *.atlassian.net site is accepted. Issues are labelled cain42 and the event name, and the description carries the signed envelope.

> Tested against: local receivers that check the ServiceNow Table API and Jira REST v3 request shapes.

PagerDuty (Events API v2)#

Page your on-call engineer when an agent is blocked or the kill switch is engaged. In PagerDuty, add an Events API v2 integration to a service and copy its integration key:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://events.pagerduty.com/v2/enqueue", "format":"pagerduty", "auth_token":"<integration key>",
       "events":["decision.blocked","killswitch.engaged","killswitch.released","approval.requested","approval.resolved"]}'

Use https://events.eu.pagerduty.com/v2/enqueue for an EU account; no other URL is accepted. Severity follows the event: the kill switch is critical, a block is error, a held action is warning. Each incident is keyed by its approval or decision id, so approval.resolved and killswitch.released resolve the incident the matching event opened.

OpenTelemetry (OTLP logs)#

Send every decision to any OpenTelemetry backend as an OTLP/HTTP log record: an OpenTelemetry Collector, Grafana Cloud, Honeycomb, Datadog, New Relic, Langfuse or Arize Phoenix. The URL is the backend's OTLP/HTTP logs endpoint (ending in /v1/logs). If it needs a key, pass one header as auth_token:

curl -s https://cainstudio.online/fabric/webhooks \
  -H "X-API-Key: $CAIN_API_KEY" -H 'content-type: application/json' \
  -d '{"url":"https://api.honeycomb.io/v1/logs", "format":"otlp", "auth_token":"x-honeycomb-team: <key>",
       "events":["decision.recorded"]}'

Each record has service.name=cain-fabric, a severity that follows the event, and the attributes cain.event, cain.verdict, cain.decision_id, cain.agent_id, cain.path and more, so you can filter and alert on them. The record body is the exact signed envelope, so the CAIN-Signature can still be checked from your backend. Headers such as Host or Content-Type cannot be set this way.

> Tested against: local receivers that check the PagerDuty Events v2 and OTLP/HTTP JSON shapes.

Any HTTPS endpoint#

Leave out format (it defaults to cain) to receive the signed JSON envelope that all CAIN webhooks use. Verify each delivery with the signing secret returned once at creation: HMAC-SHA256 over <timestamp>.<raw body>, compared with the v1 value of the CAIN-Signature header. Reject deliveries whose timestamp is more than 5 minutes old.

Events#

EventSent when
decision.recordedevery recorded decision: the SIEM feed
decision.blockeda decision that an enforcing stage blocked
approval.requested / approval.resolvedan action is held for a person, then approved or denied
killswitch.engaged / killswitch.releasedthe workspace kill switch changes

A decision.recorded event carries: decision_id, created_at, verdict, blocked, enforcing, halted, principal_id, agent_id, service, path, chain_id, outcome, the stages (name, verdict, enforcing), and digest, record_key_id and record_signature.

How forwarding behaves#

  • Never slows or fails a decision. Delivery runs after the decision is recorded, detached from it.
  • Retries up to 4 attempts with backoff, then records the fault. An endpoint that fails 20 times in

a row is paused (not deleted); see GET /fabric/webhooks/deliveries.

  • Cannot be aimed inside our network. The hostname is resolved and every address checked before the

destination is accepted and again before each attempt. Private, loopback and cloud-metadata addresses are refused, the connection goes to the address that was checked, and redirects are not followed.

  • Secrets stay put. The collector token and the signing secret are never returned by any endpoint

after creation.

Manage destinations#

curl -s https://cainstudio.online/fabric/webhooks -H "X-API-Key: $CAIN_API_KEY"               # list
curl -s https://cainstudio.online/fabric/webhooks/deliveries -H "X-API-Key: $CAIN_API_KEY"    # recent deliveries
curl -s -X DELETE https://cainstudio.online/fabric/webhooks/<endpoint_id> -H "X-API-Key: $CAIN_API_KEY"