Skip to main content
Version: 0.11

Audit

Overview

Audit provides visibility into important security-relevant actions and alert delivery outcomes. Use it to investigate who did what and when, and to prove compliance.

  • Event types: authentication, token create / update / delete, workspace membership changes, CertOps certificate and executor events, auto-sync runs, alert delivery results (success/failure), weekly digest sends, WhatsApp tests, settings updates, integrations, and imports.
  • Retention: depends on your deployment policy and log storage configuration.
  • Export: audit logs can be exported as CSV or JSON for compliance reporting and external analysis.

Workspace scope

View events tied to the currently selected workspace. Managers and admins can filter by date and event type to focus on relevant activity.

  • Token changes, member invites/removals, settings updates.
  • Alert delivery outcomes for tokens in this workspace.

Organization scope

Admins can aggregate events across all admin-owned workspaces to spot trends and investigate cross-workspace issues.

  • Organization-wide filters and export.
  • Combine with Usage to correlate incidents and thresholds.

Event catalog

Each event records "occurredAt", "actor_user_id", "subject_user_id", "action", "target_type", "target_id", "channel", "metadata", and "workspace_id" (when applicable).

Two properties of the catalog are worth knowing before you read it:

  • Some events have no user actor. Agents, executors, controllers and background workers are machine principals, and actor_user_id references a user, so those events record actor_user_id: null and identify the acting machine in metadata instead (for example agentId or apiTokenId). An empty actor column is expected on those rows, not missing data.
  • target_id is an integer column. CertOps entities are identified by UUID, so their ids live in metadata (for example managedCertificateId, jobId, profileId) and target_id is left null. When searching for everything that happened to one certificate, search the metadata, not target_id.

Authentication

  • LOGIN_SUCCESS metadata: { method }
  • LOGIN_SUCCESS_2FA metadata: { method }
  • LOGIN_FAILED metadata: { email, reason }
  • LOGOUT
  • REGISTRATION metadata: { method }. method is "invite_token" when the account was created by accepting a workspace invitation, or "self_signup" otherwise.
  • TWO_FACTOR_ENABLED
  • TWO_FACTOR_DISABLED
  • EMAIL_VERIFICATION_SENT
  • EMAIL_VERIFICATION_RESENT
  • EMAIL_VERIFICATION_FAILED
  • EMAIL_VERIFIED
  • PASSWORD_RESET_REQUESTED
  • PASSWORD_RESET_COMPLETED
  • PASSWORD_CHANGED

Workspace & RBAC

  • WORKSPACE_CREATED metadata: { name, kind }
  • WORKSPACE_RENAMED metadata: { before.name, after.name }
  • WORKSPACE_DELETED
  • MEMBER_INVITED_OR_UPDATED metadata: { role, email, workspace_name, recipient_type }. Emitted synchronously when an invitation or direct membership is created. recipient_type is "existing_user" when the email already has a TokenTimer account (membership created directly) or "invitation" when a pending invitation row was written for a new email.
  • INVITE_EMAIL_SENT metadata: { role, email, workspace_name }. Emitted only after the invitation email was handed off to the SMTP transport successfully. Absence of this event after MEMBER_INVITED_OR_UPDATED means the email step failed and the operator should check mail logs.
  • INVITATION_CANCELLED metadata: { email, role, workspace_name, invitation_id }. Emitted when a pending invitation is removed via DELETE /api/v1/workspaces/:id/invitations/:invitationId. Accepted invitations are never cancelled by this endpoint.
  • MEMBER_ROLE_CHANGED metadata: { role }
  • MEMBER_REMOVED
  • WORKSPACE_MEMBERSHIP_ACCEPTED
  • WORKSPACE_ALERT_SETTINGS_UPDATED
  • SYSTEM_ADMIN_CHANGED metadata: { is_admin }. Instance-level (not workspace-scoped); recorded when a platform administrator grants or revokes another user's admin flag.

