VibeWeek
Home/Grow/Customer-Facing Audit Logs: Chat Prompts

Customer-Facing Audit Logs: Chat Prompts

⬅️ Back to 6. Grow

When customers ask "who did what when in our workspace?" — admins want to see logins, permission changes, data exports, settings changes, integration activations, sensitive document accesses. This is a customer-facing audit log: the activity record visible to the customer's admin, not the internal audit log you keep for your own debugging. Distinct from internal audit logs (engineering / compliance use); this is a product feature that customers actively want, especially in regulated industries (healthcare, finance, education) and at the enterprise tier (SOC 2 audits, security reviews, post-incident forensics).

Done well, customer-facing audit logs sell enterprise deals (security teams demand them), simplify post-incident investigation for customers, and show up as a checkmark on their security questionnaires. Done poorly, they're an unindexed dump of events nobody can search, contain misleading or inconsistent data, or miss the events that matter.

When This Belongs

Use customer-facing audit logs when:

  • You sell to mid-market+ / enterprise (security questionnaires demand it)
  • You handle sensitive customer data (financial, health, PII)
  • Your product has multi-user workspaces with admin / member roles
  • Customers face their own compliance requirements (SOC 2, HIPAA, FINRA, GDPR)

Don't bother when:

  • Pre-revenue / consumer product
  • Single-user / no admin roles
  • No regulated customers

What to Log

The right scope: events admins genuinely need to investigate. Wrong scope: every database write or every page view.

Core Categories

Authentication events:

  • User signed in (with method: password / SSO / API key)
  • User signed out
  • Failed login attempts
  • Password resets
  • MFA enabled / disabled
  • Session expired / revoked

Permission + access changes:

  • User added / removed
  • Role changed (admin → member, etc.)
  • Workspace settings changed
  • API key created / revoked
  • Integration installed / removed

Data events:

  • Sensitive document accessed (read by user)
  • Data exported (CSV / GDPR / etc.)
  • Data deleted (especially bulk deletes)
  • Records modified (key fields)

Configuration changes:

  • SSO config updated
  • Domain verification changed
  • Billing changes (plan upgrade / downgrade / payment method)
  • Webhook destinations changed

Security events:

  • Suspicious activity detected (anomalous access patterns)
  • Account locked (too many failed attempts)
  • IP allowlist changed
  • 2FA bypass attempts

What NOT to Log (or log internally only)

  • Every page view (too noisy)
  • Every API call (use rate-limit logs internally)
  • Successful data reads (unless sensitive document)
  • Every minor field edit
  • Background system events (cron jobs, queue processing)

The principle: log events admins might investigate. Don't log every database write.

Data Model

I'm building customer-facing audit logs for my B2B SaaS.

Schema (Drizzle):
```sql
audit_log_events:
  id (UUID), workspace_id (FK; for tenant isolation),
  actor_user_id (nullable; null for system events),
  actor_type (enum: 'user' | 'system' | 'api'),
  event_type (e.g. 'user.invited', 'permission.changed', 'export.created'),
  event_action (enum: 'created' | 'updated' | 'deleted' | 'accessed'),
  target_type (e.g. 'user', 'workspace', 'document'),
  target_id (UUID),
  metadata (JSON: event-specific details — old vs new values, IP, user agent, etc.),
  ip_address, user_agent,
  created_at (immutable; never updated)

-- Append-only constraints:
-- REVOKE UPDATE, DELETE on audit_log_events FROM all roles
-- Application code only INSERTs

-- Optional: index on (workspace_id, created_at DESC) for query performance

Implement:

  • The schema
  • A logEvent(workspaceId, actorUserId, eventType, target, metadata) helper
  • Helper called from key code paths (not on every database write)
  • Async write (if scaling matters)
  • 7-year retention (or per regulation)

Stack: Next.js + Drizzle + Postgres.


## Append-Only Discipline

Audit logs MUST be append-only. Never updated; never deleted (except by retention policy after years). The pitfall: a bad actor with DB access modifies past events to hide their tracks.

### Enforcement Patterns

1. **Database role permissions**: revoke UPDATE/DELETE on the audit_log table from the application's role; only INSERT.
2. **Hash chain**: each row contains hash of previous row. Tampering breaks the chain. Verifiable later.
3. **Separate database**: audit log lives in a different DB from app data. Different access controls.
4. **WORM storage**: write-once-read-many storage (S3 Object Lock, etc.) for archival.

For most B2B SaaS: option 1 (DB role permissions) is sufficient. Add option 2 (hash chain) for high-stakes regulated industries.

## Customer-Facing UI

Build the audit log viewer at /workspace/admin/audit-log:

UI:

  • Reverse-chronological list of events
  • Per event: timestamp, actor name + avatar, event description ("Invited user@example.com as Admin"), target
  • Expandable for full details (IP, metadata, JSON payload)
  • Filters:
    • Event type (auth / permission / data / config / security)
    • Actor (specific user; system; API)
    • Date range (last 24h / 7d / 30d / custom)
    • Target (specific user / document / etc.)
  • Search (full-text across event descriptions)
  • Pagination (cursor-based; sort by created_at DESC)
  • Export to CSV (for compliance audits)

Permissions:

  • Workspace admins can see all events
  • Members might see only events involving them (configurable)
  • Audit log itself logs who viewed it (meta!)

Performance:

  • Index on (workspace_id, created_at DESC)
  • Cache common filter queries
  • Stream large exports

Stack: Next.js + Drizzle + TanStack Query + shadcn/ui + cmdk for search.


## Event Description Quality

The hardest part: making events human-readable.

### Bad

event_type: user.permissions.changed metadata: { user_id: 'abc', old_role: 'member', new_role: 'admin' }


Display: "user.permissions.changed for abc"

Useless.

### Good

Display: "Sarah Chen changed Alex Roman from Member to Admin in marketing-team workspace"

Implementation:

```ts
function describeEvent(event) {
  switch (event.event_type) {
    case 'user.permissions.changed':
      return `${actor.name} changed ${target.name} from ${event.metadata.old_role} to ${event.metadata.new_role}`;
    case 'user.invited':
      return `${actor.name} invited ${target.email} as ${event.metadata.role}`;
    case 'export.created':
      return `${actor.name} exported ${event.metadata.row_count} ${event.metadata.resource_type} records`;
    // ... per event type
  }
}

