Developer Docs

SIEM Event Streaming

Stream your Organization Audit Trail to external Security Information and Event Management (SIEM) platforms in real time. Configure webhook endpoints that receive HMAC-SHA256 signed payloads for every audit event โ€” member changes, role assignments, webhook lifecycle, and more โ€” enabling centralized security monitoring, compliance dashboards, and automated incident response.


How It Works

โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          Organization Audit Trail              โ”‚
โ”‚     (OrgAuditService.log() โ€” fire-and-forget)  โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                   โ”‚
                   โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚          SiemDeliveryService.dispatch()        โ”‚
โ”‚                                                โ”‚
โ”‚  1. Find active webhooks matching event type   โ”‚
โ”‚  2. Fan out to all matching endpoints          โ”‚
โ”‚  3. Sign payload with HMAC-SHA256              โ”‚
โ”‚  4. POST to each endpoint                      โ”‚
โ”‚  5. Retry failed deliveries (3 attempts)       โ”‚
โ”‚  6. Log delivery result                        โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
       โ”‚                    โ”‚
       โ–ผ                    โ–ผ
โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”    โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
โ”‚   Splunk HEC  โ”‚    โ”‚   Datadog    โ”‚
โ”‚  (webhook A)  โ”‚    โ”‚  (webhook B) โ”‚
โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜    โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜

Every call to OrgAuditService.log() asynchronously triggers the SIEM delivery pipeline. Webhook delivery is fire-and-forget โ€” audit logging performance is never impacted, even if all external endpoints are down.


Webhook Configuration

Creating a Webhook

Organization admins with the MANAGE_WEBHOOKS permission can register up to 10 SIEM webhook endpoints per Organization:

mutation CreateSiemWebhook($input: CreateSiemWebhookInput!) {
	createSiemWebhook(input: $input) {
		id
		organizationId
		name
		url
		signingSecret # โ† save this! never shown again
		isActive
	}
}

Input:

{
	"input": {
		"orgId": "org-enterprise-01",
		"name": "Splunk HEC โ€” Production",
		"url": "https://splunk.corp.example.com/services/collector/event",
		"eventTypes": [],
		"headers": {
			"Authorization": "Splunk YOUR_HEC_TOKEN"
		}
	}
}
Field Type Required Description
orgId ID! โœ… Organization to attach the webhook to
name String! โœ… Human-readable label (unique per Organization)
url String! โœ… HTTPS endpoint to receive events
eventTypes [String!] โ€” Filter to specific event types. Empty = all events
headers JSON โ€” Custom HTTP headers (e.g., auth tokens for the target)

Important: The signingSecret is returned only on create and rotate operations. Copy it immediately โ€” it cannot be retrieved later.

Updating a Webhook

mutation UpdateSiemWebhook($input: UpdateSiemWebhookInput!) {
	updateSiemWebhook(input: $input) {
		id
		name
		url
		isActive
	}
}

All fields except webhookId and orgId are optional โ€” only provided fields are updated:

{
	"input": {
		"webhookId": "wh-abc123",
		"orgId": "org-enterprise-01",
		"name": "Splunk HEC โ€” Staging",
		"isActive": false
	}
}

Listing Webhooks

query {
	siemWebhooks(orgId: "org-enterprise-01") {
		id
		name
		url
		isActive
		eventTypes
		createdAt
		updatedAt
	}
}

Signing secrets are masked in list/detail queries (e.g., whsec_abโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข7890). Sensitive header values (keys matching secret, token, key, auth) are also masked.

Deleting a Webhook

mutation {
	deleteSiemWebhook(orgId: "org-enterprise-01", webhookId: "wh-abc123") {
		id
		name
	}
}

Secret Management

Each webhook has a cryptographically generated signing secret (whsec_ prefix + 32 random bytes, hex-encoded). This secret is used to compute HMAC-SHA256 signatures for every delivered payload.

Rotating a Secret

If a signing secret is compromised, rotate it immediately:

mutation {
	rotateSiemWebhookSecret(orgId: "org-enterprise-01", webhookId: "wh-abc123") {
		id
		signingSecret # โ† the NEW secret (save it!)
	}
}

The old secret becomes invalid immediately. Update your SIEM integration's signature verification to use the new secret.

