Skip to content

Audit Trail

The audit trail module records security-relevant events across the application. Auth events are captured automatically through a BetterAuth plugin, while application-level events can be logged explicitly from any Convex function.

The auditTrail BetterAuth plugin hooks into 30+ auth lifecycle events, including:

  • Sign-in, sign-up, and sign-out (email, social, passkey)
  • Password changes and resets
  • Email verification
  • 2FA enable, disable, and verify
  • Passkey registration and deletion
  • Session revocation
  • Social account linking
  • Admin actions (ban, impersonate, role changes)
  • Organization operations (create, update, delete, member management)

Any module can log audit events by scheduling the internal log function:

import { internal } from "../../../_generated/api"
import { audit } from "@red/backend-contract/core/audit"
// Inside an admin or authenticated mutation
await ctx.scheduler.runAfter(
0,
internal.modules.core.audit.audit_internal.log,
audit(ctx.user, "myModule.api", {
eventName: "resource.created",
eventType: "mutation",
readOnly: false,
targetType: "book",
targetId: resourceId,
status: "success"
})
)

Global admins can query the full audit log with filters:

import { api } from "@red/backend/api"
const logs = await convex.query(
api.modules.core.audit.audit_api.listAuditLogs,
{
paginationOpts: { numItems: 50, cursor: null },
actorId: "user-id", // optional
eventName: "user.login", // optional
status: "success", // optional
orgId: "org-id", // optional
startDate: 1710000000000, // optional
endDate: 1710100000000 // optional
}
)

Authenticated users can query logs scoped to their active organization:

const logs = await convex.query(
api.modules.core.audit.audit_api.listOrgAuditLogs,
{
paginationOpts: { numItems: 50, cursor: null }
}
)

This endpoint requires the ac:read permission and automatically filters by the user’s active organization.

Each audit log entry captures:

Field Type Description
actorId string User ID or apikey:{keyId}
actorRole string? Global role at time of action
eventName string Event identifier (e.g. user.login, organization.create)
eventType "auth" | "mutation" | "query" Event category
readOnly boolean Whether the action modified data
source string Module or plugin that logged the event
targetType string? Resource type affected
targetId string? Resource ID affected
orgId string? Organization context
ipAddress string? Client IP address
userAgent string? Client user agent
request object? Additional metadata
status "success" | "error" Outcome
errorMessage string? Error details on failure

Indexes: by_actor, by_event, by_status, by_org.

File Purpose
packages/backend/src/convex/lib/core/auth/audit_trail.ts BetterAuth plugin (auto-logs auth events)
packages/backend/src/convex/modules/core/audit/audit_api.ts Admin and org-scoped query endpoints
packages/backend/src/convex/modules/core/audit/audit_internal.ts Internal log mutation
packages/backend/src/convex/modules/core/audit/audit_table.ts Database schema
packages/backend-contract/src/core/audit.ts Shared audit() helper and validators