Inspiration
Expense reimbursement fraud isn't rare or exotic; it's a well-documented, measurable cost, with the median loss from reimbursement fraud schemes sitting around $50,000 per incident according to ACFE's most recent Report to the Nations. And the attack itself takes seconds: any free online PDF editor lets someone change a number on an invoice before forwarding it to finance. Despite that, most companies still verify reimbursements by having a human eyeball an emailed attachment. We wanted to close that specific gap — not build another full expense-management suite, but make the one thing that actually stops fraud (proving a document hasn't been altered since it was issued) instant and automatic.
What it does
ProveNN verifies that an invoice submitted for expense reimbursement hasn't been altered since it was issued. Every invoice is hashed with SHA-256 and stamped with a unique reference code (plus an embedded QR code) the moment it's created. When an employee submits that invoice for reimbursement, the platform extracts the reference code, recomputes the hash, and reports match, mismatch, or not found; instantly, with no manual review. From there, a company admin reviews pending submissions, approves or rejects each one independently of the automated result, and exports everything approved straight to an Excel file ready for the reimbursement run. A separate platform console gives us, as the operator, a cross-tenant view of usage across every partner and company on the platform, without ever exposing one company's data to another.
How we built it
A Go backend (chi router) with two separate authentication paths: JWT for platform users across four roles (provider, employee, company admin, platform admin), and API-key auth for partner-style SDK integrations; sharing a single underlying invoice-creation service so both entry points behave identically. PostgreSQL holds all structured records; S3-compatible object storage (MinIO locally) holds the original PDFs. A background worker stamps the reference code and QR into each PDF with pdfcpu and computes its hash asynchronously, so issuance responds immediately instead of blocking on file processing. Verification decodes the QR with gozxing, falling back to a text search if that fails. Exports are generated with excelize. The frontend is Next.js and TypeScript. The whole thing is instrumented with Prometheus and Grafana from day one, not bolted on afterwards.
Challenges we ran into
The biggest one was making sure tamper detection survives a real edit, not just a controlled test change; opening a generated PDF in an ordinary editor, changing a field, and saving it restructures the file more than a naive scripted change does, and our reference-code extraction had to be proven against that, not just against a synthetic byte-flip. The second was multi-tenancy: with four different roles and a platform console that's deliberately allowed to see across every tenant, we had to be strict that every other code path scopes to company_id or partner_id from the authenticated principal only; never trusted from a request body; and that the one cross-tenant exception lives in its own isolated code path rather than as a bypass flag sprinkled through the normal one.
Accomplishments that we're proud of
The full loop actually works end to end; issue, download, submit, auto-verify, approve or reject, export to Excel; backed by tests, not a UI mockup standing in front of an empty backend. And it's honest about its own boundary: it clearly and correctly reports when a hash doesn't match rather than guessing, which is exactly the property that makes it trustworthy enough to build a reimbursement process around.
What we learned
That preventing tampering and detecting it are genuinely different design problems, and a system built for the second shouldn't quietly pretend to do the first. That a byte-for-byte hash is a strong guarantee but a fragile one; it breaks under any legitimate reprocessing, so knowing exactly what constitutes "the canonical file" mattered more than the hashing algorithm itself. And that automated results and human decisions need to be separate fields in the data model, not conflated into one — a clean hash match and a company's approval are two different questions, and treating them as one would have quietly removed a human's ability to reject something for reasons the hash can't see.
What's next for ProveNN
The hard part is done; the cryptographic verification core is built, tested, and working end to end, which was always the part with the most technical risk. Everything from here is additive: OCR-based verification for scanned and photographed receipts (today's version only covers digital-native PDFs), real SDK integrations with invoice-generating platforms instead of a simulated partner, self-serve partner onboarding, and richer reporting on top of the export we already have. Because the foundation is solid, we expect the next phase to be about breadth of features rather than re-architecture.
Built With
- amazon-web-services
- chi
- docker
- docker-compose
- excelize
- go
- golang-migrate
- grafana
- jwt
- minio
- next.js
- pdfcpu
- postgresql
- prometheus
- qr-code
- react
- rest-api
- river
- sha-256
- typescript
Log in or sign up for Devpost to join the conversation.