fintech April 29, 2026 Sunny Sharma
The audit trail is a first-class component, not a logger
Most teams treat the audit trail as backend telemetry. Then a regulator asks what the user actually saw, and the answer is in the wrong system. Here's how we design the client-side audit hook from the first commit.
frontend engineering-leadership
The conversation usually starts the same way. The compliance lead has a draft regulator query on their desk. The query asks what a specific user saw on a specific screen at a specific timestamp six months ago. The engineering team pulls the server logs, finds the user’s session, finds the API responses the server sent. And then someone asks: but did the user actually see that? Did the data render? Did they click anything? Did the screen freeze? The server logs answer none of these. The frontend logs, if they exist at all, are in a different system, on a different retention policy, and were never designed as evidence.
This pattern keeps happening because most teams design the audit trail as a logging concern, which means it ends up where logging lives: in a backend telemetry pipeline that captures requests and responses, not in the part of the system that knows what the user actually saw. The fix is small in code and large in posture. Design the audit trail as a first-class component of the frontend modernization, not as an afterthought to it.
What the audit trail actually has to capture
For a regulated surface (banking, insurance, payments, healthcare with PHI) the audit trail needs to answer four questions about any user action that could later become a compliance question. What did the user see on the screen? What state was the screen in when they acted? What did they do? And when did the system acknowledge that action?
That last point is where most teams stumble. The server log will tell you that a payment instruction was received at 9:17:04.337. It will not tell you that the operator’s screen had been showing stale data for the previous 1.2 seconds because the websocket had hiccupped, and the operator did not know they were acting on stale data. The regulator’s question is whether the operator had reasonable information at the time of the action. Only the frontend can answer that.
The hook we use
Every regulated surface we build has a single audit hook that wraps the components users interact with. The hook captures a structured event for every state change that could later be relevant: the visible state of the cell at moment-of-click, the user’s identity and session, a high-resolution timestamp, the data revision (a monotonic counter the websocket layer increments on every authoritative update), and the action itself. The events go to a durable client-side queue first (IndexedDB, with a fallback in-memory buffer for older browsers) and from there to a server endpoint that accepts the audit stream as a separate channel from the API.
Two things matter about this design. The audit channel is not the same channel as the API. An order submission goes over one transport and produces a server log; the audit event describing the screen state at the moment of submission goes over a separate transport and produces a different log. The two are correlated by a request ID generated on the client. This means the audit trail still gets through if the API is down, and the API can still get through if the audit endpoint is down. Compliance gets to design the retention policy on the audit channel without coupling it to product engineering’s API choices.
The second thing is that the audit hook is queueing-first, not posting-first. If the network drops, the audit events sit in the local queue and get reconciled when connectivity returns, with the original timestamps preserved. This is why we do not use a logger. A logger is fire-and-forget. An audit trail has to survive a network partition, a browser crash, and a user who refreshes the tab in the middle of submitting.
Where this changes the engineering work
Treating the audit trail as a component instead of a log changes a handful of decisions early. The websocket layer needs to expose a revision counter, which most off-the-shelf clients do not. The state store needs to make the visible state queryable at click time, which means avoiding patterns that compute display state inside the render function (tempting in React, painful in audit). The build needs to include the audit module as a release-blocking dependency, which means it gets the same security and bundle-size discipline as the rest of the product code. None of this is hard. All of it has to be decided early, because retrofitting an audit hook into an established component tree is the kind of work that takes a quarter and produces gaps regardless.
The shorthand we use internally is: if you would not be comfortable showing a regulator the audit data on its own, the audit trail is not finished. That is a higher bar than telemetry. It is the right bar for a screen that handles money.
What this is not
The audit trail isn’t analytics. We don’t store it next to the product analytics events, and we don’t share its retention policy with marketing or growth. It isn’t security telemetry either. Security has its own logging needs around authentication, authorisation, and anomalous behaviour, and conflating the two ends with both being half-built. And it isn’t a substitute for server-side audit. The server still needs its own immutable log of every API action. The client audit is a complement that captures the part the server can’t see.
When you build it this way, the conversation with the regulator gets shorter. The compliance lead gets the answer in a query, not a quarter. We shipped exactly this pattern on a payments-console refit and the first regulator query landed three months later. It was answered in four hours.
02. Newsletter
More on regulated FinTech engineering, monthly.
Practitioner notes on the work behind real FinTech systems. One short essay or case study a month. No marketing, no sequences. Reply to talk.
Prefer RSS? Subscribe to the feed →
Related work
More from the blog
All posts