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:

FieldTypeDescription
actorIdstringUser ID or apikey:{keyId}
actorRolestring?Global role at time of action
eventNamestringEvent identifier (e.g. user.login, organization.create)
eventType"auth" | "mutation" | "query"Event category
readOnlybooleanWhether the action modified data
sourcestringModule or plugin that logged the event
targetTypestring?Resource type affected
targetIdstring?Resource ID affected
orgIdstring?Organization context
ipAddressstring?Client IP address
userAgentstring?Client user agent
requestobject?Additional metadata
status"success" | "error"Outcome
errorMessagestring?Error details on failure

Indexes: by_actor, by_event, by_status, by_org.

FilePurpose
packages/backend/src/convex/lib/core/auth/audit_trail.tsBetterAuth plugin (auto-logs auth events)
packages/backend/src/convex/modules/core/audit/audit_api.tsAdmin and org-scoped query endpoints
packages/backend/src/convex/modules/core/audit/audit_internal.tsInternal log mutation
packages/backend/src/convex/modules/core/audit/audit_table.tsDatabase schema
packages/backend-contract/src/core/audit.tsShared audit() helper and validators