Tokens

  • TOKEN_CREATED metadata: { name, type, category }
  • TOKEN_UPDATED metadata: { fields, changes }
  • TOKEN_DELETED metadata: { name, reason }; deletions performed by obsolete-token cleanup also include { location, provider } and use reason: "import_cleanup" (manual imports) or reason: "auto_sync_cleanup" (auto-sync runs)
  • TOKENS_TRANSFERRED_BETWEEN_WORKSPACES
  • TOKENS_REASSIGNED_CONTACT_GROUP

Contacts & WhatsApp

  • WORKSPACE_CONTACT_CREATED metadata: { contact_id, phone_masked }
  • WORKSPACE_CONTACT_UPDATED metadata: { contact_id, fields_updated }
  • WORKSPACE_CONTACT_DELETED metadata: { contact_id }
  • WHATSAPP_TEST_SENT
  • WHATSAPP_TEST_SENT_TEMPLATE
  • WHATSAPP_TEST_FAILED
  • WHATSAPP_TEST_FAILED_TEMPLATE
  • WHATSAPP_TEST_RATE_LIMITED
  • WHATSAPP_TEST_ERROR

Alert delivery

  • ALERT_QUEUED metadata: { daysUntil, threshold, dueDate }
  • ALERT_CHANNELS_UPDATED metadata: { alertKey, from, to }
  • ALERT_NOT_QUEUED_NO_CHANNEL metadata: { reason }
  • ALERT_SENT metadata: { days, channels }
  • ALERT_SEND_FAILED metadata: { days, error, attempts }
  • ALERT_PARTIAL_SUCCESS metadata: { channel, errors }
  • ALERT_RETRY_SCHEDULED metadata: { days, next_attempt_at, remaining_channels }
  • ALERT_BLOCKED_MAX_ATTEMPTS metadata: { days, attempts_email, attempts_webhooks, attempts_whatsapp }
  • ALERT_BLOCKED_WHATSAPP_ERROR metadata: { days }
  • ALERT_MANUAL_RETRY
  • WEEKLY_DIGEST_SENT metadata: { contact_group_id, contact_group_name, tokens_count, channels, week_start_date }

System operations

  • ALERTS_BULK_REQUEUED
  • WHATSAPP_TEST_SENT and related test events

User settings & integrations

  • ALERT_PREFS_UPDATED metadata: { fields_updated }
  • INTEGRATION_SCAN metadata: { provider, region, success, tokens_found }
  • INTEGRATION_DETECT_REGIONS metadata: { provider, regions_found }
  • TOKEN_IMPORTED metadata: { source, name, type }
  • TOKENS_IMPORTED metadata: { source, count, method }

Auto-Sync

  • AUTO_SYNC_CREATED metadata: { provider, frequency, timezone }
  • AUTO_SYNC_UPDATED metadata: { provider, fields_updated }
  • AUTO_SYNC_DELETED metadata: { provider }
  • AUTO_SYNC_TRIGGERED metadata: { provider }
  • AUTO_SYNC_FAILED metadata: { provider, error }. Emitted by the worker when a scheduled sync run fails; check error and the worker logs for the underlying cause.

