Skip to content

Building a Personal Finance App with Open Banking in Europe

What it takes to build a personal finance SaaS that connects to European bank accounts. GoCardless for Open Banking, handling thousands of transactions, security and privacy decisions, and how transaction enrichment became its own product.

Building Rediant, a personal finance SaaS for European users, meant solving a problem I had personally: I wanted a tool that could pull in real bank transactions, let me categorize and budget them my way, and give me honest numbers about where my money goes. Nothing I tried did this well, especially in Europe where Open Banking exists but barely any consumer apps actually use it. This post covers what it took to build it, the decisions around Open Banking integration, how security and privacy shaped the architecture, and how the hardest part of the problem ended up becoming its own product. ## Open Banking and GoCardless PSD2 mandates that European banks provide account access to licensed third parties. In practice, that means users can authorize apps like Rediant to read their bank accounts and transaction history. The catch is that building direct integrations with individual banks is not realistic for a startup. Every bank implements the spec slightly differently, the certification process is complex, and maintaining connections to hundreds of institutions is a full-time job. I went with [GoCardless](https://gocardless.com/bank-account-data/) (formerly Nordigen) for the bank account data layer. GoCardless connects to 2,200+ banks across Europe through a single API, handles the regulatory complexity, and provides a consistent interface for account information and transaction data. For a startup, this was the practical choice: I could focus on building the product instead of fighting bank-specific API quirks. The integration flow works like this: the user selects their bank from a country-filtered institution list, gets redirected to their bank's OAuth consent screen, authenticates and grants access, and then gets redirected back to Rediant. From there, GoCardless provides account balances and transaction history through their API. The setup was straightforward, but the real challenges started after the bank connection was established. ## Handling Thousands of Transactions When a user connects their bank account, the initial sync can pull in months of transaction history. For an active account, that's easily 500-2,000 transactions in a single batch. Processing these reliably while keeping the app responsive was one of the harder engineering problems. Each transaction needs to be stored, deduplicated against any manual entries the user already created, and made available for categorization and budgeting. I process incoming transactions in batches, checking each one against existing records using a combination of the bank's transaction ID and a normalized hash of the transaction details. This prevents duplicates when a user re-syncs or when GoCardless sends overlapping data windows. The trickier part is ongoing sync. Bank connections through GoCardless expire (typically after 90 days under PSD2), and the data you get on each sync can overlap with previous syncs. I built a sync pipeline that handles incremental updates, detects expired connections and prompts the user to reauthorize, and merges incoming transactions with existing ones without creating duplicates. For the user-facing side, I display transactions with their raw bank descriptions and let users categorize them manually, by rules, or through recurring transaction templates that auto-apply. Monthly budget tracking runs on top of categorized transactions, with support for per-category budgets and month-specific overrides for irregular months. ## Security and Privacy as Architecture Decisions Building a finance app in Europe means GDPR is not optional, and beyond the legal requirements, I genuinely believe financial data deserves strong privacy protections. These principles shaped the architecture: **Authentication runs on Supabase Auth with full TOTP-based two-factor authentication.** Financial data access requires a completed MFA challenge. I cache MFA status in the session so users don't get prompted on every page load, but any sensitive operation (changing bank connections, exporting data) requires re-verification. **Minimal data collection.** Rediant stores transaction data because that's the product. But I don't store raw bank API responses beyond what's needed for the features. Personal details are kept to the minimum required for the account to function. **GDPR-compliant account deletion.** When a user deletes their account, I anonymize rather than hard-delete. Transaction records are stripped of personally identifiable information but kept for audit trail continuity. The user's profile is replaced with a hashed placeholder. If the same person signs up again later, they start fresh. I use one-way hashing for external IDs sent to GoCardless so even the banking provider can't directly identify who the customer is without access to my database. **Rate limiting and abuse prevention.** A sliding window rate limiter protects all authenticated endpoints, with progressive lockout after repeated failed login attempts. Every authenticated request goes through a cached user lookup that verifies the session before hitting the database. ```typescript async function anonymizeCustomer(userId: string): Promise<void> { const hash = crypto.createHash("sha256").update(userId).digest("hex"); await db.transaction(async (tx) => { await tx.updateUser(userId, { email: `${hash.slice(0, 16)}@anonymized.rediant.app`, name: "Anonymized User", deletedAt: new Date(), }); await tx.anonymizeTransactions(userId); await bankingProvider.deleteCustomer(userId); }); } ``` ## The Raw Data Problem After getting bank data flowing reliably, I hit the next wall: the data from banks is messy. Transaction descriptions are cryptic strings like `PP*DOORDASH*Convivo #1422 Santa Barbara, CA` or `SEPA IDEAL BETALING NR:2024 ALBERT HEIJN 1234`. They contain useful information (merchant name, location, payment method) but it's buried in inconsistent formatting that varies by bank, payment processor, and transaction type. I needed structured data: who is the merchant, what category does this belong to, where was it. Without this, the budgeting and analytics features are limited to whatever the user manually categorizes. I initially tried building pattern matching and rules within Rediant itself, but the long tail of transaction formats is enormous. Every bank formats differently, new merchants appear constantly, and international transactions add another layer of variation. This is exactly the problem that led me to build [Triqai](/works/triqai). What started as a feature inside Rediant became a standalone transaction enrichment API. Triqai takes a raw transaction string and returns structured merchant data with confidence scores: the merchant name, verified business details, location with coordinates, payment intermediaries, and a category. If you've gotten this far in building a finance app and you're facing the same raw data problem, Triqai was built specifically for this. It handles the enrichment so you can focus on the product experience instead of maintaining merchant databases and parsing rules. ## What I'd Build Differently The biggest lesson: start with the data quality problem earlier. I spent weeks building beautiful budgeting UI before realizing that the underlying transaction data was too messy for the analytics to be useful. If I were starting over, I'd invest in transaction enrichment from day one and build the UI on top of clean, structured data. GoCardless was the right choice for the banking layer. Trying to build direct bank integrations as a solo founder would have consumed months and the result would have been worse. The tradeoff is that you're dependent on a third party for a critical data pipeline, but that's a reasonable tradeoff when the alternative is not shipping at all. Building a personal finance app in Europe is a rewarding challenge. The Open Banking infrastructure is genuinely useful, the privacy requirements force you to make better architecture decisions, and there's real demand for tools that treat users' financial data with respect. The hardest part isn't any single technical problem. It's getting all the pieces (bank connectivity, data quality, security, privacy, and a usable product) working together reliably.
Blog

Blog

Browse posts in explorer mode. Click a post to open its article in a new window with metadata, images, and code blocks.

Post List

Loading workspace

Taxi the dog desktop wallpaper icon