Data processing and storage
This page describes where data is stored, how it flows through the system, and which encryption mechanisms protect it.
Storage locations
All data is stored within the EU/EEA. We do not use data centres outside the EU for primary data processing.
| Component | Provider | Region | Purpose |
|---|---|---|---|
| Database (PostgreSQL) | Supabase (AWS) | eu-central-1 (Frankfurt) | All application data |
| File storage | Supabase Storage (AWS S3) | eu-central-1 (Frankfurt) | Uploaded files and attachments |
| Document storage | Cloudflare R2 | EU | Generated reports and documents |
| Application hosting | Vercel | EU (via Edge Network) | Web application and API |
| Background jobs | Railway | EU | Automatic matching, report generation |
Encryption
In transit (data in transit)
All communication between the user’s browser and Revizo is encrypted with TLS 1.2 or newer. This applies to:
- Browser → Revizo API (HTTPS)
- Revizo → Database (SSL/TLS)
- Revizo → File storage (HTTPS)
- Revizo → Third-party services (HTTPS)
- Webhook receipt (HTTPS with signature verification)
HTTP Strict Transport Security (HSTS) is enabled with max-age=63072000 (2 years), includeSubDomains, and preload to prevent downgrade to an unencrypted connection.
At rest (data at rest)
| Layer | Method | Details |
|---|---|---|
| Database | AES-256 | Supabase/AWS encrypts all data at disk level |
| File storage | AES-256 | Supabase Storage and Cloudflare R2 encrypt stored files |
| Application level | AES-256-GCM | Sensitive secrets (API tokens for accounting systems) are encrypted with a dedicated key before storage in the database |
Application-level encryption uses AES-256-GCM (Galois/Counter Mode), which provides both confidentiality and integrity protection. The encryption key is stored as an environment variable and is rotated as needed.
Data flow
User interaction
User (browser)
│
│ HTTPS / TLS 1.2+
│
▼
Vercel Edge Network (EU)
│
│ Clerk authentication
│ Session validation
│
▼
Next.js API routes
│
├── Input validation (Zod)
├── RBAC check
├── Tenant isolation
│
▼
PostgreSQL (Supabase, Frankfurt)
│
│ SSL/TLS, tenant_id filter
│
▼
Response to user
Import and reconciliation
File import (CSV/Excel/CAMT)
│
├── MIME validation
├── Size check (max 20 MB)
├── Filename sanitisation
│
▼
Supabase Storage (encrypted, tenant-scoped)
│
▼
Parser → Transactions in database
│
▼
Smart Match (automatic matching)
│
▼
Results available to user
Integrations (Tripletex / Visma NXT)
User connects integration
│
▼
API tokens encrypted (AES-256-GCM)
│
▼
Stored encrypted in database
│
▼
On synchronisation:
Token decrypted → API call → Data stored → Token removed from memory
Data categories
| Category | Examples | Sensitivity | Encryption |
|---|---|---|---|
| User identity | Name, email, Clerk ID | Personal data | TLS + platform encryption |
| Accounting data | Transactions, balances, account numbers | Confidential | TLS + platform encryption |
| Contact data | Name, email, phone, role | Personal data | TLS + platform encryption |
| Integration tokens | API keys for Tripletex/Visma | Highly sensitive | AES-256-GCM (application level) |
| Files and attachments | CSV, Excel, PDF, images | Confidential | TLS + platform encryption |
| AI conversations | Chat with AI assistant | May contain PII | TLS + platform encryption |
| Audit log | User actions, timestamps | Internal | TLS + platform encryption |
Backup and recovery
Database backup
- Daily snapshots via Supabase (automatic)
- Point-in-time recovery (PITR) — recovery to any point in time within the retention period
- Backup is stored encrypted in the same EU region as the production database
Application backup
- Daily automatic code backup via GitHub Actions
- Manual backup before major changes
- All backups are tagged and can be restored
Recovery time
In the event of an unexpected incident, the target is:
- RPO (Recovery Point Objective): Max 1 hour of data loss (PITR)
- RTO (Recovery Time Objective): Max 4 hours of downtime
The recovery procedure is documented in an internal DR runbook and is verified through an annual recovery exercise against a separate test environment.
Data segregation
Each organisation in Revizo has a unique identifier (tenant_id) that ensures data is never mixed between organisations. Isolation is enforced in three layers:
- Authentication — Organisation ID is taken from the authenticated session, never from the client
- Application layer — All database queries automatically filter on
tenant_id - Database — Row Level Security (RLS) provides an extra safety net
See Access control for more on how access is managed.
Last updated: March 2026