CertOps

  • CERTOPS_API_TOKEN_CREATED metadata: { api_token_id, token_prefix, name, scopes, status, expires_at }. Emitted when a workspace admin creates a machine API token for a CertOps executor.
  • CERTOPS_API_TOKEN_REVOKED metadata: { api_token_id, token_prefix, name, scopes, status, revoked_at }.
  • CERTOPS_CERTIFICATE_REGISTERED / CERTOPS_CERTIFICATE_IMPORTED metadata: { source, count, certificate_ids, fingerprints_sha256 }. source is "api" for CertOps API registrations or "import" for dashboard PEM imports.
  • CERTOPS_CERTIFICATE_ISSUED metadata: { managedCertificateId, commonName, jobId, operation, source, agentId, fingerprintSha256, serialNumber, issuer, notAfter, subjectAltNames, deployedCertPath, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost, profileId, profileDerivationReason }. Emitted the moment a certificate TokenTimer ordered actually exists: the agent's verify-step evidence has been accepted, the certificate is promoted from provisioning to active, and its real expiry is known. Written in the same transaction as the promotion, so an active certificate always has this event. operation is "issue" for a first issuance and "renew" for a retry against a certificate that had not reconciled. There is no user actor because the agent is a machine principal; agentId names the agent that did the work. deployedCertPath is always null for a windows-iis target (its destination is a Windows certificate store + IIS binding, not a file, per the windows-iis target); targetType: "windows-iis" plus the four windows* fields are the substitute, naming the store and the exact IIS site/port/SNI host the certificate was bound to. targetType is null for a target shape without an explicit type (older job payloads).
  • CERTOPS_CERTIFICATE_ISSUANCE_UNRECONCILED metadata: { managedCertificateId, commonName, jobId, operation, claimId, agentId, reconciliationReason, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost }. Emitted when a job reported success but its certificate could not be promoted, so the certificate is not usable and will not renew. reconciliationReason is one of no_claim_bound_verify_evidence, verify_evidence_missing_fingerprint, verify_evidence_missing_expiry. The reason is also stored on the certificate row, but that column only shows its latest value; this event is where the history of failed attempts survives. The windows* fields (present only when targetType is "windows-iis") name the store/site/port/SNI host the failed issuance was attempting to bind to. See Reconciling interrupted jobs.
  • CERTOPS_CERTIFICATE_RENEWAL_UNRECONCILED metadata: same shape as CERTOPS_CERTIFICATE_ISSUANCE_UNRECONCILED above. Emitted on the renewal-side equivalent failure: a renew job against an already-active certificate reported success, but the agent's claim-bound verify evidence was missing, incomplete, or unfingerprinted, so not_after/fingerprintSha256 could not be refreshed. Left unaddressed, the renewal scheduler's idempotency key (certificate id + not_after) keeps colliding with the same already-succeeded job on every later sweep, so a certificate can stay genuinely due for renewal indefinitely while every sweep reports nothing wrong; this event and the row's reconciliationReason are what make that visible.
  • CERTOPS_CERTIFICATE_RETIRED metadata: { managedCertificateId, tokenId, status, reason, fingerprintSha256 }.
  • CERTOPS_JOB_CREATED_MANUAL metadata: { operation, subjectType, subjectId, source, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost }. Emitted when a workspace manager creates an executor job manually from the dashboard or POST .../certops/jobs. source is always "api". The windows* fields (present only when targetType is "windows-iis") name the store and IIS site/port/SNI host the manual job targets.
  • CERTOPS_JOB_CREATED_AUTOMATIC metadata: { jobId, operation, mode, subjectType, subjectId, commonName, notAfter, profileId, source, trigger, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost }. The unattended counterpart of the event above: emitted when the renewal scheduler creates a renewal nobody asked for. source is always "automation" and trigger is "renewal_scheduler". Idempotent replays create no job and therefore no event, so the count of these events is the count of renewals actually scheduled. The windows* fields (present only when targetType is "windows-iis") name the store and IIS site/port/SNI host the scheduled renewal targets.
  • CERTOPS_JOB_FAILED metadata: { jobId, operation, jobStatus, source, mode, agentId, claimId, errorCode, errorMessage, subjectType, subjectId, needsOperatorReconciliation, reconciliationReason, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost }. Emitted when an agent reports a terminal non-success (failed, rejected, cancelled, orphaned_unknown_effect). Successful jobs are not audited individually: CERTOPS_CERTIFICATE_ISSUED already records the outcome that changes state, and a row per successful renewal per certificate would bury the failures. needsOperatorReconciliation: true means the job's real-world effect is unknown and somebody has to look. errorMessage is scrubbed of private key material and generic secrets before it is stored. The windows* fields (present only when targetType is "windows-iis") name the store/site/port/SNI host the failed job was targeting.
  • CERTOPS_EXECUTOR_EVENT_ACCEPTED metadata: { apiTokenId, executorEventId, eventType, jobId, logId, status, evidenceIds }. Emitted for every accepted job lifecycle event posted by a CertOps executor using a machine API token.
  • CERTOPS_EVIDENCE_ACCEPTED metadata: same fields as CERTOPS_EXECUTOR_EVENT_ACCEPTED. Emitted alongside it when the accepted event carried evidence.
  • CERTOPS_EVIDENCE_REJECTED metadata: { apiTokenId, jobId, eventId, eventType, rejectionCode, routeFamily }.
  • CERTOPS_KEY_MATERIAL_REJECTED — emitted whenever a request is rejected because it contained private key material; TokenTimer never accepts or stores private keys. Dashboard/API requests use { code: "PRIVATE_KEY_MATERIAL_REJECTED", method, path, body_type }; CertOps executor events (machine token) use { code: "PRIVATE_KEY_MATERIAL_REJECTED", method, routeFamily, apiTokenId }.
  • CERTOPS_GENERIC_SECRET_REDACTION_APPLIED metadata: { ...executor event fields, redactionApplied: true, redactionCount, redactedFields }. Emitted when an executor event's free-text fields matched a generic secret pattern and were redacted before storage.

