What nobody tells you about building fintech: compliance is a product feature
I walked into FinanceOps thinking I was building software. Six months later I realized I was building a compliance platform that happens to have software around it.
The moment I realized I was not building a software product was a Tuesday afternoon in a conference room with our compliance advisor. I had just spent two weeks building a beautiful invoicing feature. Clean API, responsive UI, real-time preview. I was proud of it. She looked at the demo and asked one question: “Where is the audit trail?” I did not have one. Every financial mutation in our system happened silently. No record of who changed what, when, or why. In a regulated financial product, that is not a missing feature. That is a showstopper.
That conversation changed how I think about every line of code I write at FinanceOps. Compliance is not a checkbox you handle after the product works. It is a design constraint that shapes the architecture from day one. If you treat it as an afterthought, you will rewrite your entire data layer six months in. I know because I almost had to.
In that first stretch at FinanceOps, I was still learning how to wear the Head of Engineering title without hiding behind it. It also builds on what I learned earlier in “The mass migration: moving 200 API endpoints to TypeScript strict mode in one sprint.” The only credibility that mattered was whether the decision survived contact with real money, ugly edge cases, and the next person I would eventually hire. That same bias toward strict boundaries later shaped how I approached ftryos and pipeline-sdk: make correctness boring before you make the API clever.
Audit Trails as First-Class Citizens
After that meeting I spent a week redesigning our data layer. Every table in our database now has a corresponding audit table that captures every mutation with a complete before and after snapshot. The implementation uses PostgreSQL triggers so the audit trail is enforced at the database level, not the application level. No developer can accidentally skip it.
CREATE TABLE payment_audit ( audit_id UUID PRIMARY KEY DEFAULT gen_random_uuid(), payment_id UUID NOT NULL, action TEXT NOT NULL CHECK (action IN ('INSERT', 'UPDATE', 'DELETE')), old_data JSONB, new_data JSONB NOT NULL, changed_by UUID NOT NULL, changed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), reason TEXT);
CREATE OR REPLACE FUNCTION audit_payment_changes()RETURNS TRIGGER AS $$BEGIN INSERT INTO payment_audit ( payment_id, action, old_data, new_data, changed_by ) VALUES ( COALESCE(NEW.id, OLD.id), TG_OP, CASE WHEN TG_OP = 'INSERT' THEN NULL ELSE to_jsonb(OLD) END, to_jsonb(NEW), current_setting('app.current_user_id')::UUID ); RETURN NEW;END;$$ LANGUAGE plpgsql;The key design decision was storing the complete row snapshot in both old_data and new_data rather than just the changed fields. This makes compliance queries simple: for any payment, at any point in time, you can reconstruct the exact state of the record. Auditors love this because they can answer “what did this payment look like on March 15th” with a single query.
The Four Compliance Principles That Shaped Our Architecture
After working with our compliance advisor for three months, I distilled four principles that now drive every architectural decision at FinanceOps.
- Immutability: Financial records are never updated in place. Every change creates a new version. The original record persists forever. This maps naturally to an append-only audit log and soft deletes.
- Traceability: Every action in the system traces back to a specific user, API key, or system process. We pass actor context through every function call. No anonymous mutations exist anywhere in the codebase.
- Completeness: Every financial calculation must produce the same result if re-run with the same inputs. We store the calculation inputs alongside the outputs so reconciliation can verify independently.
- Timeliness: Compliance reports have deadlines. Our reporting queries must run in under 30 seconds for any time range. This drove our indexing strategy and the decision to pre-aggregate certain financial summaries.
These four principles sound obvious in hindsight. They were not obvious when I was heads-down building features. The natural developer instinct is to optimize for the happy path: create payment, charge card, record transaction. Compliance forces you to optimize for the unhappy paths: disputes, reversals, audits, investigations, and regulatory inquiries. Those paths touch every table and every function in your system.
What This Means in Practice
Every API endpoint at FinanceOps now follows the same pattern. Validate input. Record intent. Execute action. Capture result. Log audit trail. This five-step pattern adds roughly 20 lines of code per endpoint but it has eliminated an entire category of compliance findings. Our last audit had zero critical findings related to data traceability.
The upfront cost is real. Compliance-first architecture is about 30% more code than building without it. But the alternative is rewriting your data layer when a compliance audit finds gaps, which is what three of the five fintech startups I have talked to ended up doing. Two of them lost customers during the rewrite because they could not produce audit reports for ongoing regulatory inquiries.
That was the pattern of my first months at FinanceOps: I did not have management scar tissue yet, so I earned trust by making technical decisions that stayed boring under pressure. The same bias toward strict defaults still shows up in ftryos and pipeline-sdk today.
If you are building fintech and you do not have a compliance advisor in the room before you write your first database migration, stop and go find one. The architecture decisions you make in month one determine whether your compliance story in month twelve is a strength or a liability.
Compliance is not the enemy of shipping fast. It is a design constraint, like performance or accessibility. Once you internalize it as a constraint rather than a burden, it actually simplifies decisions. Every ambiguous architecture question has a clear tiebreaker: which option produces a better audit trail? That clarity is a gift.