The deal is nearly done. SSO works, SCIM is provisioned, the questionnaire is coming back green. Then the customer’s security team sends one final email: “Can we get a sample of your audit log export?” Not a screenshot of your settings page — the export, the artifact their SOC will actually ingest. This is the moment a SaaS discovers whether its audit logs are a feature or a debug console with ambitions.
Part 1 established whose data is whose; Part 2 established whose identity is whose. This part is about evidence: who did what, to which data, when — recorded in a way that survives both an attacker and an audit. Part 2’s questionnaire playbook ended with a list of line items; audit logs are the one that separates “we take security seriously” from “prove it.” Their SOC team will ask for the export before their lawyers ask for anything else.
An audit log is not a log
Your application already produces logs — request traces, errors, debug lines. None of that is an audit log. The three systems get confused constantly, and they serve different readers:
- Application logs are for your engineers, debugging your system. Verbose, technical, short-lived, free to contain internals.
- Analytics events are for your product team, measuring user behavior. Sampled, aggregated, optimistic — losing 2% of events changes no conclusion.
- Audit events are for your customer’s security team, evidencing actions on their data. Complete, tamper-evident, tenant-scoped, retained for years. Losing one event is a reportable gap.
The distinction matters because it dictates everything downstream: audit events cannot be sampled, cannot be dropped under load, cannot expire after 30 days, and cannot live in the same bucket as your console.log output. If your “audit log” ships to the same place your debug lines go, you have a debug log.
The anatomy of an event
Every audit event answers the same questions, in the same shape, forever. Consistency is the feature — a SIEM parsing ten thousand of your events should never encounter a surprise:
The field that does the most work is tenant_id — every event carries it, no exceptions, exactly as Part 1 demanded of every table. Audit logs are tenant data: when Acme’s SOC asks “who exported our report on Tuesday,” the answer must come from Acme’s events only, filtered by the same verified claim as everything else. A cross-tenant audit viewer is a cross-tenant leak with extra steps.
What to log — and what never to log
The rule of thumb: log every action that changes access, data, or configuration. Concretely, the events buyers check first:
- Identity events — login success and failure, MFA changes, SSO configuration changes, SCIM provisioning and deprovisioning (Part 2’s joiners, movers, leavers, as evidence).
- Authorization events — role grants and revocations, permission changes, API key creation, invitations sent and accepted.
- Data events — exports, bulk reads, deletions, sharing-permission changes. An export is the single most scrutinized event in any incident review; if you log only one data action, log that one.
- Admin events — everything your own support staff does inside a customer’s tenant. Support impersonation without an audit trail is how “we’d never touch your data” becomes unfalsifiable.
The negative space matters as much. Never log passwords, session tokens, API key values, full request bodies, or raw PII you don’t need. An audit log full of secrets is a credential database with a friendlier name — it concentrates exactly what attackers want in exactly the place retention policies keep longest. Log that a password changed, never its value; log the key’s name and prefix, never the key. Redaction is not a cleanup step you add later; it is part of the event schema.
The write path: never block, never drop
Audit events are born in the request path but must not live there. A synchronous write means your logging infrastructure’s bad day becomes your product’s outage — or worse, your engineers “temporarily” wrap the call in a try/catch that swallows failures, and now events silently vanish.
The shape that works: emit the event to a durable queue, return to the user, and let a separate writer append it to the store. The queue absorbs bursts and outages; the writer owns ordering and retry. “Complete” is a hard requirement — a SIEM gap during an incident window reads as a cover-up, not a hiccup. And because events are tenant data, the pipeline applies Part 1’s discipline all the way down: tenant_id set from the verified claim at emission time, never copied from the request.
Tamper-evidence: the log must outlive the liar
Here is the uncomfortable scenario the design has to survive: the person investigating the breach suspects an insider — possibly one with database access. A UPDATE or DELETE against a normal table rewrites history silently. The countermeasure is two layers:
- Hash chaining — each event stores the hash of the previous event, so the log is a chain: edit or delete any row and every subsequent hash breaks. Verification re-walks the chain and fails at the exact gap. This doesn’t prevent tampering; it makes tampering provable, which is the actual requirement.
- Immutability at the storage layer — object-lock / WORM retention on the underlying storage, with write and delete permissions held by a role your day-to-day database credentials don’t have. Hash chains catch math; access separation catches the person who would rather delete the whole log and claim an outage.
Neither layer is exotic. Both are the difference between “our logs show nothing suspicious” and “our logs can show nothing suspicious and prove they weren’t edited.” Auditors phrase this distinction as integrity evidence, and it is a checkbox with your name on it.
The export is the product
Everything above exists to serve one moment: the customer’s security team pulling their events into their tools. That means the read side is a feature with its own roadmap:
- Self-serve per-tenant export — an API and a CSV download, scoped to the tenant’s own events, available without a support ticket. Every manual export you run by hand is a support cost and a delay in someone’s incident review.
- SIEM forwarding — streaming events to Splunk, Datadog, Panther, or the customer’s Chronicle instance. This is the enterprise tier’s real content: SSO got their people in (Part 2); the SIEM feed keeps their SOC watching.
- Retention as a plan tier — 90 days self-serve, a year or more on enterprise, matching whatever their compliance regime demands. Retention is cheap to store and expensive to backfill; decide tiers before the questionnaire decides for you.
This is also why WorkOS-style vendors sell audit logs alongside SSO: the buyers, the questionnaire line items, and the admin portal are the same. If you bought your identity layer in Part 2, check whether audit logs came with it before building your own.
Where the series goes next
Current thinking for the next door: billing security — webhook signature verification, the PCI boundary you do and don’t own, and why the payment flow is the one place “we’ll add checks later” becomes fraud. The SOC 2 report itself — the document the questionnaire keeps asking for — is the other strong candidate. As before: the order is decided when we get there.
Practice
- Write the audit event schema for your current project: the seven fields, past-tense action names, and the explicit list of what is excluded (secrets, bodies, PII). If the exclusion list feels unnecessary, you haven’t grepped your logs for a token lately.
- Pick three actions — an export, a role change, and a support impersonation — and trace each from request to stored event. Where would an event get lost if the database hiccuped mid-request? That’s the gap the queue fills.
- Simulate the insider: with your own database credentials, try to
UPDATEyesterday’s audit row. If you can, your tamper-evidence is a blog post, not a control — add the hash chain and move delete permissions to a role you don’t hold.