The problem
Nigerian SME tax filing in 2024 was a four-hour manual process per client. Not because the tax rules are especially complex — they are not — but because the tooling forced accountants through a sequence of manual data entry steps, PDF exports, web portal submissions, and phone calls to confirm receipt. Every step was a potential error. None had a machine-readable audit trail.
The Nigerian Revenue Service (NRS) enforces filing deadlines strictly. Missing one means penalties. Getting one wrong means an audit trail that doesn't match the submission. The friction was not just time — it was regulatory risk sitting inside a manual process.
TaxBridge was built to make correctness a property of the system, not a consequence of a careful accountant.
What was built
A Turborepo monorepo with four surfaces:
- React Native / Expo SDK 54 mobile app — the primary filing interface for accountants in the field, offline-first with a SQLite sync queue
- Next.js 15 admin dashboard — multi-client management, filing history, audit trail viewer
- Fastify 5 API — submission orchestration, NRS DigiTax integration, BullMQ queue management
- Java 17 / Spring Boot 3 compliance engine — jurisdiction-specific tax computation (VAT, WHT, PIT, CIT, CGT) with compile-time rule validation
The monorepo enforces a single TypeScript schema across mobile, web, and API surfaces. A rule change in the compliance engine propagates to every client automatically at the type level.
Key decision: PostgreSQL RLS over application-layer tenant filtering
Chosen: PostgreSQL Row-Level Security at the database engine
Over: WHERE tenant_id = ? filtering in the application layer
Because: NRS audit scrutiny demands proof that tenant data cannot cross-contaminate. "We filter in the application" is not proof — it is a claim that is unverifiable by the auditor without reading every query path. RLS policy attached to the database schema is enforceable and verifiable. Even if an application bug omits a WHERE clause, the database rejects the query. Tenant data is mathematically invisible across boundaries, not conditionally filtered.
Key decision: Idempotent BullMQ queues for NRS API rate limits
Chosen: BullMQ with stable job identifiers and pre-execution deduplication
Over: Synchronous NRS API calls with simple 429 retry
Because: The NRS API enforces 30 requests per minute per TIN. Filing windows — when many SMEs submit simultaneously — produce burst traffic that exceeds this limit. A simple retry loop works until a job fails mid-execution, because the retry re-sends the same request. The NRS API processes it again: duplicate submission, real consequence. Every BullMQ job in TaxBridge carries an idempotency key derived from the submission content. Before execution, the worker checks whether that key has already succeeded. If it has, the job completes without re-submitting. No duplicate submissions, no double-processed tax returns, even through mid-request server failure.
Key decision: Hash-chained audit trail as a data model property
Chosen: Cryptographically bound append-only audit records
Over: Timestamped log table with standard UPDATE access
Because: The NRS requires an immutable audit trail. A log that can be retroactively edited — by anyone with database access — is not an audit trail. Every audit event in TaxBridge is cryptographically bound to the previous event's hash. Any retroactive modification breaks the chain at the point of modification, detectable without access to the original data. Integrity is a structural property of the data model, not a policy enforced at the application layer.
Constraint
The NRS API rate limit (30 req/min per TIN) becomes a hard constraint during filing deadlines, when concurrent submissions are highest. The system must handle burst traffic without client-visible failure and without producing duplicate filings on retry.
Offline filing is a real user requirement. Accountants in the field work on spotty Nigerian mobile networks. Every submission must survive the mobile app being killed mid-flight.
Results
- 4h → 15min filing time per client (measured end-to-end, accountant-reported)
- sub-150ms real-time tax calculations at p99 under load (Redis rule cache)
- 95% test coverage — every jurisdiction rule, every edge case in withholding rate calculation, every submission state transition has a corresponding test
- Zero data-loss record — offline SQLite queue survives app kills; BullMQ replays on reconnect with idempotency intact
Lessons
Compliance-critical systems fail most often at the seam between external API rate limits and internal queue management. The naive assumption is that external APIs are reliable enough to call synchronously. They are not, especially not under load, especially not with enforcement windows. Building the queue before the first incident — not retrofitting it after — is the only approach that holds.
The 95% test coverage figure is not a quality target. It is a compliance artifact. NRS audit inquiries ask for evidence that calculation logic was validated. The test suite is that evidence.
Status
Active build. NRS DigiTax 2026 / FIRS integration in progress. Source available to verified employers on request.