Audit Logs Are a Product Feature, Not a Compliance Checkbox
Audit logs earn their keep during an incident. Build them like a product feature — streamable, consistently structured, tamper-evident, and retention-aware.
Most teams build audit logs to close a deal. A security questionnaire asks whether you keep them, someone writes a table to a database, exposes a CSV export, and moves on. The box is checked. The feature is, for all practical purposes, dead on arrival.
Then a customer has an incident. They open your audit log for the first time and start asking questions your schema can't answer. That is the moment audit logging is actually graded, and by then it's too late to fix. The first time a customer really reads your audit log is during their incident, which is the worst time to discover gaps. If you design for that moment instead of for the questionnaire, you end up building something different.
The checkbox version fails the people who need it
A compliance checkbox optimizes for existence: can you produce a record? An incident optimizes for use: can someone reconstruct what happened, quickly, inside the tools they already trust?
Those are not the same product. Enterprise security teams expect to stream events into their SIEM, not download CSVs. A CSV export assumes a human will sit down, open a spreadsheet, and eyeball rows. That is not how a modern security operation works. Events belong in the same pipeline as the rest of the organization's telemetry, where they can be correlated, alerted on, and searched alongside everything else. If your audit log is a button that produces a file, you've built something the buyer's security team will politely ignore.

Streaming is also the part teams most underestimate, because every SIEM wants events a little differently. WorkOS Log Streams send audit log events from your application straight to your customers' SIEM platforms like Datadog, Splunk, and AWS S3. Once a stream is configured, customers can search your application's events alongside the rest of the data already in their SIEM. For anything off the beaten path, a generic HTTP POST destination streams events to any provider that accepts an HTTP POST with a JSON payload. And the setup doesn't have to route through your support queue: customers can configure it themselves in the WorkOS Admin Portal.
The difference shows up in adoption. A downloadable report gets pulled during an audit and forgotten. A stream that lands in the customer's SIEM becomes part of their daily operating picture.
A schema is a contract, and consistency is the whole point
The fastest way to make an audit log useless is to let every event type invent its own shape. A login event with one set of fields, a permissions change with another, an API call with a third. Now anyone trying to reason across them has to learn three formats and reconcile them by hand.
A useful event schema is consistent across every event type: actor, action, target, timestamp, and context. Who did it, what they did, what they did it to, when, and the surrounding detail. When every event answers those five questions the same way, an investigator can filter, pivot, and correlate without a decoder ring. Asking what a single actor touched in the last hour becomes one query instead of a research project.

Consistency is easier to promise than to hold. It usually erodes one well-meaning pull request at a time, when a new event ships with a slightly different field name and nothing stops it.
That's an argument for defining the shape somewhere central rather than in each call site. In WorkOS you declare the actions, targets, and metadata you want to record in the Dashboard, and the JSON schema editor enforces strongly-typed metadata so your app can only send consistently structured, valid event data. The schema stops being a convention people remember and becomes something the pipeline checks.
The structural pieces are modeled explicitly too. Actors can be user, anonymous, or system types, and custom metadata attaches to the top-level event, the actor, or any target on it, so context has a defined home instead of being flattened into a message string. Emitting events works through SDKs in Node.js, Ruby, Python, PHP, Go, .NET, and more.
Trust comes from storage, not intentions
An audit log is only as good as your ability to say, under pressure, that nothing in it was altered. That guarantee doesn't come from good intentions or access controls alone. It comes from how the data is stored. Append-only storage with tamper-evidence is what makes logs trustworthy in an incident.
Append-only means records are written once and never updated or deleted in place. Tamper-evidence means any modification is detectable, typically through cryptographic hashing that chains each record to the ones before it, so a change anywhere in the history breaks the chain and shows itself.
This matters most in exactly the scenario the whole feature exists for. During an incident, especially one involving a compromised account or an insider, the integrity of the log is the thing standing between an investigation and a dead end. If an attacker with database access can quietly edit the evidence, the log is worse than useless — it's a false sense of security. This is also the argument against keeping audit events in an ordinary application table that half your services can write to. The moment the log lives in the same mutable store as your product data, its evidentiary value depends on every code path that touches it.
Retention is a customer setting, not a company default
There is no single correct retention period, which means any hardcoded one is wrong for somebody. Retention requirements differ by compliance framework, so retention must be configurable per customer. One customer's framework demands a year; another has data-minimization pressure pulling the other direction and wants the shortest defensible window.
If you bake one retention period into the platform, you force every customer into a policy that fits their obligations by accident. Make it a setting, scoped per customer rather than per your convenience. WorkOS retains audit logs for 30 days by default, and an organization's retention period can be extended to 365 days with an API call. That makes a longer window something you can offer as part of an enterprise package, set programmatically per organization, instead of a migration project.

Build for the incident, not the questionnaire
Every one of these choices points the same direction. Stream instead of export because the security team lives in their SIEM. Enforce one schema because an investigator can't afford to learn five. Store append-only because the record has to hold up when someone is actively trying to hide. Make retention configurable because the right answer belongs to the customer.
Availability belongs on that list too, once the log is load-bearing in someone else's investigation. WorkOS guarantees 99.99% availability for customers using SSO, Directory Sync, and Audit Logs in its SLA, and when a customer wants to look directly rather than through their SIEM, logs are available in the WorkOS Dashboard and to your customers through a private URL.
The compliance checkbox asks whether the feature exists. The incident asks whether it works. Design for the second question and you pass both, because the day a customer finally reads their audit log in anger is the day they decide whether they can keep trusting you with their data.
If you want to see what that looks like before you build it, sign up for WorkOS for free today and try Audit Logs: define a few events, emit them, then point a stream at your own SIEM and read it the way your customer would.