Test Send

Verify connectivity before relying on a webhook for production monitoring:

mutation {
	testSiemWebhook(orgId: "org-enterprise-01", webhookId: "wh-abc123") {
		success
		httpStatus
		error
	}
}

This sends a synthetic TEST_EVENT payload to the endpoint โ€” useful for validating URL reachability, authentication, and signature verification logic on the receiving side.


Payload Format

Every SIEM delivery is a POST request with a JSON body:

{
	"event": {
		"id": "evt_a1b2c3d4e5f6",
		"organizationId": "org-enterprise-01",
		"eventType": "ROLE_CHANGED",
		"actorId": "user-admin-01",
		"targetUserId": "user-member-42",
		"metadata": {
			"oldRole": "MEMBER",
			"newRole": "ADMIN"
		},
		"ipAddress": "203.0.113.42",
		"createdAt": "2026-04-08T17:00:00.000Z"
	},
	"webhookId": "wh-abc123",
	"timestamp": "2026-04-08T17:00:01.234Z"
}

HTTP Headers

Header Value Description
Content-Type application/json Always JSON
x-webhook-signature sha256=a1b2c3... HMAC-SHA256 hex digest of the body
x-webhook-id wh-abc123 Webhook identifier
x-webhook-timestamp 2026-04-08T17:00:01Z Delivery timestamp
(custom headers) (as configured) Any headers set on the webhook

Verifying Signatures

On your SIEM receiver, verify the payload authenticity by computing the HMAC and comparing:

import { createHmac } from 'crypto';

function verifyWebhookSignature(rawBody: string, signatureHeader: string, secret: string): boolean {
	const expected = createHmac('sha256', secret).update(rawBody).digest('hex');
	const received = signatureHeader.replace('sha256=', '');
	return expected === received;
}

// In your Express/Fastify handler:
app.post('/siem-webhook', (req, res) => {
	const isValid = verifyWebhookSignature(
		req.rawBody,
		req.headers['x-webhook-signature'],
		process.env.SIEM_SIGNING_SECRET
	);
	if (!isValid) return res.status(401).send('Invalid signature');
	// Process event...
	res.status(200).send('OK');
});

Event Types

Webhooks can be filtered to specific event types. If eventTypes is empty (the default), the webhook receives all audit events for the Organization.

Event Type Description
MEMBER_INVITED New member invitation sent
MEMBER_JOINED Member accepted invitation
MEMBER_REMOVED Member removed from Organization
ROLE_CHANGED Member's base role changed
VERTICAL_ROLE_CHANGED Member's vertical role changed
TEAM_CREATED Team created
TEAM_UPDATED Team name or settings changed
TEAM_DELETED Team deleted
TEAM_MEMBER_ADDED Member added to a Team
TEAM_MEMBER_REMOVED Member removed from a Team
TEAM_ROLE_CHANGED Team member role changed (LEAD/MEMBER)
COLLABORATOR_ADDED External collaborator added
COLLABORATOR_UPDATED Collaborator permissions/status changed
COLLABORATOR_REMOVED Collaborator removed
CUSTOM_ROLE_CREATED Custom role created
CUSTOM_ROLE_UPDATED Custom role permissions changed
CUSTOM_ROLE_DELETED Custom role deleted
CUSTOM_ROLE_ASSIGNED Custom role assigned to a member
CUSTOM_ROLE_REMOVED Custom role removed from a member
SAML_CONFIG_CREATED SAML SSO configured
SAML_CONFIG_UPDATED SAML configuration changed
SAML_CONFIG_DELETED SAML configuration removed
SAML_LOGIN Visitor authenticated via SAML SSO
SCIM_TOKEN_GENERATED SCIM provisioning token created
SCIM_TOKEN_REVOKED SCIM token revoked
SCIM_USER_PROVISIONED User provisioned via SCIM
SCIM_USER_UPDATED User updated via SCIM
SCIM_USER_DEPROVISIONED User deactivated via SCIM
SCIM_GROUP_SYNCED Group membership synced via SCIM
SIEM_WEBHOOK_CREATED SIEM webhook endpoint created
SIEM_WEBHOOK_UPDATED SIEM webhook configuration changed
SIEM_WEBHOOK_DELETED SIEM webhook endpoint deleted

