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
| Field | Type | Required | Description |
|---|---|---|---|
action | string | ✅ | Dot-namespaced action type: service.entity.verb (e.g. money.transaction.credited) |
entityType | string | ✅ | Type of the affected entity: user, wallet, role, module, subscription, etc. |
entityId | string | ✅ | ID of the affected entity (ULID or UUID) |
userId | string | ✅ | Who performed the action. Use system identity (e.g. system:money-worker) for automated operations |
ip | string | ☐ | Client IP. null for internal/system operations |
userAgent | string | ☐ | Client User-Agent string. null for internal operations |
before | object | null | ☐ | State snapshot before the change. null for creation events |
after | object | null | ☐ | State snapshot after the change. null for deletion events |
metadata | object | ☐ | Arbitrary key-value context (session ID, request ID, module ID, etc.) |
Action Naming Convention
Actions follow the pattern service.entity.verb:
| Service | Example actions |
|---|---|
| IAM | user.login, user.logout, user.mfa.enabled, role.permission.granted |
| Money | money.transaction.credited, money.hold.created, money.hold.expired |
| Files | files.file.uploaded, files.file.deleted, files.orphan.deleted |
| Notify | notify.channel.registered, notify.message.sent |
| Billing | billing.subscription.created, billing.plan.changed |
| Module | module.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:
| Category | Must audit | Example action |
|---|---|---|
| Financial transactions | Every credit, debit, hold, confirm, cancel, reversal | money.transaction.debited |
| Admin actions | Any admin-initiated change affecting tenants or users | billing.subscription.suspended |
| API integrations | All external postback receipts, webhook deliveries | integration.postback.received |
| RBAC changes | Every role assignment and permission grant/revoke | role.permission.revoked |
| Tracking events | Registration, first deposit (FTD), login, logout | user.login, user.registered |
| Configuration changes | Feature flag changes, module registry edits | module.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
// 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
| Scenario | HTTP | Code |
|---|---|---|
Missing required field (action, entityType, entityId, userId) | 400 | validation-error |
action does not match naming pattern | 400 | validation-error |
| Batch exceeds 100 records | 400 | BATCH_TOO_LARGE |
| Service unavailable (both Kafka and WAL failed) | 503 | AUDIT_UNAVAILABLE |