Skip to main content

Recording Audit Events

Every service writes audit records through kernel.audit(). The call is asynchronous and non-blocking — the service continues execution without waiting for ClickHouse acknowledgment.


record()

Record a single audit event.

import { kernel } from '@platform/sdk-core';

await kernel.audit().record({
action: 'user.login',
entityType: 'user',
entityId: '01j9pa5mz700000000000000',
userId: '01j9pa5mz700000000000000',
ip: '192.0.2.10',
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)',
before: null,
after: { loginAt: '2026-04-15T10:30:00.000Z', method: 'password' },
metadata: { sessionId: '01j9psess00000000000000' },
});
POST https://api.septemcore.com/v1/audit
Authorization: Bearer <access_token>
Content-Type: application/json

{
"action": "user.login",
"entityType": "user",
"entityId": "01j9pa5mz700000000000000",
"userId": "01j9pa5mz700000000000000",
"ip": "192.0.2.10",
"userAgent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)",
"before": null,
"after": { "loginAt": "2026-04-15T10:30:00.000Z", "method": "password" },
"metadata": { "sessionId": "01j9psess00000000000000" }
}

Response 202 Accepted — the record has been accepted for processing. It is not yet confirmed in ClickHouse (async pipeline):

{
"auditId": "01j9paud1000000000000000",
"status": "accepted",
"timestamp": "2026-04-15T10:30:00.000Z"
}

recordBatch()

Record multiple audit events in a single request. Use when a single business operation produces multiple audit-worthy state changes (e.g. a permission change that affects 5 roles simultaneously).

await kernel.audit().recordBatch([
{
action: 'role.permission.granted',
entityType: 'role',
entityId: '01j9prole100000000000000',
userId: '01j9padmin0000000000000',
before: { permissions: ['billing.view'] },
after: { permissions: ['billing.view', 'billing.plan.change'] },
},
{
action: 'role.permission.granted',
entityType: 'role',
entityId: '01j9prole200000000000000',
userId: '01j9padmin0000000000000',
before: { permissions: [] },
after: { permissions: ['billing.view'] },
},
]);
POST https://api.septemcore.com/v1/audit/batch
Authorization: Bearer <access_token>
Content-Type: application/json

{
"records": [
{
"action": "role.permission.granted",
"entityType": "role",
"entityId": "01j9prole100000000000000",
"userId": "01j9padmin0000000000000",
"before": { "permissions": ["billing.view"] },
"after": { "permissions": ["billing.view", "billing.plan.change"] }
},
{
"action": "role.permission.granted",
"entityType": "role",
"entityId": "01j9prole200000000000000",
"userId": "01j9padmin0000000000000",
"before": { "permissions": [] },
"after": { "permissions": ["billing.view"] }
}
]
}

Response 202 Accepted:

{
"accepted": 2,
"auditIds": [
"01j9paud2000000000000000",
"01j9paud3000000000000000"
],
"timestamp": "2026-04-15T10:30:00.000Z"
}

Audit Record Model

FieldTypeRequiredDescription
actionstringDot-namespaced action type: service.entity.verb (e.g. money.transaction.credited)
entityTypestringType of the affected entity: user, wallet, role, module, subscription, etc.
entityIdstringID of the affected entity (ULID or UUID)
userIdstringWho performed the action. Use system identity (e.g. system:money-worker) for automated operations
ipstringClient IP. null for internal/system operations
userAgentstringClient User-Agent string. null for internal operations
beforeobject | nullState snapshot before the change. null for creation events
afterobject | nullState snapshot after the change. null for deletion events
metadataobjectArbitrary key-value context (session ID, request ID, module ID, etc.)

Action Naming Convention

Actions follow the pattern service.entity.verb:

ServiceExample actions
IAMuser.login, user.logout, user.mfa.enabled, role.permission.granted
Moneymoney.transaction.credited, money.hold.created, money.hold.expired
Filesfiles.file.uploaded, files.file.deleted, files.orphan.deleted
Notifynotify.channel.registered, notify.message.sent
Billingbilling.subscription.created, billing.plan.changed
Modulemodule.enabled, module.disabled, module.registry.updated

Mandatory Audit Categories

The following categories must be audited by every module and service. This is a platform compliance requirement:

CategoryMust auditExample action
Financial transactionsEvery credit, debit, hold, confirm, cancel, reversalmoney.transaction.debited
Admin actionsAny admin-initiated change affecting tenants or usersbilling.subscription.suspended
API integrationsAll external postback receipts, webhook deliveriesintegration.postback.received
RBAC changesEvery role assignment and permission grant/revokerole.permission.revoked
Tracking eventsRegistration, first deposit (FTD), login, logoutuser.login, user.registered
Configuration changesFeature flag changes, module registry editsmodule.enabled, flags.flag.updated

Omitting audit records for these categories is a compliance violation. The platform does not enforce this at runtime — it is the module developer's responsibility.


Before / After Snapshots

Always populate before and after with the relevant fields of the entity state:

// Correct — include the specific changed fields
before: { status: 'active' }
after: { status: 'suspended' }

// Correct — creation event
before: null
after: { userId: '...', email: '[email protected]', role: 'member' }

// Correct — deletion event
before: { fileId: '...', filename: 'contract.pdf', bucket: 'documents' }
after: null

Do not include PII (passwords, tokens, full credit card numbers) in before / after. The Audit Service does not automatically redact these fields.


System Identity for Automated Operations

For operations performed by background workers (not by a human user), use a system identity string in the userId field:

{
action: 'money.hold.expired',
userId: 'system:money-hold-worker',
// ...
}

{
action: 'files.orphan.deleted',
userId: 'system:files-orphan-scanner',
// ...
}

System identities are prefixed with system: to distinguish them from real user IDs in audit queries and compliance reports.


Error Reference

ScenarioHTTPCode
Missing required field (action, entityType, entityId, userId)400validation-error
action does not match naming pattern400validation-error
Batch exceeds 100 records400BATCH_TOO_LARGE
Service unavailable (both Kafka and WAL failed)503AUDIT_UNAVAILABLE