Maintain a description-rendering function per event type. Worth the effort; transforms log from useless to useful.

API Access for Audit Logs

Enterprise customers want programmatic access to pipe to their SIEM.

Provide API endpoints for audit log access:

GET /api/audit-log
  - Query params: workspace_id, since, until, event_type, actor_id, target_id
  - Pagination: cursor + limit
  - Response: array of events as JSON

GET /api/audit-log/export
  - Async export in CSV / JSON / NDJSON format
  - Returns job_id; poll for completion

Webhook subscription:
  - Customer subscribes to audit_log.event webhook
  - Each event POSTed to customer endpoint (sumolicious / Datadog / Splunk / etc.)
  - HMAC signed
  - Retry on failure

SIEM integrations:
  - Pre-built connectors for Datadog, Splunk, Sumo Logic, Elastic
  - Or just provide standard webhook + let them configure

Permissions:
- API requires admin-tier API key
- Logged in audit log itself (meta)

Stack: Next.js + Drizzle + your webhook infrastructure.

Retention Policy

Decide retention:

Standard tiers:
- Free / Starter: 30-90 days
- Pro / Business: 1 year
- Enterprise: 7+ years (regulatory)

Implementation:
- Cron job daily: delete events older than retention period
- Customer-facing message: "Audit log retained for [X] days on your plan"
- Upgrade path: enterprise plan extends retention

Compliance considerations:
- HIPAA: 6 years
- SOX: 7 years for public companies
- GDPR: as long as needed for stated purposes; document
- Some industries (financial): 10+ years

For regulated customers, offer "Long-term retention" as an enterprise feature.

Stack: Next.js + Vercel Cron + Drizzle.

Common Pitfalls

Logging too much. Every page view + every database write = log too noisy to use. Log events admins need; not raw activity.

Logging too little. Skipping permission changes / data exports because "we'll add later." Customers can't investigate; you fail security review. Add core events at MVP.

Logs in the wrong place. Audit logs in same table as app data; same access; bad actor edits both. Separate access controls; revoke UPDATE/DELETE.

Mutable logs. Allowing edits / deletes. Append-only or audit log itself is suspect.

Generic event descriptions. "user.action.completed" with cryptic metadata. Per-event-type descriptions.

Missing actor. Who did this? Unknown. Always capture actor (user / system / API key).

Missing IP / user agent. Customer wants to know if event came from US or VPN. Log enough context.

No filter / search. 10,000 events; admin can't find the one they need. Search + filter mandatory.

No export. Customer needs to send to auditor; can't get data out. CSV / JSON export.

No API for SIEM ingestion. Enterprise customers want logs in Splunk / Datadog. Provide API or webhooks.

Retention too short. Customer needs 1-year-old event for audit; logs deleted. Match retention to customer compliance needs.

Retention too long without performance plan. 7 years × millions of events = slow queries. Partition by date + archive old.

No documentation. Customers don't know what's logged. Public docs listing event types.

Logs leaking PII unnecessarily. Logging full document content "for audit." Now logs ARE the leak. Log references (IDs); not content.

Cross-workspace contamination. Bug exposes another workspace's events to wrong admin. Tenant isolation strict + tested.

Audit log query timeouts. Querying 10M events without index. Index on (workspace_id, created_at).

No real-time. Admin investigates incident; audit log shows events from yesterday. Make near-real-time (lag <1 min).

Audit log not itself logged. Bad actor reads audit log to plan attack; nobody knows. Log audit-log access.

Inconsistent timestamps. Some events in UTC; some local; confusing. Always UTC; display in user's TZ.

Forgetting system / API events. Logging only user actions; missing system / cron / API. Log all actor types.

No tamper detection. Hash chain absent; tampering invisible. For high-stakes: hash chain + verification.

Exports without authentication check. Anyone with workspace access exports audit log. Restrict to admins.

Documentation for Customers

Provide documentation explaining your audit log:

Sections:
1. **What's logged** — list of all event types with descriptions
2. **What's NOT logged** — explicit list (page views, etc.)
3. **Retention** — per plan tier
4. **Access** — who can view; how to view; how to export
5. **API access** — endpoint docs
6. **SIEM integration** — supported destinations
7. **Compliance** — what regulations this satisfies

Why this matters:
- Security teams demand it during procurement
- Compliance audits ask
- Customer admins need to know what to expect

Treat your audit log docs like API docs — keep them updated.

See Also