Agents and the execution plane

These events cover the native TokenTimer agent (as opposed to the external executor path above).

  • CERTOPS_AGENT_BOOTSTRAP_TOKEN_CREATED metadata: { bootstrap_token_id, token_prefix, name, status, expires_at }. Emitted when an admin mints a single-use ttboot_ token to enroll an agent. The token value itself is never in the audit trail, only its prefix.
  • CERTOPS_AGENT_BOOTSTRAP_TOKEN_REVOKED metadata: { bootstrap_token_id, token_prefix, name, status, revoked_at }. Note that a successful enrollment consumes the token rather than revoking it, so a used token produces no revocation event; the CERTOPS_AGENT_REGISTERED event below is where a spent token is accounted for.
  • CERTOPS_AGENT_REGISTERED metadata: { agentId, hostname, platform, agentVersion, protocolVersion, credentialPrefix, bootstrapTokenId, signingKeyId, declaredTargetSelectors, declaredCommandProfileNames, declaredCapabilities }. Emitted when an agent enrolls, which is the moment a new machine principal gains the right to run commands on hosts in this workspace. declaredTargetSelectors and declaredCommandProfileNames are the scope the agent claimed, and are only ever sent at registration, so this event is the only record of what it asked for. declaredCapabilities is where a Windows agent's windows-cert-store-v1/iis-binding-v1 strings first appear (see Install an agent (Windows)); a Linux agent's list typically has neither. The credential is returned to the agent exactly once and never appears here; credentialPrefix identifies it. A retried registration that replays the original response is not audited a second time, so one enrollment is always one event.
  • CERTOPS_AGENT_CAPABILITIES_CHANGED metadata: { agentId, previousCapabilities, declaredCapabilities }. Unlike registration, declaredCapabilities/declaredTargetSelectors are re-sent on every heartbeat, but this event fires only when the declared capability set itself actually differs from what is already on file, for example after upgrading a Windows agent's build and picking up windows-cert-store-v1/iis-binding-v1 for the first time, or downgrading and losing them. An ordinary heartbeat with an unchanged capability set produces no event at all — there is deliberately no CERTOPS_AGENT_HEARTBEAT event, since heartbeats are frequent and a row for every one of them would swamp the log with nothing to act on. Diff previousCapabilities against declaredCapabilities to see exactly what was gained or lost.
  • CERTOPS_AGENT_RETIRED metadata: { agentId, force, reason, leasedJobs }, plus { cancelledJobIds, orphanedJobIds } when the retirement fenced in-flight work. A forced retirement (force: true) is the case worth reviewing: orphanedJobIds lists jobs whose real-world effect is unknown because the agent was removed mid-execution, which is the same state described in Reconciling interrupted jobs. Any fenced job that was a renew also raises a cert_renewal_failed alert, through the same durable outbox intent as an agent-reported or lease-reaped failure, so a forced retirement mid-renewal reaches your alert channels and not only this audit row.
  • CERTOPS_KEY_MATERIAL_REJECTED also fires on the agent routes, with { code: "PRIVATE_KEY_MATERIAL_REJECTED", method, routeFamily: "agent-protocol", agentId } and targetType: "certops_agent". An agent should never send key material, so this event on an agent route means either a misbuilt custom client or a tampered agent. It is written with no actor user, since the caller is a machine.

