Jan Demel7 min

How We Built the Backend for a Banking App in 2 Months

EngineeringProductJul 25, 2022

It all began when STRV was tasked with building the MVP of a banking application for Nav (online banking for business financing) in two months. Impossible? Generally, yes, but we made it happen. This article explains how.

When you start a project like Nav Business Checking with such a tight deadline, the first thing to remember is: Don’t even think of developing everything yourself. It’s better to use existing services, SaaS and other options on the market.

This advice doesn’t only apply to a crazy deadline. Whatever the scenario, if there are tools that can do the job, why reinvent them?

On top of that, using battle-tested third parties will improve the security of the whole application. A third-party provider whose only product is its API or service is likely to have its implementation better secured than you would since it’s the provider’s only job. This is especially relevant when talking about banking.

Let’s talk about what we did during those two months, which tools we used (and why) — plus some advice that may help you on a similar project.

“We gave the STRV team an absurdly compact timeline to launch a full-featured small business bank account. Despite this, my confidence in their ability to deliver was never in doubt. STRV’s ability to fully grasp the interoperability of the multiple services has resulted in early signs of success measured via customer engagement.”

– George Kurtyka, VP & Head of New Product Strategy at Nav

How_We_Build_BE-2

Banking as a Service

Probably the most important tool we used is Unit, a banking-as-a-service platform that basically solves the real magic behind the backend for you. Take advantage of it and bam, you have your banking app, DONE.

Just kidding.

Unit doesn't solve everything — but it does solve quite a large scale of things! Its API provides you with features allowing the user to issue a credit card (both virtual and physical), open an account, make a payment (ACH), see transactions… and, according to their roadmap, more features are coming!

So... what’s the catch? Why do you need another service? Isn't Unit enough?

Well, your users need a way to fund their accounts. You have multiple options — but it would be great to let users connect their existing bank accounts to your app, right?

Funding Account

As mentioned, you have several options. But the ones that make the most sense are:

  • Connect a user’s existing banking account and allow using it for funding
  • Manually send money from the user’s banking account

The second option is pretty doable without any service. Users just send money. Everybody should be able to do that. But to enable funding an account with a connected banking account, you need some other external service. Enter Plaid.

Using Plaid (more specifically, Plaid Link) in combination with Unit is fairly simple — you redirect the user to Plaid Link (on the frontend side of the app) and, after the user goes through all necessary steps, you receive a public token. Then, exchange tokens and you can create a new linked counterparty in Unit using the Plaid processor token. This freshly-created counterparty can be used to fund an account even after the initial connection!

After some time, the connection may expire. To be prepared for this scenario, you should save the public token returned from Plaid Link in your database to use it for something called "Update Mode." But here comes a warning…

Don’t store such unencrypted data in your database, even when you use an encrypted database-as-a-service platform. For such a security-sensitive app, double-layered encryption is usually a great (and crucial) idea.

Encryption as a Service

We now know there’s data you might want to encrypt before storing. To encrypt the data before you insert it into your database, you have two options:

  • Do the encryption yourself (but, as explained, doing things yourself isn't the best idea in this scenario)
  • Use an external service

Encrypting the Data Yourself

This one’s fairly simple. There are libraries (Node has Crypto) that let you encrypt the data, so it’s quite easy to get an encryption key from a key management system or to get environment variables and encrypt the data. Then, you can save it without needing to set anything up; everything is ready to be used and your app is easy to deploy to any environment — because you don't need any external service. But, again, there’s a catch.

The first issue is performance. Encrypting the data is an expensive operation in terms of computation power. If you use asymmetric encryption, it is even more expensive.

Second, even though you might think your app is secured, this may not be the case. What if you lose the encryption key or it’s leaked? Suddenly, anyone who has the key can decrypt your stored data.

Using an External Encryption Service

As you can probably guess by now, in our case, we didn’t do the encryption ourselves. There are many encryption-as-a-service platforms on the market and, since Nav was already utilizing Vault in its infrastructure, that’s what we used.

Vault doesn't serve solely as an encryption-as-a-service platform, but also as secret key storage. It has some other interesting features, too.

Apart from the biggest pros — like offloading encryption-needed processing power to external services — it also has procedures to rotate encryption keys, which are neither saved nor stored on the API side. How does that work? Basically, to encrypt the data, you send a request to Vault with base64 encoded data and it returns encrypted data. For decoding, it is vice-versa.

Auth0

At STRV, we usually handle authentication ourselves. We have a user table in the database with a password hash (if you’re curious about hashing passwords, read our mighty backend engineer Robert’s article about backend security). But again, for high-security apps, it’s not a great idea to implement this yourself. What if you have a password leaked?

Just think: Wouldn't it be cool to have a service that could handle this for you and prevent anyone with stolen credentials from logging in while notifying the owner of the account at the same time? Well, that service exists, and we used it: Auth0.

Not only does it provide you with the features mentioned above but, at the same time, it lets you connect your custom database to store the credentials — so you can use it anywhere (for example, in some of your other apps).

Custom Features

Of course, we didn't just glue multiple services together without any further work. Let's go through some custom features that we implemented.

Accounts Management

By default, Unit provides you with the API only. It solves relations in a data model internally but, to make everything work properly, you have to implement it, too. Among many others, we had to implement the following relations:

  • One customer can be accessed by multiple users
  • One customer can have multiple accounts
  • One account can be shared between multiple customers

Transactions

With transactions, it was quite a different challenge. Currently (July ‘22), Unit recognizes 23 transaction types. When you want to aggregate all of them while every single one has a different payload, it’s challenging. And if you also want to do something like summarize the month or include authorizations (cards) and payments, that poses another challenge — because the direction (credit or debit) of payments and transactions is opposite. So, you have to filter the data quite a lot.

Notifications

We were looking for a notifications system that would suit our needs but, unfortunately, we didn't find anything like that. So, we implemented it ourselves.

Did I mention that Unit can send you a webhook event once an event from their webhook-supported events occurs? That’s right. When a transaction has been created, you receive a transaction-created webhook event to which you can react. You create a notification, save it into the database and then return it once frontend asks for notifications.

The cool thing is that you can also create a routing system for frontend. Let's say that frontend gets a transaction-created notification, and they would like to open the notification. Essentially, you have two options:

  • Construct the URL for frontend - you'd return something like https://my.frontend/transaction/123
  • Provide frontend with data needed to construct the URL - you'd return a structure containing the event and unique identifiers

The second option is way better because if frontend changes its routes in the future, you will have your notifications backward-compatible. Additionally, you can send emails for every event!

Webhook Locks

As I said, Unit can send you webhooks with events. Those events are sent for almost every operation in Unit. To name a few:

  • Transaction created
  • Card activated
  • Payment sent
  • Account blocked

These events have two delivery modes: "at least once" and "at most once."

For the "at least once" delivery mode, we had to implement a webhook locking system so that the same event doesn't get processed twice. The solution for this was fairly simple; we’re just saving the id of the event in the database and checking for the status of the event. If the status is “finished” or “processing,” the next webhook with this id isn't processed.

In Conclusion

When building a more complicated backend, always look for solutions and third-party software that are already out there. It’ll make your life way easier — not only from the implementation perspective but, more importantly, from the security perspective. Especially if you’re working on a fintech app, there are many services to choose from.

And a final reminder: Never store any sensitive data in your database. And if you do, make sure that even if an attacker would be successful in getting the data, it would be worthless.

I’d like to thank my fellow STRV backend engineer Pavel Knotek for working with me on this project. Couldn’t have done it without you, bud!

Share this article


What to see next...


Sign up to our newsletter

Monthly updates, real stuff, our views. No BS.