Delivery Resilience

Retry Strategy

Failed deliveries are retried with exponential backoff:

Attempt Delay Cumulative Wait
1 Immediate 0s
2 1 second 1s
3 4 seconds 5s

After 3 failed attempts, the delivery is marked as FAILED and logged for monitoring.

Delivery Logs

Track delivery health via the siemDeliveryLogs query:

query {
	siemDeliveryLogs(orgId: "org-enterprise-01", webhookId: "wh-abc123", limit: 20, status: FAILED) {
		id
		eventId
		status # PENDING | DELIVERED | FAILED
		httpStatus
		attempts
		error
		lastAttemptAt
		createdAt
	}
}

Delivery logs have a 30-day TTL โ€” MongoDB automatically removes entries older than 30 days via a TTL index on createdAt.

Delivery Status Reference

Status Meaning
PENDING Delivery in progress (currently retrying)
DELIVERED Successfully delivered (2xx response)
FAILED All retry attempts exhausted

Security

URL Validation

Webhook URLs are validated at create and update time:

Rule Description
HTTPS only http:// URLs are rejected โ€” all deliveries use TLS
No localhost localhost, 127.0.0.1, ::1 are blocked
No private IPs 10.x, 172.16-31.x, 192.168.x, 169.254.x ranges are blocked
Valid URL Must parse as a valid URL

These rules prevent Server-Side Request Forgery (SSRF) attacks where a malicious webhook URL could probe internal infrastructure.

Secret Masking

  • Signing secrets are returned in full only on create and rotate operations
  • All subsequent reads mask the secret (e.g., whsec_abโ€ขโ€ขโ€ขโ€ขโ€ขโ€ข7890)
  • Custom header values matching secret, token, key, or auth are masked on read

Axios Security

Webhook HTTP requests use axios@1.14.0, which includes patches for:

  • CVE-2024-39338: SSRF via protocol-relative URLs
  • CVE-2025-27152: SSRF via absolute URL overrides

Permission Model

Operation Required Permission Who Gets It
Create / Update / Delete / Rotate / Test webhook MANAGE_WEBHOOKS OWNER, ADMIN (+ Custom Roles with this permission)
List webhooks / View delivery logs VIEW_AUDIT_LOGS OWNER, ADMIN

File Reference

File Purpose
apps/organizations/src/siem/siem-webhook.entity.ts SiemWebhook entity with signing secret, event type filter, custom headers
apps/organizations/src/siem/siem-delivery-log.entity.ts SiemDeliveryLog entity with 30-day TTL
apps/organizations/src/siem/siem-webhook.service.ts CRUD, secret rotation, URL validation, header masking
apps/organizations/src/siem/siem-delivery.service.ts HMAC signing, retry logic, delivery log management
apps/organizations/src/siem/siem-webhook.resolver.ts 5 mutations + 3 queries, permission-gated
apps/organizations/src/siem/dto/create-siem-webhook.input.ts Create input DTO
apps/organizations/src/siem/dto/update-siem-webhook.input.ts Update input DTO
apps/organizations/src/audit/org-audit.service.ts Dispatch integration via ModuleRef lazy resolution
libs/common/src/guards/permission.enum.ts MANAGE_WEBHOOKS permission

Testing

Suite Tests Type
SiemWebhookService 18 Unit
SiemDeliveryService 13 Unit
SIEM Event Streaming (e2e) 11 E2E
Total 42 โ€”
# Unit tests
npm run test -- apps/organizations

# E2E tests
npx jest --config apps/organizations/test/jest-e2e.json --testPathPattern siem

  • Organizations & Permissions โ€” Base Organization infrastructure, roles, and the audit trail that SIEM webhooks stream from
  • Custom RBAC โ€” Admin-defined custom roles โ€” MANAGE_WEBHOOKS can be granted to custom roles for delegated webhook management
  • Corporate Identity (SAML & SCIM) โ€” Enterprise identity events (SAML logins, SCIM provisioning) are streamed to SIEM endpoints
  • Edit History Audit Trail โ€” Field-level change tracking โ€” complementary to SIEM streaming for compliance
  • Advanced Audit Logging UI โ€” Frontend UI that hosts the SIEM webhook management panel alongside the Organization Audit Log