Renewal automation

A renewal profile is standing authority: it lets the scheduler re-run a specific command, on a specific host, against a specific CA, indefinitely and with no operator in the loop. The events below record changes to that authority.

  • CERTOPS_RENEWAL_PROFILE_DERIVED metadata: { profileId, profileName, created, managedCertificateId, renewBeforeDays, acmeKind, commandRef, caEndpoint, certPath, targetType, windowsStore, windowsBindingSite, windowsBindingPort, windowsBindingSniHost, dnsProvider, dnsZone, keyAlgorithm, keySize }. Emitted when TokenTimer generates a renewal profile from an issuance that just succeeded (see Automation). commandRef, caEndpoint and certPath are the fields that decide the real-world effect of every future renewal, so they are recorded here rather than left to be looked up. certPath is always null for a windows-iis profile (its destination is a Windows certificate store + IIS binding, not a file); targetType: "windows-iis" plus the four windows* fields are the substitute for that case. There is no user actor: nobody authored this profile. created: false means an existing derived profile was refreshed rather than a new one added. A certificate that already links to a profile is never re-derived, so this event never reports overwriting an operator's own profile.
  • CERTOPS_RENEWAL_PROFILE_DERIVATION_DECLINED metadata: { managedCertificateId, commonName, jobId, operation, agentId, derivationReason, reconciliationReason, detail, targetType }. Emitted when a certificate was successfully issued but a renewal profile could not be derived from the job payload that issued it, so the certificate is active with a real expiry but will never renew on its own. derivationReason is one of the DERIVATION_REASON_* codes (for example a missing store/binding.site on a windows-iis job payload, an incomplete DNS-01 configuration, or an operator-owned profile-name collision); detail names the specific field that failed validation, never its value, so this event cannot become a topology disclosure the way a successful CERTOPS_RENEWAL_PROFILE_DERIVED event's metadata can. reconciliationReason (renewal_profile_<derivationReason>) is the same string written to the certificate row's reconciliation_reason column. targetType is included for context even though it could not be validated; the windows store/binding fields themselves are deliberately omitted, since a decline usually means those exact fields were the ones that failed validation.
  • CERTOPS_RENEWAL_PROFILE_UPDATED metadata: { profileId, profileName, changes }. Emitted when an admin edits a profile from the Renewal automation page or PATCH .../certops/profiles/:profileId. changes lists only the fields that actually changed, including autoRenewEnabled when automatic renewal was switched on or off. The audit write and the profile update share one transaction, so a change to what runs on a host cannot commit unaudited. Only a safe subset of fields is editable; host-affecting fields are immutable, which is why this event never shows a changed certPath or commandRef.

Approval gates

  • CERTOPS_JOB_APPROVAL_GRANTED metadata: { jobId, status, payloadHash, canonicalIntentHash, reason } (reason present only when the approver supplied one). The two hashes are what the approval is bound to: if the job payload is edited afterwards, the hashes no longer match and the approval is invalidated rather than silently covering the new intent. The actor on this event is the approver, who cannot be the requester.
  • CERTOPS_JOB_APPROVAL_REJECTED metadata: { jobId, status, reason } (reason present only when the approver supplied one).

