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
signingSecretis 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, orauthare 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
Related
- Organizations & Permissions โ Base Organization infrastructure, roles, and the audit trail that SIEM webhooks stream from
- Custom RBAC โ Admin-defined custom roles โ
MANAGE_WEBHOOKScan 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