See Approval gates for the rules these events record.

Kill switch

  • CERTOPS_WORKSPACE_PAUSED / CERTOPS_WORKSPACE_RESUMED metadata: { workspaceId, previousCertOpsPaused, certOpsPaused, certOpsEnabled, certOpsActive, reason }. certOpsEnabled reflects the platform-wide flag and certOpsActive the combination of both, so a pause recorded while the platform flag is off shows certOpsActive: false. reason is operator-supplied free text.

Kubernetes controller

  • CERTOPS_CONTROLLER_OBSERVATION_ACCEPTED metadata: { apiTokenId, clusterId, managedCertificateId, observationId, resourceRecreated, targetId, certificateInstanceId }. Emitted for each accepted cert-manager observation. resourceRecreated is true when the cluster resource was deleted and recreated under the same identity, which is a legitimate but notable cluster-side change.
  • CERTOPS_CONTROLLER_PROVISION_INTENT_CREATED metadata: { clusterId, jobId, managedCertificateId, targetId }. Emitted when a provisioning intent is authorized, before the controller mutates anything in the cluster. Provisioning is re-authorized before every mutation, so this is the record of intent, not of completion.
  • CERTOPS_GENERIC_SECRET_REDACTION_APPLIED is also emitted against targetType: "certops_controller_observation" when an observation's free-text fields were redacted.

Signing-key rotation

  • CERTOPS_SIGNING_KEY_ROTATION_STARTED metadata: { signing_key_id, supersedes_signing_key_id, source }.
  • CERTOPS_SIGNING_KEY_ROTATION_COMPLETED metadata: { retired_signing_key_id, active_signing_key_id, forced, force_reason, active_agents, ack_count, source }. A forced: true completion retired the old key before every agent acknowledged the new one, and force_reason records the operator's stated justification.
Deployment-scoped, not workspace-scoped

The job-signing key belongs to the deployment, not to a workspace, so these two events are written with no workspace and no actor user (source: "certops-rotate-signing-key" identifies the CLI). Workspace-scoped and personal audit views filter by workspace or subject user and therefore will not list them; query the audit_events table directly, or use the organization-wide view as a system admin. See Rotate the job-signing key.

See Connect an external executor for how machine API tokens and executor events are used in practice.

Endpoint Monitoring

  • DOMAIN_MONITOR_CREATED metadata: { url, ssl_detected }
  • DOMAIN_MONITOR_UPDATED metadata: { url, fields_updated }
  • DOMAIN_MONITOR_DELETED metadata: { url }
  • DOMAIN_MONITOR_HEALTH_CHECK metadata: { url, status, response_ms }

Domain Checker

Emitted when a workspace member discovers publicly known subdomains for a root domain using passive discovery (subfinder), or imports selected hostnames into the workspace as SSL tokens. Lookup is rate limited per workspace and user.

  • DOMAIN_CHECKER_LOOKUP metadata: { domain, source, results, partial, tools_succeeded, tools_failed, truncated }. "source" is "subfinder". "partial" is true when only some discovery tools completed. "tools_failed" lists any tool names that timed out or errored.
  • DOMAIN_CHECKER_IMPORT metadata: { domain, source, submitted, imported, skipped, skipped_duplicate, skipped_invalid, skipped_unreachable, skipped_other_invalid, create_monitors, monitors_created, monitors_existing }. When "create_monitors" is true the event also includes { monitor_check_interval, monitor_health_check_enabled, monitor_alert_after_failures, monitor_contact_group_id }. "submitted" is the number of certificates the client selected, "imported" counts newly created SSL tokens, "skipped_unreachable" counts DNS failures (no name or temporary resolver failure), "skipped_other_invalid" is invalid skips excluding those DNS cases, and "skipped_duplicate" / "skipped_invalid" are aggregate skip counts.